自己实现SpringMVC 底层机制

自己实现SpringMVC 底层机制[一]

搭建SpringMVC 底层机制开发环境

  1. 创建Maven 项目

  2. 对Myspringmvc 进行配置: 修改my-springmvc\pom.xml , 将1.7修改成1.8.

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
  1. 对my-springmvc 进行配置: 创建main 和test 相关的源码目录和资源目录和测试目录

在这里插入图片描述

  1. 引入需要的基本的jar 包, 修改my-springmvc\pom.xml
 <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!--引入原生servlet依赖 的jar-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--
            1. scope标签表示引入的jar的作用范围
            2. provided:表示该项目在打包,放到生产环境的时候,不需要带上servlet-api.jar
            3. 因为tomcat本身是有servlet的jar, 到时直接使用tomcat本身的servlet-api.jar,防止版本冲突
            -->
            <scope>provided</scope>
        </dependency>

        <!--引入dom4j,解析xml文件-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

        <!--引入常用工具类的jar 该jar含有很多常用的类-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
</dependencies>

自己实现SpringMVC 底层机制【核心分发控制器+ Controller 和Service 注入容器+ 对象自动装配+ 控制器方法获取参数+ 视图解析+ 返回JSON 格式数据】

实现任务阶段1- 开发myDispatcherServlet

编写myDispatcherServlet 充当原生的DispatcherServlet(即核心控制器)

  1. 创建src\main\java\com\myspringmvc\servlet\myDispatcherServlet.java
public class myDispatcherServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws  ServletException, IOException {
        		super.doGet(req, resp);
        }	
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws  ServletException, IOException {
      		  super.doPost(req, resp);
        }
}
  1. 创建src\main\resources\myspringmvc.xml, 充当原生的applicationContext-mvc.xml 文件

(就是spring 的容器配置文件, 比如指定要扫描哪些包下的类), 先创建个空的文件

  1. 修改src\main\webapp\WEB-INF\web.xml, 完成myDispatcherServlet 的配置

    <!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>
      <!--配置myDispatcherServlet, 作为我们自己的前端控制器-->
      <servlet>
        <servlet-name>myDispatcherServlet</servlet-name>
        <servlet-class>com.myspringmvc.servlet.myDispatcherServlet</servlet-class>
        <!--给myDispatcherServlet配置参数,指定要操作的spring容器配置文件-->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:myspringmvc.xml</param-value>
        </init-param>
        <!--myDispatcherServlet在tomcat启动时,就自动加载-->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>myDispatcherServlet</servlet-name>
        <!--因为myDispatcherServlet作为前端控制器,所以需要拦截所有请求,url-pattern配置 /-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    </web-app>
    
    

配置Tomcat, 完成测试

修改my-springmvc\src\main\java\com\servlet\myDispatcherServlet.java

public class myDispatcherServlet extends HttpServlet {
	@Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws  ServletException, IOException {
       		 System.out.println("myDispatcherServlet doGet()被调用");
        }
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws  ServletException, IOException {
        	System.out.println("myDispatcherServlet doPost()被调用");
        }
}

启动Tomcat,完成测试.

实现任务阶段2- 完成客户端/浏览器可以请求控制层

创建自己的Controller 和自定义注解

创建my-springmvc\src\main\java\com\controller\MonsterController.java

public class MonsterController {
    public void listMonsters(HttpServletRequest request, HttpServletResponse response) {
        response.setContentType("text/html;charset=utf-8");
        try {
                PrintWriter printWriter = response.getWriter();
                printWriter.write("<h1>妖怪列表</h1>");
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
}

创建自定义注解,my-springmvc\src\main\java\com\myspringmvc\annotation\Controller.java

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    	String value() default "";
}

创建自定义注解,my-springmvc\src\main\java\com\myspringmvc\annotation\RequestMapping.java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
	String value() default "";
}
配置myspringmvc.xml

在该文件指定,我们的springmvc 要扫描的包

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<component-scan base-package="com.controller"></component-scan>
</beans>
编写XMLParser 工具类,可以解析myspringmvc.xml,得到要扫描的包

创建my-springmvc\src\main\java\com\myspringmvc\xml\XMLPaser.java

