《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.
原文地址:https://www.cnblogs.com/jssj/p/13001335.html