Spring Boot框架 - SpringMVC专题

一、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中。

四、总体流程

  

    

猜你喜欢

转载自www.cnblogs.com/pinenut/p/11851734.html