//XMLParser 用于解析spring配置文件
public class XMLParser {
    public static String getBasePackage(String xmlFile) {
        SAXReader saxReader = new SAXReader();
        //通过得到类的加载路径-》获取到spring配置文件.[对应的资源流]
        InputStream inputStream =  XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
        try {
            //得到xmlFile文档
            Document document = saxReader.read(inputStream);
            Element rootElement = document.getRootElement();
            Element componentScanElement =  rootElement.element("component-scan");
            Attribute attribute = componentScanElement.attribute("base-package");
            String basePackage = attribute.getText();
            return basePackage;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

创建my-springmvc\src\test\java\com\test\mySpringMvcTest.java 完成测试

public class mySpringMvcTest {
    @Test
    public void readXML() {
        String basePackage = XMLPaser.getbasePackage("myspringmvc.xml");
        System.out.println("basePackage= " + basePackage);
    }
}
开发myWebApplicationContext,充当Spring 容器-得到扫描类的全路径列表

即把指定的目录包括子目录下的java 类的全路径扫描到集合中,比如ArrayList。

扫描后的classFullPathList= 
    [com.controller.MonsterController,
     com.service.impl.MonsterServiceImpl,
     com.service.MonsterService]

创建my-springmvc\src\main\java\com\myspringmvc\context\myWebApplicationContext.java

public class myWebApplicationContext {
    private ArrayList<String> classFullPathList = new ArrayList<>();
	// 初始化自定义的spring 容器, 目标就是把要@Controller 等等初始化到容器中
    public void init() {
        //这里是写的固定的spring容器配置文件=>做活
        //String basePackage = XMLParser.getBasePackage("myspringmvc.xml");
        String basePackage =   XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时basePackage => com.controller,com.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        //测试
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
   
    }

    //创建方法,完成对包的扫描
     //@param pack 表示要扫描的包,比如"com.controller"
    public void scanPackage(String pack) {
        //得到包所在的工作路径[绝对路径]
        //下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
        //比如 "com.controller" => url 是 D:\my-springmvc\target\my-springmvc\WEB-INF\classes\com\controller
        //细节说明: 1. 不要直接使用Junit测试, 否则 url null
        //             2. 启动tomcat来测试
        URL url =
                this.getClass().getClassLoader()
                        .getResource("/" + pack.replaceAll("\\.", "/"));

        //System.out.println("url=" + url);
        //根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList

        String path = url.getFile();
        System.out.println("path= " + path);
        //在io中,把目录,视为一个文件
        File dir = new File(path);
        //遍历dir[文件/子目录]
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {//如果是一个目录,需要递归扫描
                scanPackage(pack + "." + f.getName());
            } else {
                //说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
                //就算是.class, 也存在是不是需要注入到容器
                //目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
                String classFullPath =
                        pack + "." + f.getName().replaceAll(".class", "");
                classFullPathList.add(classFullPath);
            }
        }

    }
}

修改com\myspringmvc\servlet\MyWebApplicationContext.java , 增加

@Override
public void init(ServletConfig config) throws ServletException {
    MyWebApplicationContext myWebApplicationContext = new   myWebApplicationContext();
    myWebApplicationContext.init();
}

启动Tomcat 完成测试,看看扫描是否成功. 需要使用Tomcat 启动方式完成测试,直接用Junit 测试URL 是null

完善myWebApplicationContext,充当Spring 容器-实例化对象到容器中。

将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service…), 反射注入到ioc 容器。

扫描后的ioc={monsterController=com.controller.MonsterController@690aac1e}。

修改com\myspringmvc\context\MyWebApplicationContext.java

public class MyWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayList<>();
//ioc 用于存放反射后的bean 对象.
private ConcurrentHashMap<String, Object> ioc =  new ConcurrentHashMap<>();
	// 初始化自定义的spring 容器, 目标就是把要@Controller 等等初始化到容器中
    public void init() {
        //这里是写的固定的spring容器配置文件.?=>做活
        //String basePackage = XMLParser.getBasePackage("Myspringmvc.xml");
        String basePackage =
                XMLParser.getBasePackage(configLocation.split(":")[1]);
        //这时basePackage => com.controller,com.service
        String[] basePackages = basePackage.split(",");
        //遍历basePackages, 进行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                scanPackage(pack);//扫描
            }
        }
        System.out.println("扫描后的= classFullPathList=" + classFullPathList);
        //将扫描到的类, 反射到ico容器
        executeInstance();
        System.out.println("扫描后的 ioc容器= " + ioc);

        //完成注入的bean对象,的属性的装配
        executeAutoWired();
        System.out.println("装配后 ioc容器= " + ioc);
    }

    //创建方法,完成对包的扫描

    /**
     * @param pack 表示要扫描的包,比如"com.controller"
     */
    public void scanPackage(String pack) {

        //得到包所在的工作路径[绝对路径]
        //下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
        //比如 "com.controller" => url 是 D:\My-springmvc\target\My-springmvc\WEB-INF\classes\com\controller
        //细节说明: 1. 不要直接使用Junit测试, 否则 url null
        //             2. 启动tomcat来吃测试
        URL url = this.getClass().getClassLoader()
                        .getResource("/" + pack.replaceAll("\\.", "/"));

        //System.out.println("url=" + url);
        //根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList

        String path = url.getFile();
        System.out.println("path= " + path);
        //在io中,把目录,视为一个文件
        File dir = new File(path);
        //遍历dir[文件/子目录]
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {//如果是一个目录,需要递归扫描
                scanPackage(pack + "." + f.getName());
            } else {
                //说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
                //就算是.class, 也存在是不是需要注入到容器
                //目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
                String classFullPath =
                        pack + "." + f.getName().replaceAll(".class", "");
                classFullPathList.add(classFullPath);
            }
        }

    }
}

