一、SpringMVC发展史
2004年 Spring Framework 1.0 final 正式问世,当时只包含一个完整的项目,他把所有的功能都集中在一个项目中,其中包含了核心的 IOC、AOP,同时也包含了其他的诸多功能,例如:JDBC、Mail、ORM、事务、定时任务等。Spring团队超前的眼光,在第一个版本的时候已经支持了很多第三方的框架,例如:Hibernate、ibatis、模板引擎等,为其后来快速发展奠定了基础。
Spring 2.x增加对注解的支持,支持了基于注解的配置。
Spring 3.x支持了基于类的配置。
Spring 4.x全面支持jdk1.8,并引入RestController注解,直到今天依然是接口编程的首选。
现在最近GA版本Spring 5.2.1,Spring正在稳步向前,越走越稳。
二、总体架构

引入Intercepter(拦截器),类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理,本质上也会AOP,把符合横切关注点的所有功能都可以放入拦截器实现,Intercepter面向的是页面处理Handler(Controller),允许开发人员自定义某一请求路径上的处理程序执行链。
引入HandlerMapping(路由器),定义了Spring MVC的路由机制,把请求地址映射到对应的Controller和Action。
引入HandlerAdapter(适配器),这也是核心类,调用handler方法,处理请求数据,封装返回数据。
引入ViewResolver(视图解析器),把一个逻辑上的视图名称解析为一个真正的视图,SpringMVC中用于把View对象呈现给客户端的是View对象本身,而ViewResolver是把逻辑视图名称解析为对象的View对象。
三、分类概述
1、Intercepter
a) 接口源码介绍
1 public interface HandlerInterceptor {
2
3 /**
4 * 这个方法在业务处理器处理请求之前被调用,SpringMVC 中的Interceptor 是链
5 * 式的调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor
6 * 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是
7 * Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化
8 * 操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请
9 * 求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为
10 * false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返
11 * 回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是
12 * 最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
13 */
14 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
15 throws Exception {
16
17 return true;
18 }
19
20 /**
21 * 这个方法在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是
22 * 它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个
23 * 方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方
24 * 法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的
25 * postHandle 方法反而会后执行。
26 */
27 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
28 @Nullable ModelAndView modelAndView) throws Exception {
29 }
30
31 /**
32 * 该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才
33 * 会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet
34 * 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。
35 */
36 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
37 @Nullable Exception ex) throws Exception {
38 }
39
40 }
b) 示例程序
1 package com.pine.property.manage.service.common;
2
3 import java.util.List;
4
5 import javax.servlet.http.HttpServletRequest;
6 import javax.servlet.http.HttpServletResponse;
7
8 import org.springframework.beans.factory.annotation.Autowired;
9 import org.springframework.web.servlet.HandlerInterceptor;
10 import org.springframework.web.servlet.ModelAndView;
11
12 import com.baiyyy.core.common.AuthConstant;
13 import com.baiyyy.core.common.BizException;
14 import com.baiyyy.core.common.StatusCode;
15 import com.baiyyy.core.service.IBaseService;
16 import com.baiyyy.core.util.CookieUtil;
17 import com.baiyyy.core.util.JedisUtil;
18 import com.baiyyy.core.util.StringUtil;
19 import com.pine.property.manage.entity.Account;
20 import com.pine.property.manage.entity.Menu;
21 import com.pine.property.manage.service.auth.message.FunctionData;
22 /**
23 * 1、每次请求延长登录缓存时间
24 * 2、验证url访问权限
25 * @author pinenut
26 * @date 2018年2月22日
27 */
28 @Component
29 public class AuthIntercepter implements HandlerInterceptor{
30
31 @Autowired
32 public HttpServletRequest request;
33
34 private IBaseService<List<FunctionData>, Account> userAuthService;
35
36 public boolean preHandle(HttpServletRequest httpRequest,
37 HttpServletResponse response, Object handler) throws Exception {
38
39 String currentUrl = httpRequest.getServletPath();
40
41 //1、校验url权限
42 this.authUrl(currentUrl);
43 //2、更新用户cookie时间
44 String cookieId = CookieUtil.getCookie(httpRequest.getCookies(), AuthConstant.SESSION_ID);
45 JedisUtil.getInstance().STRINGS.setEx(cookieId, CommonConstant.USER_LOGIN_CONFIG_TIME_OUT, JedisUtil.getInstance().STRINGS.get(cookieId));
46
47
48 return true;
49
50 }
51
52 /**
53 * 校验url访问权限
54 * @author liuqingsong
55 * @date 2018年2月22日
56 */
57 private void authUrl(String currentUrl){
58 List<FunctionData> functionDataList = this.userAuthService.process(null);
59 boolean authed = false;
60 for(FunctionData item : functionDataList){
61 if(item.getMenuList() == null || item.getMenuList().size() == 0){
62 //只有一级菜单
63 if(currentUrl.toLowerCase().contains(item.getFunctionEntry().toLowerCase())){
64 authed = true;
65 break;
66 }
67 }
68 else{
69 //多级菜单
70 for(Menu menu : item.getMenuList()){
71 if(currentUrl.toLowerCase().contains(menu.getMenuUrl().toLowerCase())){
72 authed = true;
73 break;
74 }
75 }
76 if(authed){
77 break;
78 }
79 }
80 }
81
82 if(!authed){
83 throw new BizException(StatusCode.FAILURE_LOGIC, "用户无此功能权限!");
84 }
85 }
86
87
88 @Override
89 public void postHandle(HttpServletRequest request,
90 HttpServletResponse response, Object handler,
91 ModelAndView modelAndView) throws Exception {
92 // TODO Auto-generated method stub
93
94 }
95
96 @Override
97 public void afterCompletion(HttpServletRequest request,
98 HttpServletResponse response, Object handler, Exception ex)
99 throws Exception {
100 // TODO Auto-generated method stub
101
102 }
103
104 public IBaseService<List<FunctionData>, Account> getUserAuthService() {
105 return userAuthService;
106 }
107
108 public void setUserAuthService(
109 IBaseService<List<FunctionData>, Account> userAuthService) {
110 this.userAuthService = userAuthService;
111 }
112
113
114
115 }
c) 多拦截器执行顺序

