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

时间:2022-07-22
本文章向大家介绍猿进化系列13——一文搞懂MVC相关框架套路,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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

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

JSP Model1刚开始的时候,谁用谁爽,简单直接敞开撸,可是慢慢的系统越来越庞大,java代码,html代码,css代码,js代码……都在一个页面里,各种标签还满天飞,没法维护了。最后,大家发现各种代码还是分开写比较好。于是servlet就被再次利用了起来——好歹是个堆代码的地方,代码和标签不用放在一起,于是就搞出了下面这种模式——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之后再进行渲染,从而呈现出多姿多彩的界面。

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

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

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

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

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

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";



    }

}

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