猿进化系列13——一文搞懂MVC相关框架套路

Posted 猿人工厂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了猿进化系列13——一文搞懂MVC相关框架套路相关的知识,希望对你有一定的参考价值。

看完上一个章节,相信你已经掌握了JDBC访问数据库的基本操作,也学会了使用数据源和数据库连接池,还学会了一个小框架——SpringJbdcTemplete,回过头来看看,似乎你已经掌握了,不少东西了,java,jsp,servlet,jstl,jdbc……看起来已经有足够的实力去开发一个动态web站点了。不过在这之前,似乎有一些东西还需要思考一下。

  

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路



      在聊web框架之前,我们先来思考一个事情——为什么会有JSP?我们知道JSP运行在服务端,可以在页面中编写java代码,甚至可以在页面中访问数据库,然后生成一段html代码,然后发给客户端,大大的简化了远古时期的应用开发问题——在servlet中使用out输出HTML代码。后来有的人认为这样真的很爽,JSP页面相互就能完成跳转,servlet几乎用不上,还专门给起了个名字——JSP Model1.

    

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路



JSP Model1刚开始的时候,谁用谁爽,简单直接敞开撸,可是慢慢的系统越来越庞大,java代码,html代码,css代码,js代码……都在一个页面里,各种标签还满天飞,没法维护了。最后,大家发现各种代码还是分开写比较好。于是servlet就被再次利用了起来——好歹是个堆代码的地方,代码和标签不用放在一起,于是就搞出了下面这种模式——MVC.

 

猿进化系列13——一文搞懂MVC相关框架套路

MVC是Model ViewController(模型-视图-控制器)的缩写,被广泛的应用到开发工作中。

Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。

View(视图)是应用程序中处理数据显示的部分。通常视图是依据模型数据创建的。

Controller(控制器)是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

MVC模式对软件开发有着极为重要的意义:有助于程序的分层开发,可以让人专注于某一方面的逻辑,比如不依赖业务逻辑也能开发视图层面的东西。对程序来说也是一个解耦的过程,各类程序分开编写,有助于代码的维护。更可贵的是,正是因为有了这样的分层,让职业更加细分,最早开发程序,大家都是一个人从头撸到尾,现在可以让擅长展示层面的人发挥特长,做前端工程师,让擅长数据处理,逻辑处理的人做后端工程师,干活的人更加专业、高效。在人力资源充足的情况下,做一个完整的项目,时间上大大缩短,质量还更加可靠。

JavaBean充当了模型的概念,有些人误认为模型就是JavaBean,JavaBean是什么?JavaBean只包含了构造方法、私有成员变量、公共的getter和setter方法。如何能够负担程序的数据逻辑呢?如果硬要说承担,也只能是数据的载体。准确的说法就是Model,因为它不止有属性,还包含了数据的访问程序。

Servlet作为控制器的角色,承接了和用户的交互,获取数据,并向模型发送数据。这倒是没什么毛病,虽然有很多web框架,但是几乎所有的web框架都实际上是对Servlet进行封装,提供了很多框架性的工具,完成框架的职责。

JSP作为视图层处理处理数据显示,但是实际上在视图这个层面来讲,处理展示JSP到现在几乎没什么用武之地了。因为用户看到的永远都是HTML.而渲染HTML出来的技术有很多,JSP只是其中一种,现在用得相对较多的是模板渲染技术,比如velocity、free marker等等。JSP也好,模板技术也好,使用它们做web开发时的作用绝大多数只有一个——在服务端渲染出相应的视图(最多的时候是HTML)返回给客户端,客户端拿到HTML之后再进行渲染,从而呈现出多姿多彩的界面。

 

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

但实际上web开发发展到今天,由于用户的机器越来越好,带宽也越来越大,浏览器的职责也越来越多。在过去,浏览器不能做(毕竟浏览器搞太多的事情容易卡死,你家也不是1024,卡死用户就不来了)的计算,可以更多的放在浏览器去做了——服务端返回数据和HTML,浏览器根据HTML和数据进行渲染得最后的页面展示,前后端的开发也彻底分家,这样做有些好处:

1.HTML无需后端程序的过多响应,返回就好,页面内容有兜底,在极端情况下(比如后端程序错误),不会看到丑陋的500或者404,用户体验相对好。

2.javascript是一个动态语言,运行在浏览器(搞懂代码到底在哪里执行是需要大家长期关注的问题),使用的是用户的机器,从某种意义上讲,降低了服务器资源的开销(渲染页面最终是IO操作产生的HTML),用别人家的电费就是爽。

