• 《Java Spring框架》基于Tomcat模拟实现SpringMVC


    前言

    Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts 2(一般老项目使用)等。

    原理

    SpringMVC 原理图

    代码

    创建一个web项目

    准备两个jar包(dom4j-1.6.1.jar和javax.servlet-api-3.1.0.jar)

    百度网盘下载地址:https://pan.baidu.com/s/1BjC34AYtjf17g5nZ2Hr4HA 提取码: 6yad

    完整目录结构如下:

    MyMVC.xml

    <beans>
        <compentScan package="com"></compentScan>
    
        <view prefix = "/page/" suffix=".html"></view>
    </beans>

    MyController

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyController {
    }

    MyRequestMapping

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyRequestMapping {
    
        /**
         * url 的拦截地址
         * @return
         */
        String value() default "";
    
    }

    MyResponseBody

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyResponseBody {
    }

    TestController

    import com.annotation.MyController;
    import com.annotation.MyRequestMapping;
    import com.annotation.MyResponseBody;
    
    @MyController
    public class TestController {
    
        @MyRequestMapping(value = "/test.do")
        @MyResponseBody
        public Object test(String name){
            return "hello world";
        }
    
        @MyRequestMapping(value = "/test1.do")
        public Object test1(String name){
            return "index";
        }
    }

    MyServlet

    import com.annotation.MyController;
    import com.annotation.MyRequestMapping;
    import com.annotation.MyResponseBody;
    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    import java.util.HashMap;
    import java.util.Map;
    
    public class MyServlet extends HttpServlet {
    
        private static String  COMPENT_SCAN_ELEMENT_PACKAGE_NAME= "package";
        private static String COMPENT_SCAN_ELEMENT_NAME = "compentScan";
        private static String XML_PATH_LOCAL= "xmlPathLocal";
        /**
         * 视图前缀
         */
        private  static String prefix = "";
        /**
         * 视图后缀
         */
        private  static String suffix = "";
        private static String projectPath = MyServlet.class.getResource("/").getPath();
        private  static Map<String,Method> methodMap = new HashMap<>();
    
        /**
         * Tomcat容器会根据web.xml中的配置,执行init方法(类需要继承HttpServlet)
         * 第一步:通过tomcat解析web.xml的方式,获取xmlPathLocal对应的自定义XML。
         * 第二步:解析自定义的xml(MyMVC.xml)
         * 第三步:根据MyMVC.xml,设置的规则扫描controller和视图文件,并将对应信息写入methodMap中
         * @param config
         * @throws ServletException
         */
        @Override
        public void init(ServletConfig config) throws ServletException {
            // 进行url转义会将空格变成%20,所以需要转化回来
            projectPath = projectPath.replaceAll("%20"," ");
            String initParameter = config.getInitParameter(XML_PATH_LOCAL);
            // 解析xml文件 file:xml 文件对象
            File file = new File(projectPath + "//" + initParameter);
            // 使用dom4j解析XML
            Document prase = prase(file);
            Element rootElement = prase.getRootElement();
            Element view = rootElement.element("view");
            prefix = view.attribute("prefix").getValue();
            suffix = view.attribute("suffix").getValue();
            Element compentScanEle = rootElement.element(COMPENT_SCAN_ELEMENT_NAME);
            String value = compentScanEle.attribute(COMPENT_SCAN_ELEMENT_PACKAGE_NAME).getValue();
            // 根据解析后信息,扫描controller和视图文件
            scanProjectByPath(projectPath+"\"+value);
        }
    
        /**
         * 扫描文件
         * @param path
         */
        public void scanProjectByPath(String path){
            File file =new File(path);
            //递归解析项目所有文件
            scanFile(file);
        }
    
        /**
         * 递归扫描
         * @param file
         */
        public void scanFile(File file){
            //递归解析项目
            if (file.isDirectory()){
                for (File file1 : file.listFiles()) {
                    scanFile(file1);
                }
            }else{
                String filePath =  file.getPath();
                String suffix =filePath.substring(filePath.lastIndexOf("."));
                if (suffix.equals(".class")){
                    String classPath  =  filePath.replace(new File(projectPath).getPath()+"\","");
                    classPath = classPath.replaceAll("\\",".");
                    String className = classPath.substring(0,classPath.lastIndexOf("."));
                    try {
                        Class<?> clazz = Class.forName(className);
                        if (clazz.isAnnotationPresent(MyController.class)) {
                            MyRequestMapping classRequestMapping = clazz.getAnnotation(MyRequestMapping.class);
                            String classRequestMappingUrl = "";
                            if (classRequestMapping!=null){
                                classRequestMappingUrl = classRequestMapping.value();
                            }
                            for (Method method : clazz.getDeclaredMethods()) {
                                if (!method.isSynthetic()) {
                                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                                    if (annotation != null) {
                                        String methodRequsetMappingUrl  = "";
                                        methodRequsetMappingUrl  = annotation.value();
                                        System.out.println("类:"+clazz.getName()+"的"+method.getName()+"方法被映射到了"+classRequestMappingUrl+methodRequsetMappingUrl+"上面");
                                        methodMap.put(classRequestMappingUrl+methodRequsetMappingUrl,method);
                                    }
    
                                }
                            }
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 通过dom4j 将文件变成对象
         * @param file
         * @return
         */
        public Document prase(File file){
            SAXReader saxReader = new SAXReader();
            try {
                return saxReader.read(file);
            } catch (DocumentException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req, resp);
        }
    
        /**
         * 获取请求,然后转发到对应controller中
         * @param req
         * @param resp
         * @throws ServletException
         * @throws IOException
         */
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //拿到请求的URI
            String requestURI = req.getRequestURI();
            Method method = methodMap.get(requestURI);
            if (method!=null){
                //jdk8以前 直接拿参数名称 拿不到
                Parameter[] parameters = method.getParameters();
                Object[] objects = new Object[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    String name = parameter.getName();
                    Class type = parameter.getType();
                    if (type.equals(String.class)){
                        objects[i] = req.getParameter(name);
                    }else if(type.equals(HttpServletRequest.class)){
                        objects[i] = req;
                    }else if(type.equals(HttpServletResponse.class)){
                        objects[i] = resp;
                    }else{
                        try {
                            Object o = type.newInstance();
                            for (Field field : type.getDeclaredFields()) {
                                field.setAccessible(true);
                                String fieldName = field.getName();
                                field.set(o,req.getParameter(fieldName));
                            }
                            objects[i] = o;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
    
                try {
                    Object o= null;
                    o = method.getDeclaringClass().newInstance();
                    Object invoke = method.invoke(o, objects);
                    // 判断返回值是否是Void
                    if (!method.getReturnType().equals(Void.class)){
                        MyResponseBody annotation = method.getAnnotation(MyResponseBody.class);
                        if (annotation!=null){
                            //提供接口来做这个事情
                            resp.getWriter().write(String.valueOf(invoke));
                        }else {
                            // 返回视图信息
                            req.getRequestDispatcher(prefix+String.valueOf(invoke)+suffix).forward(req,resp);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }else {
                resp.setStatus(404);
            }
        }
    }

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>这里是index.xml</h1>
    </body>
    </html>

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <display-name>Archetype Created Web Application</display-name>
        <servlet>
            <servlet-name>dispatcher</servlet-name>
            <servlet-class>com.servlet.MyServlet</servlet-class>
            <init-param>
                <!-- contextConfigLocation 是参数名称,该参数的值包含 Spring MVC 的配置文件路径 -->
                <param-name>xmlPathLocal</param-name>
                <param-value>MyMVC.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>dispatcher</servlet-name>
            <url-pattern>*.do</url-pattern>
        </servlet-mapping>
    </web-app>

    部署tomcat
    直接部署会缺少jar包

    总结

    通过这方式可以让我们更好的理解SpringNVC的原理,遇到问题也可以帮助你更好去处理。以上案例只是简单的模拟,真正的SpringMVC是有很强的扩展性,这大概也是为什么大家都选择用的原因。

    This moment will nap, you will have a dream; But this moment study,you will interpret a dream.
  • 相关阅读:
    spring中用到哪些设计模式?
    配置监听器使项目启动时创建消费者
    ActiveMQ依赖JDK版本关系
    ActiveMQ之topic主题模式
    ActiveMQ与Spring整合
    ac自动机模板
    poj 3735 Training little cats矩阵快速幂
    KMP算法模板 求子串和模板串首先匹配的位置
    hdoj 2665 Kth number主席树裸
    vijos P1081野生动物园 主席树求区间第K大
  • 原文地址:https://www.cnblogs.com/jssj/p/13001335.html
Copyright © 2020-2023  润新知