手写SpringMVC框架
一、传统Servlet请求
Java之间的网络通信是使用的Socket,而B/S结构的项目,是浏览器和服务器之间交互的项目,由于浏览器不是我们写的,且浏览器只发送http请求,因此才会有了web容器(Tomcat、Weblogic等)帮我接收http请求,然后再将请求交给Servlet处理。
关于Servlet,其生命周期包括:初始化、服务、销毁三个阶段;Servlet的创建是由Web容器创建的,创建时机是第一次访问该Servlet程序的时候,或者是web容器启动的时候(需要在web.xml中配置启动项 load-on-startup=2)
对于一个http请求,包含 协议、域名/IP、端口号、项目名、接口URI几个内容组成(http://127.0.0.1:8080/lclspring/queryUser),其中协议是用来描述通信协议的,域名/IP + 端口号 是用来定位某一台机器中的指定进程的服务,然后通过URI在web.xml中找到对应的Servlet来进行处理。
web.xml分为两类:全局web.xml和自定义web.xml,所有的app项目都会使用全局web.xml,里面包含了DefaultServlet和JspServlet,其中DefaultServlet主要是用来做全路径匹配的(/),而JspServlet是用来对jsp访问做处理的(*.jsp)
那么这里引申出来一个问题,为什么在web.xml中不能设置 /* : 因为Servelet的路径匹配顺序是 完全路径匹配(/a/b/c) > 模糊匹配(/*) > 指定类型匹配(*.jsp) > 全路径匹配(/),那么如果我们在web.xml中配置了最高层级的模糊匹配(/*),那么jsp文件就会被我们自定义的Servlet拦截掉,而不会走JspServlet。
处于传统的Servlet代码编写,只需要两个步骤:1、web.xml配置Servlet;2、编写自定义Servlet类并继承HttpServlet
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <description></description> <display-name>MyServlet</display-name> <servlet-name>MyServlet</servlet-name> <servlet-class>com.lcl.galaxy.springmvc.frame.dispatcher.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/MyServlet</url-pattern> </servlet-mapping> </web-app>
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("hello lcl............."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } }
然后就可以使用在web.xml中配置的Servlet对应的路径进行请求了。
但是对于我们来说,每增加一个请求地址,就要写一个Servlet类,就太麻烦了,因此就会有Spring MVC
二、Spring MVC处理
(一)SpringMvc组件介绍及流程分析
Spring MVC主要分为六个组件,分别是前端控制器、处理器映射器、处理器、处理器适配器、视图解析器、视图。
其中,前端控制器、处理器和视图就是我们通常说的MVC(Model-View-Controller)
其中前端控制器就是C(Controller),用来做接口请求、响应结果和分发请求;
处理器就是M(Model),用来处理业务请求
视图就是V(View),用来将返回结果进行美化,并返回给浏览器
那么还有三个组件,分别是处理器映射器、处理器适配器和视图解析器,这三个组件主要是用来对不同的处理器进行适配和返回的结果进行处理的。
处理器映射器:在该映射器内维护了一个映射Map,key为请求路径,value为实际的处理器。同时提供了根据key获取处理的方法。
处理器适配器:由于处理器有很多,因此当前端控制器接收到一个请求时,其不知道应该调用哪一个处理器来进行处理,因此,前端控制器就不与处理器直接交互处理,而是调用处理器适配器进行处理,在处理器适配器中会调用真正的处理器进行处理。
视图解析器:在拿到处理结果后,可以对结果进行处理,例如加上前缀路径和后缀文件名等操作。
对于SpringMvc的处理流程如下:
1、接收到请求
2、根据请求从处理器映射器中获取对应的处理器
3、根据处理器获取对应的处理器适配器,并调用处理器适配器处理
4、处理器适配器调用对应的处理器进行处理
5、处理器执行完成返回结果给处理器适配器,处理器适配器再将结果返回给前端控制器
6、前端控制器调用视图解析器进行处理
7、前端控制器调用试图进行页面渲染
8、将结果进行返回
由于现在都是前后端分离项目,且前后端基本上都用Json来进行传输,因此本次不处理视图解析器和视图。
(二)手写SpringMvc框架
通过上述流程分析,可以发现,有六个组件,那么我们分别创建六个组件的对象
1、前端控制器
前端控制器要继承HttpServlet类,并且重写doGet、doPost等方法,那么,就先写一个抽象的Servelt类,让该类继承HttpServlet,然后doGet和doPost统一调用doDispatch方法。
package com.lcl.galaxy.springmvc.frame.dispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public abstract class MyAbstructServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doDispatch(req, resp); }
protected abstract void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException;
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doDispatch(req, resp); } }
然后编写我们的前端控制器,继承HttpServlet方法,在该方法中,需要根据请求获取处理器,然后根据处理器获取适配器,然后再调用适配器进行处理,那么在方法调用之前,就需要将相关的处理器和适配器进行加载完毕,这里就需要重写Servlet的init方法,该方法是在tomcat容器启动后进行加载的,因此可以在该方法中对处理器映射器、处理器进行加载。
在处理init方法时,主要分为两步,第一步就是加载Spring配置文件,第二不就是根据配置文件组装处处理器映射器集合和处理器适配器集合:
加载Spring配置文件:直接使用之前写的spring框架,加载springmvc.xml配置文件(这里需要特殊说明一点,由于springmvc配置文件的位置和名称不定,因此,程序中直接加载web.xml中配置contextConfigLocation对应的值,因此web.xml在配置前端控制器时,需要配置springmvc配置文件的位置和名称);
组装处理器映射器:这里也是使用之前写的Spring框架,加载所有HandlerMapping类(包含子类);这里会创建一个MyHandlerMapping的接口,所有的处理器映射器都继承自该接口,改内容如何创建,后续说明。
组装处理器适配器:这里也是使用之前写的Spring框架,加载所有HandlerAdapter类(包含子类)这里会创建一个MyHandlerAdapter的接口,所有的处理器适配器都继承自该接口,改内容如何创建,后续说明。
public class MyDispatcherServlet extends MyAbstructServlet { private final String SPRING_MVC_CONTEXT = "contextConfigLocation"; private MyDefaultListableBeanFactory beanFactory; private List<MyHandlerMapping> handlerMappingList = new ArrayList<>(); private List<MyHandlerAdapter> handlerAdapterList = new ArrayList<>(); /** * Servelet自身的生命周期方法;servlet被实例化后就会执行初始化方法 * * @param config */ @Override public void init(ServletConfig config){ loadContext(config); initStratery(); } private void loadContext(ServletConfig config){ String pathvalue = config.getInitParameter(SPRING_MVC_CONTEXT); //实用配置话获取spring mvc配置文件 MyResources resources = new MyClassPathResource(pathvalue); beanFactory = new MyDefaultListableBeanFactory(); MyXmlBeanDefinitionReader reader = new MyXmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions(resources); } private void initStratery(){ handlerMappingList = beanFactory.getBeansByType(MyHandlerMapping.class); handlerAdapterList = beanFactory.getBeansByType(MyHandlerAdapter.class); } @Override protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException { } }
2、web.xml配置
<servlet> <servlet-name>MyDispatcherServlet</servlet-name> <servlet-class>com.lcl.galaxy.springmvc.frame.dispatcher.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>springmvc.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MyDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
3、处理器映射器
定义一个统一的处理器映射器接口,同时提供根据请求获取处理器的方法
public interface MyHandlerMapping { Object getHandler(HttpServletRequest request); }
写一个实现类,让其继承MyHandlerMapping,实现映射器的获取handler方法,在该方法中,从handler的map中直接获取。
那么handler的map数据从哪来呢?这就要写一个实现类的初始化方法init,在init方法中对map进行初始化,使用beanFactory获取带有请求路径的BeanName,并将对应的Bean放入handlerMap中。
那么仍然有个问题,BeanFactory从哪来呢?在手写Spring源码时,已经介绍过,只要类实现了BeanFactoryAware接口,就可以直接实现其setFactory方法,因此就可以获得到beanFactory
public class MyBeanNameUrlHandlerMapping implements MyHandlerMapping, MyBeanFactoryAware { private MyDefaultListableBeanFactory beanFactory; private Map<String, Object> urlHanlderMap = new HashMap<>(); public void init(){ try { List<MyBeanDefinition> beanDefinitions = beanFactory.getBeanDefinitions(); for (MyBeanDefinition beanDefinition : beanDefinitions){ String beanName = beanDefinition.getBeanName(); if(beanName.startsWith("/")){ urlHanlderMap.put(beanName, beanFactory.getBean(beanName)); } } }catch (Exception e){ e.printStackTrace(); } } @Override public Object getHandler(HttpServletRequest request) { return urlHanlderMap.get(request.getRequestURI()); } @Override public void setBeanFactory(MyBeanFactory beanFactory) { this.beanFactory = (MyDefaultListableBeanFactory) beanFactory; } }
然后需要将该处理器映射器添加到springmvc.xml配置文件中,由于需要初始化handlerMap,因此需要配置初始化方法
<bean class="com.lcl.galaxy.springmvc.frame.handlermapping.MyBeanNameUrlHandlerMapping" initMethod="init"></bean>
4、处理器适配器
第1步中说到除了初始化处理器映射器之外,还需要初始化处理器适配器,那么和处理器映射器一样,需要先写一个处理器适配器接口,该接口提供处理方法和是否使用该适配器的判断方法。
public interface MyHandlerAdapter { void handleRequest(Object handler, HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException; boolean support(Object handler); }
然后定义实现类:
执行方法:将处理器强制转换为该适配器对应的处理器,然后调用处理器进行处理
是否要使用该适配器:判断处理器是否为该适配器所对应的处理器
public class MyHttpRequestHandlerAdapter implements MyHandlerAdapter { @Override public void handleRequest(Object handler, HttpServletRequest request, HttpServletResponse response) throws IOException { MyHttpRequestHandler httpRequestHandler = (MyHttpRequestHandler) handler; httpRequestHandler.handleRequest(request, response); } @Override public boolean support(Object handler) { return handler instanceof MyHttpRequestHandler; } }
然后将适配器配置在pringmvc.xml中
<bean class="com.lcl.galaxy.springmvc.frame.adapter.MyHttpRequestHandlerAdapter"></bean>
5、处理器
定义一个与适配器对应的handler接口,该接口只提供处理方法
public interface MyHttpRequestHandler { void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException; }
然后分别写两个实现类
public class SaveUserHandler implements MyHttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("SaveUserHandler"); } }
public class QueryUserHandler implements MyHttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("QueryUserHandler"); } }
最后将这两个实现类配置到springmvc.xml中
<bean name="/queryUser" class="com.lcl.galaxy.springmvc.frame.handler.QueryUserHandler"></bean> <bean name="/saveUser" class="com.lcl.galaxy.springmvc.frame.handler.SaveUserHandler"></bean>
6、执行流程
上面几步已经将相关的内容进行了配置,然后就是执行流程了(第一步中的doDispatch方法),在该方法中,就是我们springmvc的处理流程,(1)根据请求从加载过的handlermapping集合中获取对应的handler;(2)使用handler从适配器集合中获取对应的适配器;(3)调用适配器的执行方法(在适配器中会调用对应的handler的执行方法)
@Override protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException { //查找处理器 Object handler = getHandler(request); if(handler == null){ return; } //执行处理器方法 MyHandlerAdapter mha = getHandlerAdapter(handler); if(mha == null){ return; } //执行并响应结果 try { mha.handleRequest(handler, request, response); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } /** * 根据请求获取处理器 * @param request * @return */ private Object getHandler(HttpServletRequest request) { if(handlerMappingList != null && handlerMappingList.size()>0){ for (MyHandlerMapping handlerMapping : handlerMappingList){ Object handler = handlerMapping.getHandler(request); if(handler != null){ return handler; } } } return null; } /** * 根据处理器查找适配器 * @param handler * @return */ private MyHandlerAdapter getHandlerAdapter(Object handler) { if(handlerAdapterList != null && handlerAdapterList.size() >0){ for (MyHandlerAdapter handlerAdapter : handlerAdapterList){ if(handlerAdapter.support(handler)){ return handlerAdapter; } } } return null; }
三、注解方式Spring MVC处理
在第二步中,已经将SpringMvc的处理流程编写完毕,但是这仍不是我们平时使用注解的写法,并且一个类只能对应一个请求,同时该类还需要在springmvc.xml上进行配置,非常的繁琐。
1、自定义注解
对于注解的处理,首先我们分别仿照@Controller、@RequestMapping、@ResponseBody来创建三个自定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyController { String value() default ""; }
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyRequestMapping { String value() default ""; }
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface MyResponseBody { }
2、使用自定义注解
我们先来写处理器,然后再看应该如何进行加载适配器和映射器
首先我们按照平时写代码的方式来写一个Controller
@MyController @MyRequestMapping("/user") public class UserController implements Serializable { private static long serialUUid = 1L; @MyRequestMapping("/get") @MyResponseBody public Map<String, Object> getUser(Integer id, String name){ Map<String, Object> map = new HashMap<>(); map.put("id", id); map.put("name", name); return map; } @MyRequestMapping("/save") @MyResponseBody public String saveUser(){ return "OK"; } }
这种请求模式,我们平时代码都这么写,无需多说,主要是使用了我们第1步中自定义的注解
接下来,我们按照第二步中的写法一一梳理
3、前端控制器和web.xml
对于前端控制器和web.xml,可以服用第二步中的内容,无需新增
4、控制器
对于一个控制器,那么肯定对应一个请求,但是像第二步中的请求一样,实际上一个请求对应的是一个类中的一个方法,而非直接针对一个类,所以,我们的handler也应该是有两个属性:类和方法;
那我们就定义一个统一的handler类
@Data public class MyHandlerMethod { private Object controller; private Method method; public MyHandlerMethod(Object controller, Method method){ super(); this.controller = controller; this.method = method; } }
同时将Controller配置到springmvc.xml文件中,让spring进行加载,以便于后续初始化handlerMapping和handlerApater;(这一步可以使用AOP注解扫描来处理,由于我们使用的是自定义注解和自己手写的spring框架,因此就直接在配置文件中配置了)
<bean class="com.lcl.galaxy.springmvc.frame.controller.UserController"></bean>
5、处理器映射器
新创建一个针对注解的handlerMapping实现类,在该类中,同样实现根据请求获取handler方法和设置BeanFactory方法。
但是最重要的,还是需要去加载处理器映射器中的handler集合。
初始化handler集合主要分为以下几步:
(1)判断BeanDefinition是否使用了@MyController注解或@MyRequestMapping注解
(2)如果使用注解,则获取该类下所有的方法
(3)循环方法,判断方法上是否使用了@MyRequestMapping注解
(4)如果使用,则初始化一个HandlerMethod方法,对其封装,然后将HandlerMethod放入handler集合中,key是请求的uri,value是HandlerMethod
public class MyRequestMappingHandlerMapping implements MyHandlerMapping, MyBeanFactoryAware { private MyDefaultListableBeanFactory beanFactory; private Map<String, MyHandlerMethod> urlHanlderMap = new HashMap<>(); public void init(){ //获取并遍历所有的BeanDefinition List<MyBeanDefinition> beanDefinitions = beanFactory.getBeanDefinitions(); for (MyBeanDefinition beanDefinition : beanDefinitions){ String clazzName = beanDefinition.getClazzName(); Class<?> clazz = resolveClass(clazzName); //判断Beandefinition是否使用了MyController注解 if(isHandler(clazz)){ //如果使用了MyController注解,则获取它的所有方法 Method[] declaredMethods = clazz.getDeclaredMethods(); for (Method method : declaredMethods) { MyRequestMapping classAnnotation = clazz.getAnnotation(MyRequestMapping.class); String classUri = classAnnotation.value(); //判断方法是否使用了MyRequestMapping注解 if(method.isAnnotationPresent(MyRequestMapping.class)){ //获取MyRequestMapping注解中配置的值 MyRequestMapping methodAnnotation = method.getAnnotation(MyRequestMapping.class); String methodUri = methodAnnotation.value(); //封装MyHandlerMethod对象(controller + method) MyHandlerMethod handlerMethod = new MyHandlerMethod(beanFactory.getBean(beanDefinition.getBeanName()), method); urlHanlderMap.put(classUri + methodUri, handlerMethod); } } } } } private boolean isHandler(Class<?> clazz) { return clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyRequestMapping.class); } private Class<?> resolveClass(String clazzName) { try { return Class.forName(clazzName); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } @Override public Object getHandler(HttpServletRequest request) { return urlHanlderMap.get(request.getRequestURI()); } @Override public void setBeanFactory(MyBeanFactory beanFactory) { this.beanFactory = (MyDefaultListableBeanFactory) beanFactory; } }
将该映射器配置到springmvc.xml中
<bean class="com.lcl.galaxy.springmvc.frame.handlermapping.MyRequestMappingHandlerMapping" initMethod="init"></bean>
6、处理器适配器
和第二步中创建适配器一样创建一个对于注解处理的适配器MyRequestMappingHandlerAdapter,同样提供两个方法:是否要使用该适配器、适配器调用处理器进行处理。
是否使用该适配器方法:如果处理器为MyHandlerMethod,则要使用该适配器
调用适配器方法:由于现在传过来的处理器为MyHandlerMethod,该对象并非真正要调用的方法,但是该类中封装了要执行的类和方法,那么就可以使用反射进行调用;总体处理流程分为以下几步:
(1)获取controller和method
(2)根据请求的参数和方法的形参来获取参数
(3)利用反射调用方法执行
(4)对返回结果进行处理
public class MyRequestMappingHandlerAdapter implements MyHandlerAdapter { @Override public void handleRequest(Object handler, HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException { MyHandlerMethod handlerMethod = (MyHandlerMethod) handler; Object controller = handlerMethod.getController(); Method method = handlerMethod.getMethod(); Object[] args = getParamters(request, method); Object returnValue = null; if(args == null){ returnValue = method.invoke(controller); }else{ returnValue = method.invoke(controller, args); } handlerReturnValue(returnValue, method, response); } private void handlerReturnValue(Object returnValue, Method method, HttpServletResponse response) throws IOException { } private Object[] getParamters(HttpServletRequest request, Method method) { } private void handlerParamterType(String[] stringValues, Class<?> type, List<Object> params) { } @Override public boolean support(Object handler) { return handler instanceof MyHandlerMethod; } }
可以看到以上为实现了support方法和主流程的类,对于抽出来的获取参数和对结果进行处理方法如下:
获取参数:先从入参请求中获取对应的请求map集合,然后获取方法的形参,循环所有形参并获取形参的名称,根据名称从request参数map中获取对应的String值,然后将值转换为形参对应的类型,然后放入list集合中,最终返回一个请求参数的数组。
private Object[] getParamters(HttpServletRequest request, Method method) { List<Object> params = new ArrayList(); //从对象中获取KV请求 Map<String, String[]> parameterMap = request.getParameterMap(); if(parameterMap != null && parameterMap.size() > 0){ //获取方法的形参集合 Parameter[] parameters = method.getParameters(); //遍历所有形参集合 for (Parameter parameter : parameters){ //获取形参名称和类型(该步需要特殊处理) String name = parameter.getName(); //获取参数名称去参数集合中获取对应的值 String[] stringValues = parameterMap.get(name); Class<?> type = parameter.getType(); //参数类型转换 handlerParamterType(stringValues, type, params); } return params.toArray(); } return null; } private void handlerParamterType(String[] stringValues, Class<?> type, List<Object> params) { if(type == String.class){ params.add(String.valueOf(stringValues[0])); }else if(type == Integer.class){ params.add(Integer.parseInt(stringValues[0])); } }
这里需要特殊说明一下,使用parameter.getName()获取参数名称时,需要做特殊的处理,如果不做特殊处理,获取到的名称则是arg0、arg1这样的跟位置相关的名称,而非真正的名称。
特殊处理:pom中的maven插件,需要配置参数compilerArgs
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <!--通过反射获取参数时,需要设置以下参数才有意义,否则返回的就是arg0,arg1这样的参数名称--> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin>
对结果进行处理:对结果处理主要是判断方法上是否使用了自定义的@MyResponseBody注解,如果使用,则对设置响应结果为json等等处理
private void handlerReturnValue(Object returnValue, Method method, HttpServletResponse response) throws IOException { if(method.isAnnotationPresent(MyResponseBody.class)){ if(returnValue instanceof String){ response.setContentType("text/plain;charset=utf8"); response.getWriter().write(String.valueOf(returnValue)); }else { response.setContentType("application/json;charset=utf8"); response.getWriter().write(JSON.toJSONString(returnValue)); } }else { } }
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~