3.在处理文件IO这件事情上,web服务器比应用服务器强悍多了,规避应用服务器的IO操作(莫信那些JAVA IO很强的谣言,至少目前还没哪个JAVA开发的应用服务器,在处理IO上面超过C语言的)的短板。

当然,也有不足,这样做的开发代价更高(一份工给两份钱),需要用户的基本要求也更高(要求更好的机器,更好的带宽,只是现在大家的机器都比较好,网络比以前好很多,是个能上网的机器就行)。

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路

猿进化系列13——一文搞懂MVC相关框架套路



JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

听起来有点绕口,简单点讲,你可以在程序运行的时候,获取一个类的属性、方法、Constructor、annotation,并且可以调用一个类的方法。java.lang.reflect包下定义了几个类分别代表对类的抽象。

java.lang.Class 抽象类的信息,通过它可以获取类的属性,方法,构造器

java.lang.Method抽象方法的信息,通过它可以直接调用某个对象的方法。

java.lang.Field  抽象属性的信息。

java.lang.Constructor抽象类的构造方法的信息。

java.lang.Annotation抽象Annotation的信息,Annotation是给程序提供元数据的东西,可以把程序的配置放在里面。

具体的API本文就不具体阐述了,自行度娘或者等猿人工厂君的后续专栏。


下面我就讲讲一个自己定义简单的web框架,功能很简陋基本意思要有——解决url的映射、参数简单封装、数据转发,便于大家以后的学习。

框架都有自己的配置,通常都是基于xml的,当然,现在基于元数据(annotion)的越来越流行,各有各的优点吧,这次我们采用annotion。

先定义两个annotion:

packagecom.pz.web.frame.annotation;



import
java.lang.annotation.ElementType;

import
java.lang.annotation.Retention;

import
java.lang.annotation.RetentionPolicy;

import
java.lang.annotation.Target;



@Retention(RetentionPolicy.RUNTIME)

@Target({ ElementType.METHOD })

public @interface RequestURI {



    String
url() default "";



}
package com.pz.web.frame.annotation;



import
java.lang.annotation.ElementType;

import
java.lang.annotation.Retention;

import
java.lang.annotation.RetentionPolicy;

import
java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

@Target({ ElementType.TYPE })

public @interface WebController {



}

 

有WebController这个标记的就代表我们处理业务的servlet类了。

RequestURI这个标记的就代表我们处理业务的具体方法了,使用它去定义请求和方法映射。

考虑到反射的性能问题,简单的把一个类的方法抽象出来,放在内存里,这样提升一些性能。

packagecom.pz.web.frame.config;



import
java.lang.reflect.Method;



public class
MethodInfo {

   
/**

     * method
对象

     */

   
private Method method;



    private
Class[] getParameterTypes;



    public
Method getMethod() {

       
return method;

   
}



   
public void setMethod(Method method) {

       
this.method = method;

   
}



   
public Class[] getGetParameterTypes() {

       
return getParameterTypes;

   
}



   
public void setGetParameterTypes(Class[] getParameterTypes) {

       
this.getParameterTypes = getParameterTypes;

   
}

}

接下来的事情就是抽象和加载我们的配置了。

package com.pz.web.frame.config;



import
com.pz.web.frame.annotation.RequestURI;

import
com.pz.web.frame.annotation.WebController;

import
com.pz.web.frame.util.ClassUtils;



import
java.io.IOException;

import
java.io.InputStream;

import
java.lang.reflect.Method;

import
java.util.HashMap;

import
java.util.List;

import
java.util.Map;

import
java.util.Properties;



