深入底层,仿SpringMVC自己写框架

Posted Java大联盟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入底层,仿SpringMVC自己写框架相关的知识,希望对你有一定的参考价值。


前言:


前面的文章我们介绍过如何自己写一个MyBatis框架:


今天我们来写一个SpringMVC框架,相比于写MyBatis框架,SpringMVC要简单一些,只需要xml解析+反射就可以完成,不需要jdk动态代理。


废话不多说,直接开始搂干货。


要自己写框架,必须理解框架的底层原理和运行机制。那么我们首先来回顾SpringMVC的实现原理。


SpringMVC实现原理:


核心组件:


1.DispatcherServlet:前端控制器,是整个流程控制的核心,控制其他组件的执行,统一调度,降低组件之间的耦合性,相当于总指挥。


2.Handler:处理器,完成具体业务逻辑,相当于Servlet或Action。


3.HandlerMapping:DispatcherServlet接收到请求之后,通过HandlerMapping将不同的请求分发到不同的Handler。


4.HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要做一些拦截处理,可以来实现这个接口。


5.HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果需要额外拦截处理,可以添加拦截器设置)。


6.HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单数据的验证,数据类型的转换,将表单数据封装到JavaBean等等,这一系列的操作,都是由HandlerAdapter来完成,DispatcherServlet通过HandlerAdapter执行不同的Handler。


7.ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet。


8.ViewResolver:视图解析器,DispatcherServlet通过它将逻辑视图解析成物理视图,最终将渲染结果响应给客户端。


以上就是SpringMVC的核心组件。那么这些组件之间是如何进行交互的呢?


我们来看SpringMVC的实现流程:


1.客户端请求被DispatcherServlet(前端控制器)接收。


2.根据HandlerMapping映射到Handler。


3.生成Handler和HandlerInterceptor(如果有则生成)。


4.Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet。


5.DispatcherServlet通过HandlerAdapter调用Handler的方法做业务逻辑处理。


6.返回一个ModelAndView对象给DispatcherServlet。


7.DispatcherServlet将获取的ModelAndView对象传给ViewResolver视图解析器,将逻辑视图解析成物理视图View。


8.ViewResolver返回一个View给DispatcherServlet。


9.DispatcherServlet根据View进行视图渲染(将模型数据填充到视图中)。


10.DispatcherServlet将渲染后的视图响应给客户端。


深入底层,仿SpringMVC自己写框架


通过以上的分析,大致可以将SpringMVC流程理解如下:

首先需要一个前置控制器DispatcherServlet,作为整个流程的核心,由它去调用其他组件,共同完成业务。主要组件有两个:一是Controller,调用其业务方法Method,执行业务逻辑。二是ViewResolver视图解析器,将业务方法的返回值解析为物理视图+模型数据,返回客户端。


我们自己写框架就按照这个思路来。


初始化工作:

1.根据Spring IOC的思路,需要将参与业务的对象全部创建并保存,供流程调用。所以首先我们需要创建Controller对象,HTTP请求是通过注解找到对应的Controller对象,所以我们需要将所有的Controller与其注解建立关联,很显然,使用key-value结构的Map集合来保存最合适不过了,这样就模拟了IOC容器。


2.Controller的Method也是通过注解与HTTP请求映射的,同样的,我们需要将所有的Method与其注解建立关联,HTTP直接通过注解的值找到对应的Method,这里也用Map集合保存。


3.实例化视图解析器。


初始化工作完成,接下来处理HTTP请求,业务流程如下:

1.DispatcherServlet接收请求,通过映射从IOC容器中获取对应的Controller对象。

2.根据映射获取Controller对象对应的Method。

3.调用Method,获取返回值。

4.将返回值传给视图解析器,返回物理视图。

5.完成页面跳转。


思路捋清楚了,接下来开始写代码,我们需要创建以下类:

1.MyDispatcherServlet:模拟DispatcherServlet。

2.MyController:模拟Controller注解。

3.MyRequestMapping:模拟RequestMapping注解。

4.MyViewResolver:模拟ViewResolver视图解析器。


创建MyDispatcherServlet,init方法完成初始化:

1.将Controller与注解进行关联,保存到iocContainer中

哪些Controller是需要添加到iocContainer中的

必须同时满足两点:

1.springmvc.xml中配置扫描的类。

2.类定义处添加了注解。

注意这两点必须同时满足


代码思路:

(1)解析springmvc.xml。

(2)获取component-scan标签配置的包下的所有类。

(3)判断若这些类添加了@MyController注解,则创建实例对象,并且保存到iocContainer。

(4)@MyRequestMapping的值为键,Controller对象为值。