完成测试(提示: 启动tomcat 来加载MyDispatcherServlet 的方式测试)

完成请求URL 和控制器方法的映射关系

示意图分析

-将配置的@RequestMapping 的url 和对应的控制器-方法映射关系保存到集合中
如图

handlerList= [MyHandler{url='/monster/list',
controller=com.controller.MonsterController@714dab1, method=public void
com.controller.MonsterController.listMonsters(javax.servlet.http.HttpServletReques
t,javax.servlet.http.HttpServletResponse)}]

创建src\main\java\com\myspringmvc\handler\MyHandler.java

//MyHandler 对象记录请求的 url 和 控制器方法映射关系
public class MyHandler {
    private String url;
    private Object controller;
    private Method method;

    public MyHandler(String url, Object controller, Method method) {
        this.url = url;
        this.controller = controller;
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    @Override
    public String toString() {
        return "MyHandler{" +
                "url='" + url + '\'' +
                ", controller=" + controller +
                ", method=" + method +
                '}';
    }
}

修改src\main\java\com\myspringmvc\servlet\MyDispatcherServlet.java

public class MyDispatcherServlet extends HttpServlet {
    //存放url 和控制器-方法的映射关系
    private ArrayList<MyHandler> handlerList =
    new ArrayList<>();
    private MyWebApplicationContext myWebApplicationContext;
    @Override
    public void init(ServletConfig config) throws ServletException {
        myWebApplicationContext = new MyWebApplicationContext();
        myWebApplicationContext.init();
        //调用initHandlerMapping()
        搞定完成控制器层url---> Controller ---> 方法的映射关系
        initHandlerMapping();
        //测试(启动Tomcat 装载MyDispatcherServlet 对象方式来测试)
        System.out.println("handlerList= " + handlerList);
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
        ServletException, IOException {
        System.out.println("MyDispatcherServlet doGet()被调用");
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
        ServletException, IOException {
        System.out.println("MyDispatcherServlet doPost()被调用");
    }
   
    /**
    * 1. 完成控制器层url---> Controller ---> 方法的映射关系(该关系封装到 MyHandler 对象)
    * 2. 并放入到handlerList 集合中
    * 3. 后面我们可以通过handlerList 结合找到某个url 请求对应的控制器的方法(!!!)
    */
    private void initHandlerMapping() {
        if(myWebApplicationContext.ioc.isEmpty()) {
        	throw new RuntimeException("spring ioc 容器为空");
   		 }
        for(Map.Entry<String,Object> entry:
        myWebApplicationContext.ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if(clazz.isAnnotationPresent(Controller.class)) {
                    Method[] declaredMethods = clazz.getDeclaredMethods();
                        for (Method declaredMethod : declaredMethods) {
                                if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                                        RequestMapping requestMappingAnnotation =
                                        declaredMethod.getAnnotation(RequestMapping.class);
                                        String url = requestMappingAnnotation.value();
                                        handlerList.add(new   MyHandler(url,entry.getValue(),declaredMethod));
                                }
                        }
                 }
            }
    }
}

完成测试(启动Tomcat , 加载MyDispatcherServlet 方式), 注意看后台输出。

完成MyDispatcherServlet 分发请求到对应控制器方法

当用户发出请求,根据用户请求url 找到对应的控制器-方法, 并反射调用。

如果用户请求的路径不存在,返回404。

修改com\myspringmvc\servlet\MyDispatcherServlet.java

/**
 * 1. MyDispatcherServlet充当原生DispatcherServlet
 * 2. 本质是一个Servlet, 继承HttpServlet
 */
public class MyDispatcherServlet extends HttpServlet {