public class
WebConfiguration {
//暂时就不定义拦截器这类的东西了

   
private static Map<String,MethodInfo> requestMapping = new HashMap<String,MethodInfo>();

    private static
Map<Class,Object> methodClassMapping = new HashMap<Class,Object>();

    public static void
init() throws InstantiationException, IllegalAccessException{

        InputStream stream = ClassUtils.
class.getClassLoader().getResourceAsStream("web-frame.properties");

       
Properties properties = new Properties();

        try
{

            properties.load(stream)
;

       
} catch (IOException e) {

            e.printStackTrace()
;

       
}



        String packageName=properties.getProperty(
"package").toString();



       
List<String> controllerList= ClassUtils.getClassName(packageName, true);



        for
(String controller:controllerList){

            System.
out.println(controller);

            try
{

                Class clazz=Class.forName(controller)
;

               
methodClassMapping.put(clazz, clazz.newInstance());

                if
(clazz.isAnnotationPresent(WebController.class)){

                    System.
out.println("找到了controller...");

                   
Method[] methods = clazz.getMethods();

                    if
(null!=methods){

                       
for(Method method:methods){



                           
if(method.isAnnotationPresent(RequestURI.class)){

                                System.
out.println("找到了method..."+method.getName());



                                
RequestURI uriData=method.getAnnotation(RequestURI.class);

                               
String url=uriData.url();



                               
MethodInfo methodInfo = new MethodInfo();

                               
methodInfo.setGetParameterTypes(method.getExceptionTypes());

                               
methodInfo.setMethod(method);

                               
requestMapping.put(url, methodInfo);

                           
}



                        }

                    }

                }

            }
catch (ClassNotFoundException e) {

               
// TODO Auto-generated catch block

               
e.printStackTrace();

           
}

        }



       
for(String url:requestMapping.keySet()){

            System.
out.println(url+":"+requestMapping.get(url));



           
System.out.println("Object:"+getTargetObject(url));



       
}



    }



   
/**

     *
获取请求对应的方法

     * @param
uri

    
*/

   
public static MethodInfo getMethod(String uri){



       
return requestMapping.get(uri);

   
}



   
/**

     *
获取请求对应的方法的对象

     * @param
uri

    
*/

   
public static Object getTargetObject(String uri){



       
return methodClassMapping.get(requestMapping.get(uri).getMethod().getDeclaringClass());

   
}



   
public static void main(String args[]) throws InstantiationException, IllegalAccessException{

        init()
;

   
}



}

web服务器给我们留的请求处理接口实际上就是servlet,我们做下简单封装,这次仅仅实现了请求的统一转发,调用对应的方法,如果需要对流程进行统一处理,简单点搞,可以定义一个叫做handler接口,提供初始化,调用前,调用后的方法,供外部实现,然后封装个集合,方到配置中,然后改造下面的执行流程,在调用业务方法前后,循环调用即可,倒不一定用反射了。我是怎么想到的?猜的吧,反正web框架啊的套路基本就那些的。翻翻MVC框架源码,struts,struts2,springMVC,大家大体这个意思。

packagecom.pz.web.frame.servlet;



import
com.pz.web.frame.config.MethodInfo;

import
com.pz.web.frame.config.WebConfiguration;



import
javax.servlet.ServletException;

import
javax.servlet.http.HttpServlet;

import
javax.servlet.http.HttpServletRequest;

import
javax.servlet.http.HttpServletResponse;

import
java.io.IOException;



public class
DispacherServlet extends HttpServlet {





   
@Override

   
public void init() throws ServletException {

       
try {



            WebConfiguration.init()
;

       
} catch (InstantiationException | IllegalAccessException e) {

           
// TODO Auto-generated catch block

           
e.printStackTrace();

       
}

    }



   
@Override

   
protected void service(HttpServletRequest request, HttpServletResponse response)

           
throws ServletException, IOException {





        String uri=request.getRequestURI().toString()
;



       
MethodInfo targetMethodInfo= WebConfiguration.getMethod(uri);

       
Object targetObject=WebConfiguration.getTargetObject(uri);

       
//todo 404

       
if(null==targetMethodInfo||null==targetObject){

            response.sendError(
404);

       
}



       
try

       
{

            Class[] classes =targetMethodInfo.getGetParameterTypes()
;



           
Object[] args=handelParameter(targetMethodInfo,request,response);


//这里可以加入统一的前置流程处理噢
           
Object viewObject= targetMethodInfo.getMethod().invoke(targetObject,args);
          //这里可以加入统一的后置流程处理噢
            if(null==viewObject){

               
return ;

           
}

            request.getRequestDispatcher((String) viewObject).forward( request
, response );



       
} catch (Exception e) {

           
// TODO Auto-generated catch block

           
e.printStackTrace();

       
}

    }



   
private Object[] handelParameter(MethodInfo methodInfo,HttpServletRequest request, HttpServletResponse response) {



        Class[] params=methodInfo.getGetParameterTypes()
;



       
Object[] objArray=new Object[params.length];



        for
(int i=0;i<params.length;i++){

            Object obj=
null;

            try
{

                obj = params[i].newInstance()
;

           
} catch (InstantiationException e) {



            }
catch (IllegalAccessException e) {



            }

           
if(obj instanceof HttpServletRequest || obj instanceof HttpServletResponse){

                objArray[i]=params[i]
;

           
}else{

              
//todo 暂时就不搞对象转换了

            
}



        }



       
return objArray;

   
}







}

 

工具类

packagecom.pz.web.frame.util;



import
com.pz.web.frame.annotation.RequestURI;

import
com.pz.web.frame.annotation.WebController;



import
java.io.File;

import
java.io.IOException;

import
java.io.InputStream;

import
java.lang.reflect.Method;

import
java.net.URL;