2、HandlerMapping
a) springmvc默认实现HandlerMapping

以下面UserController为例
package com.baiyyy.basic.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/*
* 通用页面
* @author Administrator
*
*/
@RestController
@RequestMapping(value = "/user")
public class UserController {
* 登陆页面
* @return
*/
@RequestMapping(value = "/loginFrame", method = RequestMethod.GET)
public ModelAndView loginFrame(){
ModelAndView result = new ModelAndView("login");
return result;
}
}
| 实现类 | 继承父类 | 说明 | 使用 |
| ControllerClassNameHandlerMapping | AbstractUrlHandlerMapping | 根据类名访问 Controller |
访问地址: http://ip:port/项目名/user;注:类的首字母要小写 |
| ControllerBeanNameHandlerMapping | AbstractUrlHandlerMapping | 根据 Bean 名访问 Controller |
访问地址: http://ip:port/项目名/userController; |
| BeanNameUrlHandlerMapping | AbstractUrlHandlerMapping | 利用 BeanName 来作为 URL 使用 |
<bean id="userController" name="/users" class="com.qunar.web.controller.UserController"></bean> 访问地址: http://ip:port/项目名/users;注:bean name属性必须要以“/”开头 |
| SimpleUrlHandlerMapping | AbstractUrlHandlerMapping | 可以将 URL 与处理器的定义分离,还可以对 URL 进行统一的映射管理 |
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/userlist.htm">userController</prop>
</props>
</property>
</bean>
访问地址: http://ip:port/项目名/userlist.htm; |
| RequestMappingHandlerMapping | AbstractHandlerMethodMapping | @RequestMapping注解定义url |
访问地址: http://ip:port/项目名/user/loginFrame; |
b)自定义handlerMapping
package com.baiyyy.core.common;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
/**
* 定义restful接口handlermapping路由机制
* 默认以Controller/Action作为url,访问地址不区分大小写
* @author pinenut
* @date 2016-06-23
*/
public class RestfulHandlerMethodMapping extends AbstractHandlerMapping implements InitializingBean {
private final Map<String, HandlerMethod> urlMap = new LinkedHashMap<String, HandlerMethod>();
@Override
public void afterPropertiesSet() throws Exception {
this.initHandlerMethods();
}
@Override
protected Object getHandlerInternal(HttpServletRequest request)
throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request).toLowerCase();
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
HandlerMethod handlerMethod = this.urlMap.get(lookupPath);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
private void initHandlerMethods(){
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
Map<String, Object> controllerBeans = this.getApplicationContext().getBeansWithAnnotation(RestController.class);
for(Map.Entry<String, Object> controller : controllerBeans.entrySet()){
Object handler = controller.getValue();
Class<?> clazz = handler.getClass();
Method[] methodList = clazz.getDeclaredMethods();
for(Method method : methodList){
HandlerMethod handlerMethod = new HandlerMethod(handler, method);
String url = ("/" + clazz.getSimpleName().replaceAll("Controller", "") + "/" + method.getName()).toLowerCase();
logger.debug(url);
this.urlMap.put(url, handlerMethod);
}
}
}
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName,
getApplicationContext().getAutowireCapableBeanFactory(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
}
return handlerMethod;
}
}
3、HandlerAdapter
a) springmvc默认实现的HandlerAdapter 如下