    //定义属性 handlerList , 保存MyHandler[url和控制器方法的映射]
    private List<MyHandler> handlerList =
            new ArrayList<>();
    //定义属性myWebApplicationContext,自己的spring容器
    MyWebApplicationContextmyWebApplicationContext = null;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

        //获取到web.xml中的 contextConfigLocation
        /*
         <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:myspringmvc.xml</param-value>
        </init-param>
         */
        String configLocation =
                servletConfig.getInitParameter("contextConfigLocation");

        //创建自己的spring容器
       myWebApplicationContext =
                new MyWebApplicationContext(configLocation);

       myWebApplicationContext.init();
        //调用 initHandlerMapping , 完成url和控制器方法的映射
        initHandlerMapping();
        //输出handlerList
        System.out.println("handlerList初始化的结果= " + handlerList);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //System.out.println("--MyDispatcherServlet--doPost---");
        //调用方法,完成分发请求
        executeDispatch(req, resp);
    }

    //编写方法,完成url 和 控制器方法的映射
    private void initHandlerMapping() {
        if (myWebApplicationContext.ioc.isEmpty()) {
            //判断当前的ioc容器是否为null
            return;
        }

        //遍历ioc容器的bean对象,然后进行url映射处理
        //java基础 map的遍历
        for (Map.Entry<String, Object> entry :myWebApplicationContext.ioc.entrySet()) {
            //先取出注入的Object的clazz对象
            Class<?> clazz = entry.getValue().getClass();
            //如果注入的Bean是Controller
            if (clazz.isAnnotationPresent(Controller.class)) {
                //取出它的所有方法
                Method[] declaredMethods = clazz.getDeclaredMethods();
                //遍历方法
                for (Method declaredMethod : declaredMethods) {
                    //判断该方法是否有@RequestMapping
                    if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                        //取出@RequestMapping值->就是映射路径
                        RequestMapping requestMappingAnnotation =
                                declaredMethod.getAnnotation(RequestMapping.class);
                        //这里可以把工程路径+url
                        //getServletContext().getContextPath()
                        // /springmvc/monster/list
                        String url = requestMappingAnnotation.value();
                        //创建MyHandler对象->就是一个映射关系
                        MyHandlermyHandler = new MyHandler(url, entry.getValue(), declaredMethod);
                        //放入到handlerList
                        handlerList.add(myHandler);
                    }
                }
            }
        }
    }
}

完成测试(启动Tomcat)。

注意

因为getRequestURL返回的是主机后的所有内容,为了和handlermapping存放的URL一致,需要把tomcat配置的application context修改成 /,

否则匹配不上,因为按照原来的得到的是my_springmvc/monster/list.

增加方法和控制器

创建my-springmvc\src\main\java\com\controller\OrderController.java

@Controller
public class OrderController {

    @RequestMapping(value = "/order/list")
    public void listOrder(HttpServletRequest request,  HttpServletResponse response)  {
        //设置编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write("<h1>订单列表信息</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @RequestMapping(value = "/order/add")
    public void addOrder(HttpServletRequest request,  HttpServletResponse response)  {
        //设置编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        //获取writer返回信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write("<h1>添加订单...</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨犀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值