2.将Controller中的Method与注解进行关联,保存到handlerMapping中。

代码思路:

(1)遍历iocContainer中的Controller实例对象。

(2)遍历每一个Controller对象的Method。

(3)判断Method是否添加了@MyRequestMapping注解,若添加,则进行映射并保存。

(4)保存到handlerMapping中,@MyRequestMapping的值为键,Method为值。


3.实例化ViewResolver。

代码思路:

(1)解析springmvc.xml

(2)根据bean标签的class属性获取需要实例化的MyViewResolver。

(3)使用反射创建实例化对象,同时获取prefix和suffix属性,以及setter方法。

(4)使用反射调用setter方法给属性赋值,完成MyViewResolver的实例化。


doPost方法处理HTTP请求:

1.解析HTTP,分别得到Controller和Method对应的uri。

2.通过uri分别在iocContainer和handlerMapping中获取对应的Controller以及Method。

3.使用反射调用Method,执行业务方法,获取结果。

4.结果传给MyViewResolver进行解析,返回真正的物理视图(JSP页面)。

5.完成JSP的页面跳转。



深入底层,仿SpringMVC自己写框架


代码:


1.创建MyController注解,作用目标为类。


package com.southwind.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义Controller注解
* @author southwind
*
*/

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


2.创建MyRequestMapping注解,作用目标为类和方法。


package com.southwind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 自定义RequestMapping注解
* @author southwind
*
*/

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
   String value() default "";
}



3.创建MyDispatcherServlet,核心控制器,init完成初始化工作,doPost处理HTTP请求。


package com.southwind.servlet;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpRetryException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.southwind.annotation.MyController;
import com.southwind.annotation.MyRequestMapping;
import com.southwind.view.MyViewResolver;

/**
* DispatcherServlet
* @author southwind
*
*/

public class MyDispatcherServlet extends HttpServlet{

   //模拟IOC容器,保存Controller实例对象
   private Map<String,Object> iocContainer = new HashMap<String,Object>();
   //保存handler映射
   private Map<String,Method> handlerMapping = new HashMap<String,Method>();
   //自定视图解析器
   private MyViewResolver myViewResolver;

   @Override
   public void init(ServletConfig config) throws ServletException {
       // TODO Auto-generated method stub
       //扫描Controller,创建实例对象,并存入iocContainer
       scanController(config);
       //初始化handler映射
       initHandlerMapping();
       //加载视图解析器
       loadViewResolver(config);
   }

   /**
    * 扫描Controller
    * @param config
    */

   public void scanController(ServletConfig config){
       SAXReader reader = new SAXReader();
       try {
           //解析springmvc.xml
           String path = config.getServletContext().getRealPath("")+"\\WEB-INF\\classes\\"+config.getInitParameter("contextConfigLocation");  
           Document document = reader.read(path);
           Element root = document.getRootElement();
           Iterator iter = root.elementIterator();
           while(iter.hasNext()){
               Element ele = (Element) iter.next();
               if(ele.getName().equals("component-scan")){
                   String packageName = ele.attributeValue("base-package");
                   //获取base-package包下的所有类名
                   List<String> list = getClassNames(packageName);
                   for(String str:list){
                       Class clazz = Class.forName(str);
                       //判断是否有MyController注解
                       if(clazz.isAnnotationPresent(MyController.class)){
                           //获取Controller中MyRequestMapping注解的value
                           MyRequestMapping annotation = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
                           String value = annotation.value().substring(1);
                           //Controller实例对象存入iocContainer
                           iocContainer.put(value, clazz.newInstance());
                       }
                   }
               }
           }
       } catch (Exception e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       }
   }

   /**
    * 获取包下的所有类名
    * @param packageName
    * @return
    */

   public List<String> getClassNames(String packageName){
       List<String> classNameList = new ArrayList<String>();
       String packagePath = packageName.replace(".", "/");  
       ClassLoader loader = Thread.currentThread().getContextClassLoader();  
       URL url = loader.getResource(packagePath);  
       if(url != null){
           File file = new File(url.getPath());  
           File[] childFiles = file.listFiles();
           for(File childFile : childFiles){
               String className = packageName+"."+childFile.getName().replace(".class", "");
               classNameList.add(className);
           }
       }
       return classNameList;
   }

   /**
    * 初始化handler映射
    */

   public void initHandlerMapping(){
       for(String str:iocContainer.keySet()){
           Class clazz = iocContainer.get(str).getClass();
           Method[] methods = clazz.getMethods();
              for (Method method : methods) {
                //判断方式是否添加MyRequestMapping注解
                if(method.isAnnotationPresent(MyRequestMapping.class)){
                    //获取Method中MyRequestMapping注解的value
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String value = annotation.value().substring(1);
                    //method存入methodMapping
                    handlerMapping.put(value, method);
                }
            }
       }
   }

