2019.11.14笔记——模拟SpringMVC

为了模拟SpringMVC,首先了解一下正常SpringMVC的启动流程和它的一些配置
详细的应用参考《java框架复习——springmvc》

SpringMVC启动

引入依赖

springmvc依赖

<dependencies>
    <!--
        springmvc的依赖
        其中包含了core和context的依赖
     -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.0.8.RELEASE</version>
    </dependency>
</dependencies>

包含了core和context依赖,不需要我们单独引入
在这里插入图片描述

配置SpringMVC

这里是用的xml的方式配置的

  1. web.xml

在这里插入图片描述
在这里插入图片描述

  1. spring-mvc.xml

在这里插入图片描述

模拟SpringMVC

引入依赖

只需要dom4j和servlet的依赖

<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.6.1</version>
</dependency>


<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>

这里还需要一个tomcat容器,这里使用的tomcat插件

<plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>2.2</version>
  <configuration>
    <port>80</port>
    <path>/</path>
  </configuration>
</plugin>

这里如果启动的tomcat请参考《tomcat插件式启动》

tomcat配置

我们知道在配置springmvc需要配置一个DispatcherServlet,我们如果要模拟一个SpringMVC,那也就需要自己写一个这样的Servlet。

当然首先得再tomcat中配置一个Servlet

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>zddServlet</servlet-name>
    <servlet-class>com.zdd.servlet.ZddServlet</servlet-class>
    <init-param>
      <param-name>XmlPath</param-name>
      <param-value>zdd-mvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>zddServlet</servlet-name>
    <url-pattern>/*.do</url-pattern>
  </servlet-mapping>
  
</web-app>

mvc配置

同样我们需要一个mvc得配置,用来配置扫描的路径和访问路径的前缀和后缀

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

Servlet拦截器

springmvc的核心就是这个Servlet拦截器,它负责拦截访问的请求,并执行真正的Controller的逻辑或者将请求转发到指定的路径。

package com.zdd.servlet;

import com.zdd.annotation.Controller;
import com.zdd.annotation.RequestMapping;
import com.zdd.annotation.ResponseBody;
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 ZddServlet 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= "xmlPath";

    private  static String prefix = "";
    private  static String suffix = "";

    private static String projectPath = ZddServlet.class.getResource("/").getPath();

    private  static Map<String, Method> methodMap = new HashMap<>();


    /**
     * init主要做得事情:
     * 加载配置文件 web.xml 加载spring mvc.xml
     扫描整个项目 根据配置文件给定的目录来扫描
     扫描所有加了@Controller注解的类
     当扫描到加了@Controller注解的类之后遍历里面所有的方法
     拿到方法对象之后 解析方法上面是否加了@RequestMapping注解
     定义一个Map集合  吧@RequstMapping的Value 与方法对象绑定起来
     Map<String,Method>
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //解析xml  解析web.xml 解析lubanMvc.xml
//        lubanMvc.xml
        //initParameter 就是用户在web.xml里面指定的配置文件的地址 :lubanMvc.xml
//        \\  =\   //  d:\\\\  = d:\\  d: =
        //projectPath需要进行url转义 空格 会变成%20
//        URLEncoder;
//        URLDecoder;
        projectPath = projectPath.replaceAll("%20"," ");
        String initParameter = config.getInitParameter(XML_PATH_LOCAL);
        //解析xml文件 file:xml 文件对象
        File file = new File(projectPath + "//" + initParameter);
        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);
        // value:com
        String value = compentScanEle.attribute(COMPENT_SCAN_ELEMENT_PACKAGE_NAME).getValue();
        scanProjectByPath(projectPath+"\\"+value);
        //        element.element("A")
        //扫描项目
//        super.init(config);
    }

    public void scanProjectByPath(String path){
        File file =new File(path);
        //递归解析项目所有文件
        scanFile(file);
    }

    public void scanFile(File file){
        //递归解析项目
        if (file.isDirectory()){
            for (File file1 : file.listFiles()) {
                scanFile(file1);
            }
        }else{
            //如果不是文件夹
            //D://project//com//TestContrller.class
            //D://project//com//controller//TestController.class
            //com.controller.TestController
            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(Controller.class)) {
                        RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
                        String classRequestMappingUrl = "";
                        if (classRequestMapping!=null){
                            classRequestMappingUrl = classRequestMapping.value();
                        }
                        for (Method method : clazz.getDeclaredMethods()) {
                            if (!method.isSynthetic()) {
                                RequestMapping annotation = method.getAnnotation(RequestMapping.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();
                }


                //Class.forName
            }

        }
    }

    /**
     *
     * @param file :你的xml文件对象
     * @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 {
        doPost(req,resp);
    }

    /**
     * 执行的时候做的事情:
     * 拿到请求URI去map里面get
     * 给参数赋值并调用方法
     * 拿到方法返回值做视图跳转和消息返回
     * @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();
//                        type.getDeclaredConstructor().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)){
                    ResponseBody annotation = method.getAnnotation(ResponseBody.class);
                    if (annotation!=null){
                        //提供接口来做这个事情
                        resp.getWriter().write(String.valueOf(invoke));
                    }else {
                        // /page/index.html   page/index.html
                        req.getRequestDispatcher(prefix+String.valueOf(invoke)+suffix).forward(req,resp);
                    }

                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }else {
            resp.setStatus(404);
        }
        //        super.doPost(req, resp);
    }
}

其中为了模拟springmvc中的注解,这里同样创建了几个和springmvc相同的注解,@Controller、@RequestMapping和@ResponseBody,发挥的作用也基本和springmvc中相同,不过只有几个基本的功能。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {

    String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ResponseBody {

    String value();
}

这个servlet的工作流程分为两个阶段,一个是初始化阶段,也就是再init方法的工作,一个是请求访问阶段,就是再doPost和doGet方法中处理请求。

  • 初始化阶段
  1. 读取web.xml中的配置属性,找到配置mvc的xml文件,读取其中的配置信息。
  2. 首先找到需要扫描的路径,读取其中的class文件,找到需要映射的方法(就是加了@RequestMapping注解的),并且将方法和它的映射路径存储在一个map中。
  3. 读取xml文件的其他配置,比如访问请求路径的前缀和后缀,同样缓存在一个变量中。
  • 请求阶段
  1. 接收到请求的路径,通过这个路径从刚刚缓存的map中拿到对应的方法对象,如果能找到就执行此方法,注意这里需要处理方法中的参数,为了获得参数名需要jdk的版本是1.8以上才能得到参数名。
  2. 对于找到映射的请求,执行完方法后对于方法返回的结果有两种情况,根据注解一种是将结果直接输出到页面上,一种是将请求转发到返回的路径上。
  3. 对于没有找到对应映射的请求直接返回404。

Controller

测试用的一个Controller

@Controller
public class TestController {

    @RequestMapping("/test.do")
    @ResponseBody
    public String Test(){
        return "hello";
    }
}

访问效果
在这里插入图片描述
转发请求

@RequestMapping("/hello.do")
public String hello(){
    return "hello";
}

在这里插入图片描述
效果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值