8. Spring MVC 流程
8.1 Spring MVC 介绍
Spring MVC 是目前主流的MVC框架之一。两个核心点:
- 处理器映射:选择使用哪个控制器来处理请求
- 视图解析器:选择结果应该如何渲染
运行原理可以用下图表示:
- 首先用户发送请求,DispatcherServlet实现了Servlet接口,整个请求处理流:HttpServlet.service
-> FrameworkServlet.doGet
-> FrameworkServlet.processRequest
-> DispatcherServlet.doService
-> DispatcherServlet.doDispatch。
-> doDispatch(HttpServletRequest request, HttpServletResponse response)方法
即为整个spring mvc的处理流程。
获取url请求对应的处理方法,遍历handlerMappings列表,获取对象HandlerExecutionChain(包含一个处理器 handler 如HandlerMethod 对象、多个 HandlerInterceptor 拦截器对象)。此处的handlerMappings列表为上下文中所有HandlerMapping接口的实现类(如图中列举了4个),遍历handlerMappings列表,针对每个handlerMapping试图获取HandlerExecutionChain,一旦成功(不为null),即返回。
取对应的 HandlerAdapter,HandlerAdapter 将会把2中的handler包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器。DispatcherServlet中的HandlerAdapter列表如图中所列的3种,依次遍历,调用HanderAdapter.supports判断是否支持。
调用Controller的具体方法处理请求,并返回一个 ModelAndView。HandlerAdapter会为每一个请求生成一个ServletInvocableHandlerMethod实例,核心方法invokeAndHandle,包括输入参数的处理和返回数据的解析。
视图解析,遍历DispatcherServlet的ViewResolver列表,获取对应的View对象,入口方法DispatcherServlet.processDispatchResult
渲染,调用5中获取的View的render方法,完成对Model数据的渲染。此处的 Model 实际是一个 Map 数据结构。
DispatcherServlet 将6中渲染后的数据返回响应给用户,到此一个流程结束。
从代码角度讲,可以分为如下几步:
其中灰色标识主流程,绿色为DispathcerServlet.doDispatch方法中的流程,红色为HandlerAdapter.handler方法中的流程
参考:
8.2 Servlet
在介绍如何手动实现Spring MVC之前,首先介绍下Servlet的相关概念,因为后续的实现Spring MVC也是基于DispatcherServle实现的。
8.2.1 Servlet的生命周期
Servlet的生命周期分为4个阶段:实例化–>初始化–>执行处理–>销毁。
(1)实例化——new:服务器第一次被访问时,加载一个Servlet容器,而且只会被加载一次。
(2)初始化——init:创建完Servlet容器后,会调用仅执行一次的init()初始化方法,用于初始化Servlet对象,无论多少台客户端在服务器运行期间访问都不会再执行init()方法。
(3)执行处理——service()方法:HttpServlet的抽象类提供了doGet()、doPost()……等方法。对应了request请求的发送方法,与之相匹配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(405, msg); } else { resp.sendError(400, msg); }
} protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); if (protocol.endsWith("1.1")) { resp.sendError(405, msg); } else { resp.sendError(400, msg); }
}
|
(4)销毁——destroy:在服务器关闭或重启时,Servlet会调用destroy方法来销毁,将Servlet容器标记为垃圾文件,让GC做回收处理。我们编写的Servlet是调用了GenericServlet抽象类的destroy方法:
8.2.2 Servlet的工作原理
1、首先简单解释一下Servlet接收和响应客户请求的过程:
客户发送一个请求,Servlet是调用service()方法对请求进行响应的,通过源代码可见,service()方法中对请求的方式进行了匹配。选择调用doGet,doPost等这些方法,然后再进入对应的方法中调用逻辑层的方法,实现对客户的响应。在Servlet接口和GenericServlet中是没有doGet()、doPost()等等这些方法的,HttpServlet中定义了这些方法,但是都是返回error信息,所以,我们每次定义一个Servlet的时候,都必须实现doGet或doPost等这些方法。
2、每一个自定义的Servlet都必须实现Servlet的接口,Servlet接口中定义了五个方法,其中比较重要的三个方法涉及到Servlet的生命周期,分别是上文提到的init(),service(),destroy()方法。GenericServlet是一个通用的,不特定于任何协议的Servlet,它实现了Servlet接口。而HttpServlet继承于GenericServlet,因此HttpServlet也实现了Servlet接口。所以我们定义Servlet的时候只需要继承HttpServlet即可。
3、Servlet是单例模式,线程是不安全的,因此在service()方法中尽量不要操作全局变量。但实际上,可以通过使用session和application来代替全局变量,只是会加大服务器负载。
8.2.3 如何判断是get请求还是post请求?
8.2.4 过滤器Filter原理
生命周期:程序启动调用Filter的init()方法(永远只调用一次),程序停止调用Filter的destroy()方法(永远只调用一次),doFilter()方法每次的访问请求如果符合拦截条件都会调用(程序第一次运行,会在servlet调用init()方法以后调用,不管第几次,都在调用doGet(),doPost()方法之前)。
工作原理:在Servlet作为过滤器使用时,它可以对客户的请求进行处理。处理完成后,它会交给下一个过滤器处理,这样,客户的请求在过滤链里逐个处理,直到请求发送到目标为止。
例如,某网站里有提交“修改的注册信息”的网页,当用户填写完修改信息并提交后,服务器在进行处理时需要做两项工作:判断客户端的会话是否有效;对提交的数据进行统一编码。这两项工作可以在由两个过滤器组成的过滤链里进行处理。当过滤器处理成功后,把提交的数据发送到最终目标;如果过滤器处理不成功,将把视图派发到指定的错误页面。
过滤器与拦截器的不同之处:
总结: 两者的本质区别:拦截器是基于java的反射机制的,而过滤器是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,他都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。
8.3 手写Spring MVC
这部分将基于HttpServlet实现Spring MVC的整个流程,
- 首先是在XML中定义整个程序的入口,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <!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>DispatcherServlet</servlet-name> <display-name>DispatcherServlet</display-name> <description></description> <servlet-class>com.enjoy.james.servlet.DispatcherServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
|
- 接着实现核心流程类
DispatcherServlet
,用于初始化IOC容器,同时来处理request请求。
主要的流程分为如下:
scanPackage
函数,扫描类路径下的所有类,得到所有的类名。
instance
函数,将上一步得到的所有类进行实例化,然后将bean实例的名称和实例化的对象保存。
ioc
函数主要完成依赖注入,需要把service实例注入到controller类中。
HandlerMapping
函数把controller类中的URI和对应的method保存起来,后续进行request处理时,需要找到对应method。
下面是各个函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
|
public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L;
List<String> classNames = new ArrayList<String>();
Map<String, Object> beans = new HashMap<String, Object>();
Map<String, Object> handlerMap = new HashMap<String, Object>();
Properties prop = null;
private static String HANDLERADAPTER = "jamesHandlerAdapter";
public DispatcherServlet() { }
public void init(ServletConfig config) throws ServletException { scanPackage("com.enjoy");
for (String classname : classNames) { System.out.println(classname); }
instance(); for (Map.Entry<String, Object> entry : beans.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); }
ioc();
HandlerMapping(); for (Map.Entry<String, Object> entry : handlerMap.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); }
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uri = request.getRequestURI();
String context = request.getContextPath(); String path = uri.replace(context, ""); Method method = (Method) handlerMap.get(path); JamesController instance = (JamesController) beans.get("/" + path.split("/")[1]); HandlerAdapterService ha = (HandlerAdapterService) beans.get(HANDLERADAPTER);
Object[] args = ha.hand(request, response, method, beans);
try { method.invoke(instance, args); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }
} private void HandlerMapping() { if (beans.entrySet().size() <= 0) { System.out.println("没有类的实例化!"); return; }
for (Map.Entry<String, Object> entry : beans.entrySet()) { Object instance = entry.getValue();
Class<?> clazz = instance.getClass(); if (clazz.isAnnotationPresent(EnjoyController.class)) { EnjoyRequestMapping requestMapping = (EnjoyRequestMapping) clazz .getAnnotation(EnjoyRequestMapping.class); String classPath = requestMapping.value(); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(EnjoyRequestMapping.class)) { EnjoyRequestMapping methodrm = (EnjoyRequestMapping) method .getAnnotation(EnjoyRequestMapping.class); String methodPath = methodrm.value(); handlerMap.put(classPath + methodPath, method); } else { continue; } } } } }
private void ioc() {
if (beans.entrySet().size() <= 0) { System.out.println("没有类的实例化!"); return; } for (Map.Entry<String, Object> entry : beans.entrySet()) { Object instance = entry.getValue();
Class<?> clazz = instance.getClass(); if (clazz.isAnnotationPresent(EnjoyController.class)) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(EnjoyQualifier.class)) { EnjoyQualifier qualifier = (EnjoyQualifier) field.getAnnotation(EnjoyQualifier.class); String value = qualifier.value();
field.setAccessible(true); try { field.set(instance, beans.get(value)); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } else { continue; } } } else { continue; } } }
private void instance() { if (classNames.size() <= 0) { System.out.println("包扫描失败!"); return; } for (String className : classNames) { String cn = className.replace(".class", "");
try { Class<?> clazz = Class.forName(cn); if (clazz.isAnnotationPresent(EnjoyController.class)) { EnjoyController controller = (EnjoyController) clazz.getAnnotation(EnjoyController.class); Object instance = clazz.newInstance(); EnjoyRequestMapping requestMapping = (EnjoyRequestMapping) clazz .getAnnotation(EnjoyRequestMapping.class); String rmvalue = requestMapping.value(); beans.put(rmvalue, instance); } else if (clazz.isAnnotationPresent(EnjoyService.class)) { EnjoyService service = (EnjoyService) clazz.getAnnotation(EnjoyService.class); Object instance = clazz.newInstance(); beans.put(service.value(), instance); } else { continue; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
private void scanPackage(String basePackage) { URL url = this.getClass().getClassLoader().getResource("/" + replaceTo(basePackage));
String fileStr = url.getFile();
File file = new File(fileStr); String[] filesStr = file.list();
for (String path : filesStr) { File filePath = new File(fileStr + path);
if (filePath.isDirectory()) { scanPackage(basePackage + "." + path); } else { classNames.add(basePackage + "." + filePath.getName()); } } }
private String replaceTo(String basePackage) { return basePackage.replaceAll("\\.", "/"); }
}
|
- 在
doPost
函数中,完成了对request的主要处理,这里的主要流程首先是拿到处理request的方法,然后从request中将arguments全部取出来,最后利用反射进行调用方法。
以下是整个项目的结构,
- annotation部分定义了各种注解,类似的实现如下:
1 2 3 4 5 6
| @Target({java.lang.annotation.ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface EnjoyController { String value() default ""; }
|
- ArgumentResolver部分实现了对request参数的解析,这里使用了策略模式。
1 2 3 4 5 6 7 8 9 10
| public interface ArgumentResolver { public boolean support(Class<?> type, int paramIndex, Method method); public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type, int paramIndex,//参数索引下坐标,有很多注解,你得知道是哪个参数的注解,每个参数的索引顺序不一样 Method method); }
|
下面是不同的实现类,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @EnjoyService("httpServletRequestArgumentResolver") public class HttpServletRequestArgumentResolver implements ArgumentResolver { public boolean support(Class<?> type, int paramIndex, Method method) { return ServletRequest.class.isAssignableFrom(type); } public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type, int paramIndex, Method method) { return request; } }
@EnjoyService("httpServletResponseArgumentResolver") public class HttpServletResponseArgumentResolver implements ArgumentResolver { public boolean support(Class<?> type, int paramIndex, Method method) { return ServletResponse.class.isAssignableFrom(type); } public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type, int paramIndex, Method method) { return response; } }
|
下面这是关键的处理参数的实现类,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @EnjoyService("requestParamArgumentResolver")
public class RequestParamArgumentResolver implements ArgumentResolver { public boolean support(Class<?> type, int paramIndex, Method method) { Annotation[][] an = method.getParameterAnnotations(); Annotation[] paramAns = an[paramIndex]; for (Annotation paramAn : paramAns) { if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass())) { return true; } } return false; } public Object argumentResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type, int paramIndex, Method method) { Annotation[][] an = method.getParameterAnnotations(); Annotation[] paramAns = an[paramIndex]; for (Annotation paramAn : paramAns) { if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass())) { EnjoyRequestParam rp = (EnjoyRequestParam)paramAn; String value = rp.value(); return request.getParameter(value); } } return null; } }
|
- Controller类是整个request的入口,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| package com.enjoy.james.controller;
import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;
import com.enjoy.james.annotation.EnjoyController; import com.enjoy.james.annotation.EnjoyQualifier; import com.enjoy.james.annotation.EnjoyRequestHeader; import com.enjoy.james.annotation.EnjoyRequestMapping; import com.enjoy.james.annotation.EnjoyRequestParam; import com.enjoy.james.service.JamesService;
@EnjoyController @EnjoyRequestMapping("/james") public class JamesController { @EnjoyQualifier("JamesServiceImpl") private JamesService jamesService; @EnjoyRequestMapping("/query") public void query(HttpServletRequest request, HttpServletResponse response, @EnjoyRequestParam("name") String name, @EnjoyRequestParam("age") String age) { try { PrintWriter pw = response.getWriter(); String result = jamesService.query(name,age); pw.write(result); } catch (IOException e) { e.printStackTrace(); } } @EnjoyRequestMapping("/insert") public void insert(HttpServletRequest request, HttpServletResponse response) { try { PrintWriter pw = response.getWriter(); String result = jamesService.insert("0000"); pw.write(result); } catch (IOException e) { e.printStackTrace(); } } @EnjoyRequestMapping("/update") public void update(HttpServletRequest request, HttpServletResponse response, String param) { try { PrintWriter pw = response.getWriter(); String result = jamesService.update(param); pw.write(result); } catch (IOException e) { e.printStackTrace(); } } }
|
- JamesHandlerAdapter完成了从request中提取参数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| @EnjoyService("jamesHandlerAdapter") public class JamesHandlerAdapter implements HandlerAdapterService { public Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method, Map<String, Object> beans) { Class<?>[] paramClazzs = method.getParameterTypes(); Object[] args = new Object[paramClazzs.length]; Map<String, Object> argumentResolvers = getBeansOfType(beans, ArgumentResolver.class); int paramIndex = 0; int i = 0; for (Class<?> paramClazz : paramClazzs) { for (Map.Entry<String, Object> entry : argumentResolvers.entrySet()) { ArgumentResolver ar = (ArgumentResolver)entry.getValue(); if (ar.support(paramClazz, paramIndex, method)) { args[i++] = ar.argumentResolver(request, response, paramClazz, paramIndex, method); } } paramIndex++; } return args; } private Map<String, Object> getBeansOfType(Map<String, Object> beans,//所有bean Class<?> intfType) { Map<String, Object> resultBeans = new HashMap<String, Object>(); for (Map.Entry<String, Object> entry : beans.entrySet()) { Class<?>[] intfs = entry.getValue().getClass().getInterfaces(); if (intfs != null && intfs.length > 0) { for (Class<?> intf : intfs) { if (intf.isAssignableFrom(intfType)) { resultBeans.put(entry.getKey(), entry.getValue()); } } } } return resultBeans; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @EnjoyService("JamesServiceImpl") public class JamesServiceImpl implements JamesService { public String query(String name, String age) { return "{name="+name+",age="+age+"}"; } public String insert(String param) { return "insert successful....."; } public String update(String param) { return "update successful....."; } }
|
关于DispatcherServlet可以参考:
参考:
本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处
搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程。