| 实现类 | 说明 | 使用场景 |
| RequestMappingHandlerAdapter | 可以执行 HadnlerMethod 类型的 Handler | 主要是适配注解类处理器 |
| HttpRequestHandlerAdapter | 可以执行 HttpRequestHandler 类型的 Handler | 主要是适配静态资源处理器 |
| SimpleControllerHandlerAdapte | 可以执行 Controller 类型的 Handler | 适配实现了Controller接口或Controller接口子类的处理器 |
4、ViewResolver

a) InternalResourceViewResolver
<!-- 视图渲染 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/view/"></property>
<!-- 视图后缀 -->
<property name="suffix" value=".jsp"></property>
<property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
<property name="order" value="1"/>
</bean>
InternalResourceViewResolver也是使用的最广泛的一个视图解析器。我们可以把InternalResourceViewResolver解释为内部资源视图解析器,这就是InternalResourceViewResolver的一个特性。InternalResourceViewResolver会把返回的视图名称都解析为InternalResourceView对象,InternalResourceView会把Controller处理器方法返回的模型属性都存放到对应的request属性中,然后通过RequestDispatcher在服务器端把请求forword重定向到目标URL。比如在InternalResourceViewResolver中定义了prefix=/WEB-INF/,suffix=.jsp,然后请求的Controller处理器方法返回的视图名称为test,那么这个时候InternalResourceViewResolver就会把test解析为一个InternalResourceView对象,先把返回的模型属性都存放到对应的HttpServletRequest属性中,然后利用RequestDispatcher在服务器端把请求forword到/WEB-INF/test.jsp。这就是InternalResourceViewResolver一个非常重要的特性,我们都知道存放在/WEB-INF/下面的内容是不能直接通过request请求的方式请求到的,为了安全性考虑,我们通常会把jsp文件放在WEB-INF目录下,而InternalResourceView在服务器端跳转的方式可以很好的解决这个问题。下面是一个InternalResourceViewResolver的定义,根据该定义当返回的逻辑视图名称是test的时候,InternalResourceViewResolver会给它加上定义好的前缀和后缀,组成“/WEB-INF/test.jsp”的形式,然后把它当做一个InternalResourceView的url新建一个InternalResourceView对象返回。
b) BeanNameViewResolver
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<bean id="test" class="org.springframework.web.servlet.view.InternalResourceView">
<property name="url" value="/index.jsp"/>
</bean>
@RequestMapping("/test")
public String testXmlViewResolver() {
return "test";
}
通过把返回的逻辑视图名称去匹配定义好的视图bean对象,BeanNameViewResolver要求视图bean对象都定义在Spring的application context中。
四、总体流程