import
java.util.*;



public class
ClassUtils {



   
/**

     *
获取某包下所有类

     *

     * @param
packageName

    
*            包名

     * @param
childPackage

    
*            是否遍历子包

     * @return 类的完整名称

     */

   
public static List<String> getClassName(String packageName, boolean childPackage) {

        List<String> fileNames =
null;

       
ClassLoader loader = Thread.currentThread().getContextClassLoader();

       
String packagePath = packageName.replace(".", "/");

       
URL url = loader.getResource(packagePath);

        if
(url != null) {

            String type = url.getProtocol()
;

            if
(type.equals("file")) {

                fileNames = getClassNameByFile(url.getPath()
, null, childPackage);

           
}

        }

       
return fileNames;

   
}





   
/**

     *
从项目文件获取某包下所有类

     *

     * @param
filePath

    
*            文件路径

     * @param
className

    
*            类名集合

     * @param
childPackage

    
*            是否遍历子包

     * @return 类的完整名称

     */

   
private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {

        List<String> myClassName =
new ArrayList<>();

       
File file = new File(filePath);

       
File[] childFiles = file.listFiles();

        for
(File childFile : childFiles) {

           
if (childFile.isDirectory()) {

               
if (childPackage) {

                    myClassName.addAll(getClassNameByFile(childFile.getPath()
, myClassName, childPackage));

               
}

            }
else {

                String childFilePath = childFile.getPath()
;

                if
(childFilePath.endsWith(".class")) {

                    childFilePath = childFilePath.substring(childFilePath.indexOf(
"/classes") + 9,

                           
childFilePath.lastIndexOf("."));

                   
childFilePath = childFilePath.replace("/", ".");

                   
myClassName.add(childFilePath);

               
}

            }

        }



       
return myClassName;

   
}





   
public static void main(String args[]){



        Map<String
,Method> requestMapping = new HashMap<String,Method>();



       
InputStream stream = ClassUtils.class.getClassLoader().getResourceAsStream("web-frame.properties");

       
Properties properties = new Properties();

        try
{

            properties.load(stream)
;

       
} catch (IOException e) {

            e.printStackTrace()
;

       
}



        String packageName=properties.getProperty(
"package").toString();



        
List<String> controllerList= ClassUtils.getClassName(packageName, true);



        for
(String controller:controllerList){

            System.
out.println(controller);

            try
{

                Class clazz=Class.forName(controller)
;

                if
(clazz.isAnnotationPresent(WebController.class)){

                    System.
out.println("找到了controller...");

                   
Method[] methods = clazz.getMethods();

                    if
(null!=methods){

                       
for(Method method:methods){

                           
if(method.isAnnotationPresent(RequestURI.class)){

                                System.
out.println("找到了method..."+method.getName());

                               
RequestURI uriData=method.getAnnotation(RequestURI.class);

                               
String url=uriData.url();

                               
requestMapping.put(url, method);

                           
}



                        }

                    }

                }

            }
catch (ClassNotFoundException e) {

               
// TODO Auto-generated catch block

               
e.printStackTrace();

           
}

        }



       
for(String url:requestMapping.keySet()){

            System.
out.println(url+":"+requestMapping.get(url));



       
}



    }



}

 

Web.xml配置

<!DOCTYPE web-app PUBLIC

 "-//SunMicrosystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd">

 

<web-app>

  <servlet>

     <servlet-name>DispacherServlet</servlet-name>

     <servlet-class>com.pz.web.frame.servlet.DispacherServlet</servlet-class>

     <load-on-startup>1</load-on-startup>

  </servlet>

  <servlet-mapping>

     <servlet-name>DispacherServlet</servlet-name>

     <url-pattern>*.pz</url-pattern>

  </servlet-mapping>

</web-app>

 

 

测试一下

packagecom.pz.web.biz;



import
com.pz.web.frame.annotation.RequestURI;

import
com.pz.web.frame.annotation.WebController;

@WebController

public class MyTestController {

   
@RequestURI(url="/mytest.pz")

   
public String mytest(){

       
return "index.jsp";



   
}

}

 下一次咱们可以开始实战了噢。

以上是关于猿进化系列13——一文搞懂MVC相关框架套路的主要内容,如果未能解决你的问题,请参考以下文章

一文搞懂动态规划的套路

总结系列-一文搞懂沉浸式状态栏

总结系列-一文搞懂沉浸式状态栏

RPC基础系列2一文搞懂gRPC和Thrift的基本原理和区别

Android Flutter完整开发实战详解,一文搞懂Flutter框架

[新星计划]一文快速搞懂系列__一文快速搞懂SuperSet[实战案例]