一、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 | |
ControllerBeanNameHandlerMapping | AbstractUrlHandlerMapping | 根据 Bean 名访问 Controller | |
BeanNameUrlHandlerMapping | AbstractUrlHandlerMapping | 利用 BeanName 来作为 URL 使用 |
|
SimpleUrlHandlerMapping | AbstractUrlHandlerMapping | 可以将 URL 与处理器的定义分离,还可以对 URL 进行统一的映射管理 |
|
RequestMappingHandlerMapping | AbstractHandlerMethodMapping | @RequestMapping注解定义url | |
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中。
四、总体流程