SpringMVC之源码分析--Controller(一)

猫耳山在天边 2019-06-27

概述

Controller是Spring MVC为我们提供的基础的控制器接口,和HttpServlet一样,接收request和response参数处理用户请求,并返回ModelAndView,从概念上可以类比Struts的Action。

Controller主要实现的如下功能:

  • 接收并处理用户请求
  • 调用业务方法
  • 返回ModelAndView

基于Controller开发请求处理Handler的特点:

  • 需要实现Controller接口
  • 请求参数需从请求request中获取
  • 请求处理Handler与Spring深度耦合

就目前项目开发,几乎不会使用此方式进行开发,除非维护一些历史项目。目前是基于注解进行开发,从Spring2.5及之后开始支持。编写本章内容主要是基于知识点的全面性来考虑,大家可以了解了解。

本系列文章是基于Spring5.0.5RELEASE。

接口Controller

Spring提供的Controller接口,定义了一个方法,作用就是处理用户请求,源码如下:

package org.springframework.web.servlet.mvc;

public interface Controller {
    
    /**
     *接收request和response参数,处理用户请求
     *参数从request中获取
     *方法返回ModelAndView,Model为模型数据,View为视图
     */
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

Spring提供的实现类

Spring提供了如下实现:

SpringMVC之源码分析--Controller(一)

AbstractController 控制浏览器缓存、支持的请求方法等等

ServletForwardingController 将Spring Handler接收的请求转发给一个Servlet去执行

ParameterizableViewController 参数化视图控制器,根据参数的逻辑视图名直接选择需要展示的视图

AbstractUrlViewController 根据请求URL路径直接转化为逻辑视图名的支持基类

UrlFilenameViewController 将请求URL路径转换为逻辑视图名并返回的实现类

ServletWrappingController Servlet包装控制器

以上是Spring MVC为我们提供Controller接口的默认实现,下面我们接着分析这些实现类。

AbstractController

该抽象类继承WebContentGenerator并实现Controller接口,其中WebContentGenerator类用于浏览器缓存控制、自定义Controller支持的请求方法类型(默认支持:GET/HEAD/POST)等。

WebContentGenerator类的主要属性如下:

/** 支持的请求方法类型,默认支持:GET、HEAD、POST */
@Nullable
private Set<String> supportedMethods;

@Nullable
private String allowHeader;
/** 当前请求是否必须有session */
private boolean requireSession = false;

@Nullable
private CacheControl cacheControl;
/** 缓存过期时间,正数表示需要缓存,负数表示不做任何事情 */
private int cacheSeconds = -1; 

@Nullable
private String[] varyByRequestHeaders;

/** 是否使用HTTP1.0协议过期响应头:如果true则会在响应头添加“Expires:”;需要配合cacheSeconds使用 */
private boolean useExpiresHeader = false;

/** 是否使用HTTP1.1协议的缓存控制响应头,如果true则会在响应头添加;需要配合cacheSeconds使用 */
private boolean useCacheControlHeader = true;

/** 是否使用HTTP 1.1协议的缓存控制响应头,如果true则会在响应头添加;需要配合cacheSeconds使用 */
private boolean useCacheControlNoStore = true;

AbstractController类源码如下:

/** 表示该控制器是否在执行时同步session,从而保证该会话的用户串行访问该控制器 */
private boolean synchronizeOnSession = false;

/**
 *实现Controller接口的handleRequest方法
 */
@Override
@Nullable
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    if (HttpMethod.OPTIONS.matches(request.getMethod())) {
        response.setHeader("Allow", getAllowHeader());
        return null;
    }

    // 检查是否支持请求方法以及必须的session
    checkRequest(request);
    // 根据设置准备response
    prepareResponse(response);

    // 如果必要顺序执行handleRequestInternal方法
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                return handleRequestInternal(request, response);
            }
        }
    }
    
    return handleRequestInternal(request, response);
}

/**
 * 需要子类实现的模板方法
 */
@Nullable
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
        throws Exception;

实战

本章实战是通过继承AbstractController类编写用户请求handler,测试如下几点:

  • 继承AbstractController抽象类,实现handlerRequestInternal方法处理用户请求