   /**
    * 加载自定义视图解析器
    * @param config
    */

   public void loadViewResolver(ServletConfig config){
       SAXReader reader = new SAXReader();
       try {
           //解析springmvc.xml
           String path = config.getServletContext().getRealPath("")+"\\WEB-INF\\classes\\"+config.getInitParameter("contextConfigLocation");  
           Document document = reader.read(path);
           Element root = document.getRootElement();
           Iterator iter = root.elementIterator();
           while(iter.hasNext()){
               Element ele = (Element) iter.next();
               if(ele.getName().equals("bean")){
                   String className = ele.attributeValue("class");
                   Class clazz = Class.forName(className);
                   Object obj = clazz.newInstance();
                   //获取setter方法
                   Method prefixMethod = clazz.getMethod("setPrefix", String.class);
                   Method suffixMethod = clazz.getMethod("setSuffix", String.class);
                   Iterator beanIter = ele.elementIterator();
                   //获取property值
                   Map<String,String> propertyMap = new HashMap<String,String>();
                   while(beanIter.hasNext()){
                       Element beanEle = (Element) beanIter.next();
                       String name = beanEle.attributeValue("name");
                       String value = beanEle.attributeValue("value");
                       propertyMap.put(name, value);
                   }
                   for(String str:propertyMap.keySet()){
                       //反射机制调用setter方法,完成赋值。
                       if(str.equals("prefix")){
                           prefixMethod.invoke(obj, propertyMap.get(str));
                       }
                       if(str.equals("suffix")){
                           suffixMethod.invoke(obj, propertyMap.get(str));
                       }
                   }
                   myViewResolver = (MyViewResolver) obj;
               }
           }
       } catch (Exception e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       }
   }

   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp)
           throws ServletException, IOException
{
       // TODO Auto-generated method stub
       this.doPost(req, resp);
   }

   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp)
           throws ServletException, IOException
{
       // TODO Auto-generated method stub
       //获取请求
       String handlerUri = req.getRequestURI().split("/")[2];
       //获取Controller实例
       Object obj = iocContainer.get(handlerUri);
       String methodUri = req.getRequestURI().split("/")[3];
       //获取业务方法
       Method method = handlerMapping.get(methodUri);
       try {
           //反射机制调用业务方法
           String value = (String) method.invoke(obj);
           //视图解析器将逻辑视图转换为物理视图
           String result = myViewResolver.jspMapping(value);
           //页面跳转
           req.getRequestDispatcher(result).forward(req, resp);
       } catch (Exception e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       }
   }
}


4.创建视图解析器MyViewResolver。


package com.southwind.view;

/**
* 自定义视图解析器
* @author southwind
*
*/

public class MyViewResolver {
   private String prefix;
   private String suffix;
   public String getPrefix() {
       return prefix;
   }
   public void setPrefix(String prefix) {
       this.prefix = prefix;
   }
   public String getSuffix() {
       return suffix;
   }
   public void setSuffix(String suffix) {
       this.suffix = suffix;
   }

   public String jspMapping(String value){
       return this.prefix+value+this.suffix;
   }
}


5.创建TestController,处理业务请求。


package com.southwind.controller;

import com.southwind.annotation.MyController;
import com.southwind.annotation.MyRequestMapping;

@MyController
@MyRequestMapping(value = "/testController")
public class TestController {

   @MyRequestMapping(value = "/test")
   public String test(){
       System.out.println("执行test相关业务");
       return "index";
   }

}


6.测试。


深入底层,仿SpringMVC自己写框架


深入底层,仿SpringMVC自己写框架


跳转index.jsp,同时控制台打印业务日志,访问成功。



源码:


github

https://github.com/southwind9801/SpringMVCImitate.git





专业 热爱 专注

致力于最高效的Java学习

Java大联盟



扫描下方二维码,加入Java大联盟



以上是关于深入底层,仿SpringMVC自己写框架的主要内容,如果未能解决你的问题,请参考以下文章

目前主流的java框架有哪些?

java 后台框架 支持APP接口调用 APP后台 手机后台框架java springmvc myb

自己动手模仿 springmvc 写一个 mvc框架

自己动手模仿 springmvc 写一个 mvc框架

仿京东开放平台框架,开发自己的开放平台(包含需求,服务端代码,SDK代码)

仿京东开放平台框架,开发自己的开放平台(包含需求,服务端代码,SDK代码)