通过前几篇及上面的分析,SimpleControllerHandlerAdapter类可以适配实现了Controller接口的类,AbstractController实现了Controller接口的handleRequest方法,并预留了handleRequestInternal模板方法供子类实现,用户处理用户请求,根据这个思路,我们要使用SimpleControllerHandlerAdapter适配Controller时,只需继承AbstractController类并实现handleRequestInternal方法即可,具体源码如下:

自定义Controller源码:

/**
 *继承AbstractController并实现handleRequestInternal方法
 */
public class HelloWorldController extends AbstractController {

    /**
     *通过response直接回写数据,也可通过ModelAndView指定逻辑视图并回写数据
     */
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Writer writer = response.getWriter();
        writer.write("hello AbstractController");
        writer.flush();
        return null;
    }

}

配置文件配置如下:

<!-- 使用BeanNameUrlHandlerMapping映射器
     可以不用配置,Spring MVC默认支持:BeanNameUrlHanderMapping、RequestMappingHandlerMapping
 -->
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>

<!-- 指定Spring使用SimpleControllerHandlerAdapter适配器
     可以不用配置,Spring MVC默认支持:HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter
 -->
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

<!-- 配置Controller bean -->
<bean name="/helloWorldController" class="com.github.dalianghe.controller.HelloWorldController"/>

验证结果

代码编写完后,启动应用进行测试,AbstractController默认支持GET、HEAD、POST方法类型,我们使用Postman进行测试,GET请求结果如下:

SpringMVC之源码分析--Controller(一)

HEAD、POST也能处理,但如果发起PUT请求则返回不支持方法类型,如下图:

SpringMVC之源码分析--Controller(一)

  • 指定请求方法类型

AbstractController默认支持GET、HEAD、POST三种请求类型,本例通过设置supportedMethods属性来设置支持的请求方法类型,代码如下:

配置文件配置如下:

<!-- 配置Controller bean -->
<bean name="/helloWorldController" class="com.github.dalianghe.controller.HelloWorldController">
    <!-- 设置支持的请求方法类型,如下支持PUT、POST -->
    <property name="supportedMethods">
        <set>
            <value>PUT</value>
            <value>POST</value>
        </set>
    </property>
</bean>

验证结果

只需在Controller Bean修改supportMethods属性接口,测试PUT请求,结果如下:

SpringMVC之源码分析--Controller(一)

  • requireSession前置检查

当把requireSession属性设置为true时,访问Controller需检查有无session,如果没有将跑出HttpSessionRequiredException异常,代码如下:

配置文件配置如下:

<!-- 配置Controller bean -->
<bean name="/helloWorldController" class="com.github.dalianghe.controller.HelloWorldController">
    <!-- 设置支持的请求方法类型,如下支持PUT、POST -->
    <property name="supportedMethods">
        <set>
            <value>GET</value>
            <value>POST</value>
        </set>
    </property>
    <!-- 访问此处理器前检查session -->
    <property name="requireSession" value="true"/>
</bean>
<!-- 增加一个处理器,用于获取session -->
<bean id="/helloWorld2Controller" class="com.github.dalianghe.controller.HelloWorld2Controller"/>

新增的HelloWorld2Controller与HelloWorldController代码基础上增加获取session的代码,如下:

request.getSession(true);

测试验证

首先验证没有session情况下,结果如下:

SpringMVC之源码分析--Controller(一)

说明在访问到自定义Controller前检查session没有,抛出HttpSessionRequiredException异常

我们增加了一个新的controller用于设置创建session,此时我们访问一次后,再访问需要验证的session的请求,结果如下:

SpringMVC之源码分析--Controller(一)

通过结果可见,测试能正常访问了。

总结

本章主要分析了Controller接口、AbstractController抽象类以及对自定义Controller的几个重要属性进行了测试,希望对大家有帮助,谢谢。

最后创建了qq群方便大家交流,可扫描加入,同时也可加我qq:276420284,共同学习、共同进步,谢谢!

SpringMVC之源码分析--Controller(一)

相关推荐