Spring源码之Web MVC framework官方文档翻译

22.1 Introduction to Spring Web MVC framework

Spring Web model-view-controller (MVC)框架是围绕一个DispatcherServlet设计的,该DispatcherServlet将请求分派给处理程序,具有可配置的处理程序映射、视图解析、地区、时区和主题解析以及对上传文件的支持。默认处理程序基于@Controller和@RequestMapping注释,提供了大量灵活的处理方法。随着Spring 3.0的引入,@Controller机制还允许您通过@PathVariable注释和其他特性创建RESTful Web站点和应用程序。

在Spring Web MVC和Spring中,一个关键的设计原则是“对扩展开放,对修改关闭”。

Spring Web MVC核心类中的一些方法被标记为final。作为开发人员,您不能覆盖这些方法来提供自己的行为。这不是武断地做的,而是具体地考虑到这一原则。

关于这一原理的解释,请参考Seth Ladd等人的专家Spring Web MVC和Web Flow;具体参见第一版第117页的“A Look At Design”一节。另外,请看:

  • Bob Martin, The Open-Closed Principle (PDF)

 在使用Spring MVC时,您不能向最终方法添加建议。例如,您不能向AbstractController.setSynchronizeOnSession()方法添加通知。有关AOP代理的更多信息以及为什么不能向最终方法添加通知,请参阅第11.6.1节“理解AOP代理”。

在Spring Web MVC中,您可以使用任何对象作为命令或表单支持对象;您不需要实现特定于框架的接口或基类。Spring的数据绑定非常灵活:例如,它将类型不匹配视为可由应用程序评估的验证错误,而不是系统错误。因此,不需要在表单对象中将业务对象的属性复制为简单的非类型化字符串,只需处理无效提交或正确转换字符串即可。相反,最好直接绑定到业务对象。

Spring的视图非常灵活。控制器通常负责准备带有数据的模型映射并选择视图名称,但它也可以直接写入响应流并完成请求。模型(MVC中的M)是一个映射接口,它允许视图技术的完全抽象。您可以直接集成基于模板的呈现技术,如JSP、Velocity和Freemarker,或者直接生成XML、JSON、Atom和许多其他类型的内容。模型映射简单地转换为适当的格式,如JSP请求属性、速度模板模型。

22.1.1 Features of Spring Web MVC

Spring Web Flow

Spring Web Flow (SWF)旨在成为管理Web应用程序页面流的最佳解决方案。SWF在Servlet和Portlet环境中与Spring MVC和JSF等现有框架集成。如果您的业务流程(或多个流程)将受益于对话模型,而不是纯粹的请求模型,那么SWF可能是解决方案。

有关SWF的更多信息,请参阅Spring Web Flow网站。

Spring的web模块包含许多独特的web支持特性:

  • 明确的角色划分。每个角色——控制器、验证器、命令对象、表单对象、模型对象、DispatcherServlet、处理程序映射、视图解析器等等——都可以由一个专门的对象来实现。
  • 作为javabean的框架类和应用程序类的强大而简单的配置。这种配置功能包括跨上下文的简单引用,例如从web控制器到业务对象和验证器。
  • 适应性、非侵入性和灵活性。为给定的场景定义您需要的任何控制器方法签名,可能使用其中一个参数注释(例如@RequestParam、@RequestHeader、@PathVariable等)。
  • 可重用的业务代码,不需要复制。使用现有的业务对象作为命令或表单对象,而不是对它们进行镜像,以扩展特定的框架基类。
  • 可自定义绑定和验证。类型不匹配是应用程序级验证错误,它会保留错误的值、本地化的日期和数字绑定,等等,而不是手动解析和转换为业务对象的仅使用字符串的表单对象。
  • 可自定义处理程序映射和视图解析。处理程序映射和视图解析策略的范围从简单的基于url的配置到复杂的、专门构建的解析策略。Spring比要求特定技术的web MVC框架更灵活。
  • 灵活的模式转移。具有名称/值映射的模型传输支持与任何视图技术的轻松集成。
  • 可定制的语言环境、时区和主题解析、支持带或不带Spring标记库的jsp、支持JSTL、支持Velocity而不需要额外的桥接,等等。
  • 一个简单但功能强大的JSP标记库,称为Spring标记库,它支持数据绑定和主题等特性。自定义标记在标记代码方面具有最大的灵活性。有关标记库描述符的信息,请参见附录43章,spring JSP标记库
  • Spring 2.0中引入的JSP表单标记库使在JSP页面中编写表单变得更加容易。有关标记库描述符的信息,请参阅附录44章spring-form JSP标记库
  • 生命周期限定为当前HTTP请求或HTTP会话的bean。这不是Spring MVC本身的特定特性,而是Spring MVC使用的WebApplicationContext容器的特性。这些bean作用域在7.5.4节中描述,“请求、会话、全局会话、应用程序和WebSocket作用域”

 22.1.2 Pluggability of other MVC implementations 其他MVC实现的可插拔性

对于某些项目,非spring MVC实现更可取。许多团队希望利用他们在技能和工具方面的现有投资,例如JSF。

如果您不想使用Spring的Web MVC,但是希望利用Spring提供的其他解决方案,那么您可以轻松地将您选择的Web MVC框架与Spring集成。只需通过其ContextLoaderListener启动Spring根应用程序上下文,并通过其ServletContext属性(或Spring各自的助手方法)从任何操作对象中访问它。不涉及“插件”,因此不需要专门的集成。从web层的角度来看,您只需将Spring作为库使用,并将根应用程序上下文实例作为入口点。

即使没有Spring的Web MVC,您也可以随时使用注册的bean和Spring的服务。在这个场景中,Spring不与其他web框架竞争。它只是解决了许多纯web MVC框架没有涉及的领域,从bean配置到数据访问和事务处理。因此,您可以使用Spring中间层和/或数据访问层来丰富应用程序,即使您只是想使用JDBC或Hibernate的事务抽象。

22.2 The DispatcherServlet

Spring的web MVC框架与许多其他web MVC框架一样,是请求驱动的,围绕一个中心Servlet设计,该Servlet将请求分派给控制器,并提供其他功能,以促进web应用程序的开发。然而,Spring的DispatcherServlet做的不止这些。它与Spring IoC容器完全集成,因此允许您使用Spring的所有其他特性。

下图展示了Spring Web MVC DispatcherServlet的请求处理工作流。熟悉模式的读者会认识到DispatcherServlet是“前端控制器”设计模式(Spring Web MVC与许多其他领先的Web框架共享这种模式)的表达式。

Figure 22.1. The request processing workflow in Spring Web MVC (high level)

DispatcherServlet是一个实际的Servlet(它继承自HttpServlet基类),并在web应用程序中声明。

您需要使用URL映射来映射希望DispatcherServlet处理的请求。下面是Servlet 3.0+环境中的标准Java EE Servlet配置:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = container.addServlet("example", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }
}

在前面的示例中,以/example开头的所有请求都将由名为example的DispatcherServlet实例处理。

WebApplicationInitializer是Spring MVC提供的接口,它可以确保检测到基于代码的配置,并自动用于初始化任何Servlet 3容器。

这个接口的抽象基类实现AbstractAnnotationConfigDispatcherServletInitializer通过简单地指定它的servlet映射和列出配置类,使得注册DispatcherServlet变得更加容易——这甚至是设置Spring MVC应用程序的推荐方法。有关详细信息,请参见基于代码的Servlet容器初始化。

DispatcherServlet是一个实际的Servlet(它继承自HttpServlet基类),因此在web中声明。web应用程序的xml。您需要映射希望DispatcherServlet处理的请求,方法是在相同的web中使用URL映射。xml文件。这是标准的Java EE Servlet配置;下面的示例展示了这样一个DispatcherServlet声明和映射:


    
        example
        org.springframework.web.servlet.DispatcherServlet
        1
    

    
        example
        /example/*
    

如第7.15节“ApplicationContext的附加功能”所述,Spring中的ApplicationContext实例可以限定作用域。在Web MVC框架中,每个DispatcherServlet都有自己的WebApplicationContext,它继承在根WebApplicationContext中已经定义的所有bean。根WebApplicationContext应该包含应该在其他上下文和Servlet实例之间共享的所有基础结构bean。可以在特定于Servlet的范围内覆盖这些继承的bean,并且可以在给定的Servlet实例的本地定义新的特定于范围的bean。

Figure 22.2. Typical context hierarchy in Spring Web MVC

在初始化DispatcherServlet时,Spring MVC寻找一个名为[servlet-name]-servlet.xml 的文件。在web应用程序的WEB-INF目录中创建,并在其中创建定义的bean,覆盖全局作用域中定义的具有相同名称的任何bean的定义。

考虑以下DispatcherServlet Servlet配置(在web.xml文件):


    
        golfing
        org.springframework.web.servlet.DispatcherServlet
        1
    
    
        golfing
        /golfing/*
    

有了上面的Servlet配置,您将需要在应用程序中有一个名为/WEB-INF/golfing-servlet.xml的文件;这个文件将包含所有Spring Web mvc特定的组件(bean)。您可以通过Servlet初始化参数更改该配置文件的确切位置(详细信息请参见下面)。

对于单个DispatcherServlet场景,也可能只有一个根上下文。

 

这可以通过设置一个空的contextConfigLocation servlet初始化参数来配置,如下所示:


    
        contextConfigLocation
        /WEB-INF/root-context.xml
    
    
        dispatcher
        org.springframework.web.servlet.DispatcherServlet
        
            contextConfigLocation
            
        
        1
    
    
        dispatcher
        /*
    
    
        org.springframework.web.context.ContextLoaderListener
    

注意,我们可以通过基于java的配置实现相同的功能:

public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getRootConfigClasses() {
        // GolfingAppConfig defines beans that would be in root-context.xml
        return new Class[] { GolfingAppConfig.class };
    }

    @Override
    protected Class[] getServletConfigClasses() {
        // GolfingWebConfig defines beans that would be in golfing-servlet.xml
        return new Class[] { GolfingWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/golfing/*" };
    }
}

22.2.1 Special Bean Types In the WebApplicationContext

Spring DispatcherServlet使用特殊的bean来处理请求并呈现适当的视图。这些bean是Spring MVC的一部分。您可以通过在WebApplicationContext中配置一个或多个特殊bean来选择要使用的bean。但是,您最初不需要这样做,因为Spring MVC维护了一个默认bean列表,如果您不配置任何bean,就会使用这个列表。下一节将详细介绍这一点。首先,请参阅下表,其中列出了DispatcherServlet所依赖的特殊bean类型。

Table 22.1. Special bean types in the WebApplicationContext

Bean type Explanation

HandlerMapping

根据某些标准将传入的请求映射到处理程序和预处理程序和后处理程序(处理程序拦截器)列表,这些标准的细节因HandlerMapping实现的不同而不同。最流行的实现支持带注释的控制器,但也存在其他实现。

HandlerAdapter

帮助DispatcherServlet调用映射到请求的处理程序,而不管实际调用的是哪个处理程序。例如,调用带注释的控制器需要解析各种注释。因此,HandlerAdapter的主要目的是保护DispatcherServlet不受这些细节的影响。

HandlerExceptionResolver

将异常映射到视图还允许更复杂的异常处理代码。

ViewResolver

将基于逻辑字符串的视图名称解析为实际的视图类型。

LocaleResolver & LocaleContextResolver

解析客户端正在使用的语言环境,可能还有它们所在的时区,以便能够提供国际化的视图

ThemeResolver

解析web应用程序可以使用的主题,例如,提供个性化的布局

MultipartResolver

解析多部分请求,例如支持处理来自HTML表单的文件上传。

FlashMapManager

存储和检索“输入”和“输出”FlashMap,可以使用它们将属性从一个请求传递到另一个请求,通常是通过重定向。

22.2.2 Default DispatcherServlet Configuration

如上一节所述,对于每个特殊bean, DispatcherServlet维护一个默认使用的实现列表。这些信息保存在org.springframe .web.servlet包中DispatcherServlet.properties文件中。

所有特殊bean都有自己的一些合理的缺省值。不过,您迟早需要自定义这些bean提供的一个或多个属性。例如,将InternalResourceViewResolver的prefix属性配置为视图文件的父位置是非常常见的。

不管细节如何,这里需要理解的重要概念是,一旦您在WebApplicationContext中配置了一个特殊的bean(如InternalResourceViewResolver),您就可以有效地覆盖该特殊bean类型的默认实现列表。例如,如果您配置一个InternalResourceViewResolver,则会忽略ViewResolver实现的默认列表。

在22.16节“配置Spring MVC”中,您将了解配置Spring MVC的其他选项,包括MVC Java config和MVC XML命名空间,这两个选项都提供了一个简单的起点,并且假设您对Spring MVC的工作原理知之甚少。无论您选择如何配置您的应用程序,本节中解释的概念都应该对您有所帮助。

22.2.3 DispatcherServlet Processing Sequence DispatcherServlet处理顺序

在您设置了DispatcherServlet之后,一个特定DispatcherServlet的请求进入,DispatcherServlet开始按照以下方式处理请求:

 

在WebApplicationContext中声明的处理程序异常解析器获取在处理请求期间抛出的异常。使用这些异常解析器可以定义自定义行为来处理异常。

Spring DispatcherServlet还支持返回Servlet API指定的最后修改日期。确定特定请求的最后修改日期的过程非常简单:DispatcherServlet查找适当的处理程序映射并测试找到的处理程序是否实现了LastModified接口。如果是,LastModified接口的long getLastModified(request)方法的值将返回给客户机。

您可以通过向web.xml文件中的Servlet声明中添加Servlet初始化参数(int -param元素)来定制各个DispatcherServlet实例。有关支持的参数列表,请参见下表。

Table 22.2. DispatcherServlet initialization parameters

 

  • 在请求中搜索WebApplicationContext并将其绑定为控制器和流程中的其他元素可以使用的属性。默认情况下,它绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE键下。
  • 区域设置解析器绑定到请求,以使流程中的元素能够解析在处理请求(呈现视图、准备数据等)时使用的区域设置。如果不需要区域设置解析,就不需要它。
  • 主题解析器绑定到请求,以让视图等元素决定使用哪个主题。如果不使用主题,可以忽略它。
  • 如果指定多部分文件解析器,将检查请求的多部分;如果找到多个部分,则将请求包装在MultipartHttpServletRequest中,以便由流程中的其他元素进行进一步处理。有关多部分处理的更多信息,请参见第22.10节“Spring的多部分(文件上传)支持”。
  • 搜索适当的处理程序。如果找到处理程序,则执行与该处理程序(预处理程序、后处理程序和控制器)关联的执行链,以准备模型或呈现。
  • 如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理程序或后处理程序拦截了请求,可能是出于安全原因),则没有呈现视图,因为请求可能已经被完成。
Parameter Explanation  

contextClass

类,该类实现ConfigurableWebApplicationContext,由此Servlet实例化并在本地配置。默认情况下,使用XmlWebApplicationContext。  

contextConfigLocation

传递到上下文实例(由contextClass指定)的字符串,以指示在何处可以找到上下文。该字符串可能由多个字符串(使用逗号作为分隔符)组成,以支持多个上下文。对于定义了两次的bean的多个上下文位置,优先考虑最新的位置。

 

namespace

WebApplicationContext的命名空间。默认为 [servlet-name]-servlet.  

 22.3 Implementing Controllers

控制器提供对通常通过服务接口定义的应用程序行为的访问。控制器解释用户输入并将其转换为由视图向用户表示的模型。Spring以一种非常抽象的方式实现了一个控制器,它使您能够创建各种各样的控制器。

Spring 2.5为MVC控制器引入了一个基于注释的编程模型,它使用@RequestMapping、@RequestParam、@ModelAttribute等注释。Servlet MVC和Portlet MVC都可以使用这种注释支持。以这种风格实现的控制器不必扩展特定的基类或实现特定的接口。此外,它们通常不直接依赖于Servlet或Portlet api,尽管您可以轻松地配置对Servlet或Portlet设施的访问。

在Github上的spring-projects Org中可以找到许多web应用程序,它们利用了本节中描述的注释支持,包括MvcShowcase、MvcAjax、MvcBasic、PetClinic、PetCare等。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

如您所见,@Controller和@RequestMapping注释允许灵活的方法名和签名。在这个特定的示例中,该方法接受一个模型,并将视图名作为字符串返回,但是可以使用其他各种方法参数和返回值,如本节后面所述。@Controller和@RequestMapping以及许多其他注释构成了Spring MVC实现的基础。本节记录这些注释以及它们如何在Servlet环境中最常用。

22.3.1 Defining a controller with @Controller

@Controller注释表示一个特定的类充当一个控制器的角色。Spring不要求您扩展任何控制器基类或引用Servlet API。但是,如果需要,您仍然可以引用servlet特定的特性。

@Controller注释充当带注释类的原型,指示其角色。dispatcher扫描此类带注释的类以查找映射方法,并检测@RequestMapping注释(参见下一节)。

您可以使用dispatcher上下文中的标准Spring bean定义显式地定义带注释的控制器bean。然而,@Controller构造型还允许自动检测,与Spring通用支持对齐,用于检测类路径中的组件类,并为它们自动注册bean定义。

要启用此类带注释控制器的自动检测,您可以将组件扫描添加到配置中。使用spring上下文模式,如下面的XML片段所示:




    

    

22.3.2 Mapping Requests With @RequestMapping

您可以使用@RequestMapping注释将url(如/appointment)映射到整个类或特定的处理程序方法。通常,类级注释将特定的请求路径(或路径模式)映射到表单控制器上,附加的方法级注释缩小了特定HTTP方法请求方法(“GET”、“POST”等)或HTTP请求参数条件的主映射。

下面来自Petcare示例的示例展示了Spring MVC应用程序中使用这个注释的控制器:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(path = "/{day}", method = RequestMethod.GET)
    public Map getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

在上面的示例中,@RequestMapping在许多地方使用。第一个用法是在type (class)级别上,它表示控制器中的所有处理程序方法都相对于/appointment路径。get()方法有进一步的@RequestMapping改进:它只接受get请求,这意味着用于/约会的HTTP get调用该方法。add()也有类似的改进,getNewForm()将HTTP方法和路径的定义合并到一起,这样就可以用该方法处理约会/新建的GET请求。

getForDay()方法展示了@RequestMapping: URI模板的另一种用法。(参见“URI模板模式”一节)。

类级别上的@RequestMapping不是必需的。没有它,所有的路径都是绝对的,而不是相对的。下面来自PetClinic示例应用程序的示例展示了一个使用@RequestMapping的多操作控制器:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

上面的示例没有指定GET和PUT、POST等,因为@RequestMapping默认映射所有HTTP方法。使用@RequestMapping(method=GET)或@GetMapping来缩小映射。

Composed @RequestMapping Variants

Spring Framework 4.3引入了@RequestMapping注释的以下方法级组合变体,这些变体有助于简化常见HTTP方法的映射,并更好地表达带注释的处理程序方法的语义。例如,可以将@GetMapping读取为GET @RequestMapping。

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

 下面的示例显示了上一节中的AppointmentsController 的修改版本,该版本使用组合的@RequestMapping注释进行了简化。

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @GetMapping
    public Map get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @GetMapping("/{day}")
    public Map getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @GetMapping("/new")
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @PostMapping
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

@Controller and AOP Proxying

在某些情况下,控制器可能需要在运行时使用AOP代理进行修饰。一个例子是,如果您选择在控制器上直接使用@Transactional注释。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。但是,如果控制器必须实现一个不是Spring上下文回调的接口(例如InitializingBean、*Aware等),您可能需要显式地配置基于类的代理。例如,对于,更改为

New Support Classes for @RequestMapping methods in Spring MVC 3.1

Spring 3.1为@RequestMapping方法引入了一组新的支持类,分别称为RequestMappingHandlerMapping和RequestMappingHandlerAdapter。我们推荐使用它们,甚至要求它们利用Spring MVC 3.1中的新特性。默认情况下,MVC命名空间和MVC Java配置都支持新支持类,但如果两者都不使用,则必须显式配置。本节描述新旧支持类之间的一些重要区别。

在Spring 3.1之前,类型和方法级别的请求映射在两个独立的阶段中进行了检查——DefaultAnnotationHandlerMapping首先选择一个控制器,然后通过AnnotationMethodHandlerAdapter缩小要调用的实际方法的范围。

使用Spring 3.1中的新支持类,RequestMappingHandlerMapping是惟一需要决定应该处理请求的方法的地方。可以将控制器方法看作唯一端点的集合,每个方法的映射都来自类型和方法级别的@RequestMapping信息。

这带来了一些新的可能性。现在,HandlerInterceptor或HandlerExceptionResolver可以期望基于对象的处理程序是HandlerMethod,这允许它们检查准确的方法、其参数和相关注释。URL的处理不再需要在不同的控制器之间进行分割。

还有几件事不再可能:

  • 首先使用SimpleUrlHandlerMapping或BeanNameUrlHandlerMapping选择控制器,然后基于@RequestMapping注释缩小方法的范围。
  • 依赖于方法名作为回退机制来消除两个@RequestMapping方法之间的歧义,这两个方法没有显式的路径映射URL路径,但是在其他方面是平等匹配的,例如通过HTTP方法。在新的支持类中,@RequestMapping方法必须唯一映射。
  • 有一个默认方法(没有显式路径映射),如果没有其他控制器方法更具体地匹配,则使用该方法处理请求。在新的支持类中,如果没有找到匹配的方法,则会引发404错误。

 现有的支持类仍然支持上述特性。然而,要利用Spring MVC 3.1的新特性,您需要使用新的支持类。

URI Template Patterns

URI模板可用于方便地访问@RequestMapping方法中URL的选定部分。

URI模板是一个类似URI的字符串,包含一个或多个变量名。当您用值替换这些变量时,模板就变成了URI。URI模板的RFC定义了如何参数化URI。例如,URI模板http://www.example.com/users/{userId}包含变量userId。将fred值赋给变量将产生http://www.example.com/users/fred。

在Spring MVC中,您可以使用方法参数上的@PathVariable注释将其绑定到URI模板变量的值:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

URI模板“/owners/{ownerId}”指定变量名“ownerId”。当控制器处理这个请求时,ownerId的值被设置为在URI的适当部分中找到的值。例如,当请求/所有者/fred时,ownerId的值是fred。

要处理@PathVariable注释,Spring MVC需要按名称查找匹配的URI模板变量。您可以在注释中指定:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // implementation omitted
}

或者,如果URI模板变量名与方法参数名匹配,则可以忽略该细节。只要您的代码是用调试信息或Java 8上的-parameters编译器标记编译的,Spring MVC就会将方法参数名与URI模板变量名匹配:

@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    // implementation omitted
}

一个方法可以有任意数量的@PathVariable注释:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

当在Map参数上使用@PathVariable注释时,将使用所有URI模板变量填充映射。

可以从类型和方法级别@RequestMapping注释组装URI模板。因此,可以使用/owners/42/pets/21这样的URL调用findPet()方法。

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

@PathVariable参数可以是任何简单类型,如int、long、Date等。Spring会自动转换为适当的类型,如果不能这样做,则会抛出TypeMismatchException异常。您还可以注册对解析其他数据类型的支持。See the section called “Method Parameters And Type Conversion” and the section called “Customizing WebDataBinder initialization”.

URI Template Patterns with Regular Expressions

有时在定义URI模板变量时需要更精确。考虑URL“/spring-web/spring-web 3.0.5.jar”。如何将其分解成多个部分?

@RequestMapping注释支持在URI模板变量中使用正则表达式。语法是{varName:regex},第一部分定义变量名,第二部分定义正则表达式。例如:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
}

Path Patterns

除了URI模板之外,@RequestMapping注释和所有组合的@RequestMapping变体还支持ant风格的路径模式(例如/myPath/*.do)。还支持URI模板变量和ant样式的全局变量的组合(例如/owners/*/pets/{petId})。

Path Pattern Comparison

当URL匹配多个模式时,使用排序来查找最特定的匹配。

URI变量和通配符数量较少的模式被认为更具体。例如/hotels/{hotel}/*有一个URI变量和一个通配符,被认为比/hotels/{hotel}/**更具体,后者是一个URI变量和两个通配符。

如果两个模式具有相同的计数,则认为较长的模式更具体。例如/foo/bar*比/foo/*更长,也更具体。

当两个模式具有相同的计数和长度时,通配符较少的模式被认为更具体。例如/hotels/{hotel}比/hotels/*更具体。

还有一些额外的特别规则:

  • 默认映射模式/**没有其他模式那么具体。例如/api/{a}/{b}/{c}更具体。
  • 前缀模式(如/public/**)不如不包含双通配符的任何其他模式具体。例如/public/path3/{a}/{b}/{c}更具体。

 

有关详细信息,请参见AntPathMatcher中的AntPatternComparator。请注意,路径匹配器可以自定义(请参阅配置Spring MVC一节中的22.16.11节“路径匹配”)。

Path Patterns with Placeholders

@RequestMapping注释中的模式支持针对本地属性和/或系统属性和环境变量的${…}占位符。在需要通过配置定制控制器映射到的路径的情况下,这可能非常有用。有关占位符的更多信息,请参见PropertyPlaceholderConfigurer类的javadoc。

Suffix Pattern Matching

默认情况下,Spring MVC执行。后缀模式匹配,这样映射到/person的控制器也隐式映射到/person.*。这使得通过URL路径(例如/person)请求资源的不同表示形式变得很容易。pdf / person.xml)。

后缀模式匹配可以关闭或限制为一组路径扩展,这些扩展是为内容协商目的显式注册的。通常建议使用/person/{id}这样的常见请求映射来减少歧义,其中点可能不代表文件扩展名,例如。/person/[email protected] vs /person/[email protected]. 此外,正如下文注释中所解释的,后缀模式匹配和内容协商在某些情况下可能被用于尝试恶意攻击,有很好的理由对它们进行有意义的限制。

后缀模式匹配配置见22.16.11节“路径匹配”,内容协商配置见22.16.6节“内容协商”。

Suffix Pattern Matching and RFD

2014年,Trustwave在一篇论文中首次描述了反射文件下载(reflection file download, RFD)攻击。这种攻击类似于XSS,因为它依赖于响应中反映的输入(例如查询参数、URI变量)。然而,RFD攻击不是将JavaScript插入HTML,而是依赖于浏览器切换来执行下载,如果根据文件扩展名(例如.bat, .cmd)双击响应,则将其视为可执行脚本。

在Spring MVC @ResponseBody和ResponseEntity方法中存在风险,因为它们可以呈现客户端可以请求的不同内容类型,包括通过URL路径扩展。但是请注意,仅为内容协商目的禁用后缀模式匹配或禁用路径扩展都不能有效地防止RFD攻击。

为了对RFD进行全面的保护,在呈现响应体Spring MVC之前,添加一个Content-Disposition:inline;filename=f.txt头,建议一个固定和安全的下载文件文件名。只有当URL路径包含的文件扩展名既不是白名单的,也不是为内容协商目的显式注册的时,才需要这样做。然而,当url直接输入浏览器时,它可能会有潜在的副作用。

默认情况下,许多公共路径扩展都是白名单。此外,REST API调用通常不打算在浏览器中直接作为url使用。然而,使用自定义HttpMessageConverter实现的应用程序可以显式地为内容协商注册文件扩展名,而不会为此类扩展添加内容配置头。见第22.16.6节“内容协商”。

这最初是作为CVE-2015-5211工作的一部分引入的。以下是该报告的其他建议:

  • 编码而不是转义JSON响应。这也是一个OWASP XSS建议。有关如何使用Spring实现此目的的示例,请参见Spring -jackson-owasp。
  • 将后缀模式匹配配置为仅关闭或限制为显式注册的后缀。
  • 将内容协商配置为“useJaf”和“"ignoreUnknownPathExtensions”属性设置为false,这将导致未知扩展的url的406响应。但是请注意,如果url在末尾自然地有一个点,那么这可能不是一个选项。
  • 添加X-Content-Type-Options: nosniheader到响应。Spring Security 4在默认情况下可以做到这一点。

Matrix Variables

URI规范RFC 3986定义了在路径段中包含名称-值对的可能性。规范中没有使用特定的术语,可以使用通用的“URI路径参数”,但是更独特的“Matrix URI”(源自Tim Berners-Lee的一篇旧文章)也经常使用,而且众所周知。在Spring MVC中,这些被称为矩阵变量。

矩阵变量可以出现在任意路径段中,每个矩阵变量用“;”分隔;(分号)。例如:“/cars;color=red;year=2012”。多个值可以是“,”(逗号)分隔的“color=red,green,blue”,也可以是重复的变量名“color=red;color=green;color=blue”。

如果期望URL包含矩阵变量,则请求映射模式必须用URI模板表示它们。这确保了无论矩阵变量是否存在,以及以何种顺序提供,都可以正确匹配请求。

下面是提取矩阵变量“q”的例子:

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11

}

由于所有路径段都可能包含矩阵变量,在某些情况下,您需要更具体地确定变量的位置:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22

}

矩阵变量可以定义为可选的,并指定一个默认值:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1

}

所有矩阵变量可以在一个映射中得到:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap matrixVars,
        @MatrixVariable(pathVar="petId"") MultiValueMap petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

}

注意,要启用矩阵变量,必须将RequestMappingHandlerMapping的removeSemicolonContent属性设置为false。默认设置为true。

MVC Java配置和MVC命名空间都提供了启用矩阵变量的选项。

如果您正在使用Java config,那么MVC Java config的高级定制部分将描述如何定制RequestMappingHandlerMapping。

在MVC命名空间中,< mvc:annotation-driven>元素有一个enable-matrix-variables属性,该属性应该设置为true。默认设置为false。




    

Consumable Media Types 可消费的媒体类型

您可以通过指定可消费媒体类型列表来缩小主映射。仅当内容类型请求头与指定的媒体类型匹配时,才匹配请求。例如:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

可消费媒体类型表达式也可以像!text/plain那样被否定,以匹配除具有文本/plain内容类型的请求之外的所有请求。还可以考虑使用MediaType中提供的常量,如APPLICATION_JSON_VALUE和APPLICATION_JSON_UTF8_VALUE。

在类型和方法级别上支持consumption条件。与大多数其他条件不同,在类型级别使用时,方法级别的可消费类型会覆盖而不是扩展类型级别的可消费类型。

Producible Media Types 可延长的媒体类型

您可以通过指定可生成的媒体类型列表来缩小主映射。只有当Accept请求头与这些值之一匹配时,才会匹配请求。此外,使用produces条件可以确保用于生成响应的实际内容类型遵守produces条件中指定的媒体类型。例如:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

请注意,在produces条件中指定的媒体类型也可以选择性地指定一个字符集。例如,在上面的代码片段中,我们指定的媒体类型与MappingJackson2HttpMessageConverter中配置的默认媒体类型(包括UTF-8字符集)相同。

与using类似,可生成的媒体类型表达式可以像!text/plain那样被否定,以匹配除具有接受头值text/plain之外的所有请求。还可以考虑使用MediaType中提供的常量,如APPLICATION_JSON_VALUE和APPLICATION_JSON_UTF8_VALUE。

在类型和方法级别上支持produces条件。与大多数其他条件不同,在类型级别使用时,方法级别的可生成类型会覆盖而不是扩展类型级别的可生成类型。

Request Parameters and Header Values

您可以通过请求参数条件(如“myParam”、“!”)来缩小请求匹配范围。myParam”或“myParam = myValue”。前两个测试请求参数是否存在,第三个测试特定参数值。下面是一个带有请求参数值条件的示例:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

同样可以测试请求头的存在/不存在,或者根据特定的请求头值进行匹配:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets", headers = "myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

虽然可以使用媒体类型通配符匹配内容类型和接受头值(例如“Content-Type =text/*”将匹配“text/plain”和“text/html”),但建议分别使用consumer和produces条件。它们是专门为此目的而设计的。

HTTP HEAD and HTTP OPTIONS

映射到“GET”的@RequestMapping方法也隐式映射到“HEAD”,即不需要显式声明“HEAD”。HTTP头请求被当作HTTP GET处理,除非只计算字节数和“内容长度”头集,而不写入正文。

@RequestMapping方法内置了对HTTP选项的支持。默认情况下,通过将“Allow”响应头设置为在所有具有匹配URL模式的@RequestMapping方法上显式声明的HTTP方法来处理HTTP选项请求。当没有显式声明HTTP方法时,“Allow”报头设置为“GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS”。理想情况下,总是声明@RequestMapping方法要处理的HTTP方法,或者使用专用的组合@RequestMapping变体(请参阅“组合@RequestMapping变体”一节)。

虽然@RequestMapping方法不是必需的,但它可以映射到HTTP HEAD或HTTP选项,或者两者都可以处理。

22.3.3 Defining @RequestMapping handler methods

@RequestMapping处理程序方法可以具有非常灵活的签名。下一节将描述受支持的方法参数和返回值。大多数参数可以以任意顺序使用,惟一的例外是BindingResult参数。下一节将对此进行描述。

Spring 3.1为@RequestMapping方法引入了一组新的支持类,分别称为RequestMappingHandlerMappingRequestMappingHandlerAdapter。我们推荐使用它们,甚至要求它们利用Spring MVC 3.1中的新特性。默认情况下,新的支持类从MVC名称空间中启用,并使用MVC Java配置,但如果两者都不使用,则必须显式配置。

Supported method argument types

以下是支持的方法参数:

  • 请求或响应对象(Servlet API)。选择任何特定的请求或响应类型,例如ServletRequest或HttpServletRequest。
  • 会话对象(Servlet API):类型为HttpSession。这种类型的参数强制存在相应的会话。因此,这样的参数永远不会为空。

 会话访问可能不是线程安全的,特别是在Servlet环境中。如果允许多个请求同时访问一个会话,可以考虑将RequestMappingHandlerAdapter的“synchronizeOnSession”标志设置为“true”。

  • org.springframework.web.context.request.WebRequest or org.springframework.web.context.request.NativeWebRequest。允许通用的请求参数访问以及请求/会话属性访问,而不需要绑定到本机Servlet/Portlet API。
  • java.util.Locale 对于当前的请求区域设置(由可用的最特定的区域设置解析器确定),实际上是MVC环境中配置的LocaleResolver / LocaleContextResolver。
  • java.util.TimeZone (Java 6+) / java.time.ZoneId (on Java 8) 用于与当前请求关联的时区,由LocaleContextResolver确定。
  • java.io.InputStream / java.io.Reader 用于访问请求的内容。该值是Servlet API公开的原始InputStream/Reader。
  • java.io.OutputStream / java.io.Writer 用于生成响应的内容。该值是Servlet API公开的原始输出流/写入器。
  • org.springframework.http.HttpMethod 对于HTTP请求方法。
  • java.security.Principal 包含当前经过身份验证的用户。
  • @PathVariable 用于访问URI模板变量的带注释的参数。
  • @MatrixVariable 用于访问位于URI路径段中的名称-值对的带注释参数。
  • @RequestParam 用于访问特定Servlet请求参数的带注释的参数。参数值被转换为声明的方法参数类型。
  • @RequestHeader 用于访问特定Servlet请求HTTP头的带注释的参数。参数值被转换为声明的方法参数类型。
  • @RequestBody 用于访问HTTP请求体的带注释的参数。使用HttpMessageConverters将参数值转换为声明的方法参数类型。
  • @RequestPart 用于访问“多部分/表单-数据”请求部分的内容的带注释的参数。
  • @SessionAttribute 用于访问现有的永久会话属性(例如用户身份验证对象)的带注释的参数,而不是通过@SessionAttributes作为控制器工作流的一部分临时存储在会话中的模型属性。
  • @RequestAttribute 用于访问请求属性的带注释的参数。
  • HttpEntity 访问Servlet请求HTTP头和内容的参数。请求流将使用HttpMessageConverters转换为实体主体。
  • java.util.Map/org.springframework.ui.Model/org.springframework.ui.ModelMap 用于丰富公开给web视图的隐式模型。
  • org.springframework.web.servlet.mvc.support.RedirectAttributes 指定在重定向时要使用的确切属性集,并添加flash属性(临时存储在服务器端以使重定向后的请求可以使用这些属性)。
  • 命令或表单对象将请求参数绑定到bean属性(通过setter)或直接绑定到字段,具有可自定义类型转换,取决于@InitBinder方法和/或HandlerAdapter配置。查看RequestMappingHandlerAdapter上的webBindingInitializer属性。默认情况下,这些命令对象及其验证结果将作为模型属性公开,使用命令类名——例如,“some.package.OrderAddress”类型的命令对象的模型属性“orderAddress”。可以在方法参数上使用ModelAttribute注释来定制所使用的模型属性名。
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult 前一个命令或表单对象的验证结果(前一个方法参数)。
  • org.springframework.web.bind.support.SessionStatus 用于将表单处理标记为complete的状态句柄,它触发对处理程序类型级别上的@SessionAttributes注释所指示的会话属性进行清理。
  • org.springframework.web.util.UriComponentsBuilder 一个构建器,用于准备相对于当前请求的主机、端口、模式、上下文路径和servlet映射的文字部分的URL。

 Errors 或BindingResult参数必须遵循立即绑定的模型对象,因为方法签名可能有多个模型对象,Spring将为每个模型对象创建一个单独的BindingResult实例,因此下面的示例将不起作用:

Invalid ordering of BindingResult and @ModelAttribute. 

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

注意,在Pet和BindingResult之间有一个模型参数。为了让这个工作,你必须重新排序参数如下:

@PostMapping
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

 JDK 1.8的java.util.Optional是一种方法参数类型,带有具有所需属性的注释(例如@RequestParam、@RequestHeader等)。在这些情况下,Optional等价于required=false。

Supported method return types

以下是支持的返回类型:

  • 一个ModelAndView对象,使用命令对象和@ModelAttribute注释的引用数据访问器方法的结果隐式地丰富了模型。
  • 一个Model对象,通过RequestToViewNameTranslator隐式地确定视图名,通过命令对象和@ModelAttribute注释的引用数据访问器方法的结果隐式地丰富模型。
  • 用于公开模型的Map 对象,视图名通过RequestToViewNameTranslator隐式地确定,模型隐式地使用命令对象和@ModelAttribute注释的引用数据访问器方法的结果进行充实。
  • View 对象,通过命令对象和带注释的引用数据访问器方法隐式地确定模型。处理程序方法还可以通过声明模型参数以编程方式丰富模型(参见上面)。
  • 一个String 值,它被解释为逻辑视图名,模型通过命令对象和带注释的引用数据访问器方法隐式地确定。处理程序方法还可以通过声明模型参数以编程方式丰富模型(参见上面)。
  • void 如果方法本身处理响应(通过直接编写响应内容,声明类型为ServletResponse / HttpServletResponse的参数),或者假设视图名是通过RequestToViewNameTranslator隐式确定的(而不是在处理程序方法签名中声明响应参数)。
  • 如果方法使用@ResponseBody注释,则返回类型被写入响应HTTP主体。返回值将使用HttpMessageConverters转换为声明的方法参数类型。
  • 一个HttpEntity or ResponseEntity 对象提供对Servlet响应HTTP头和内容的访问。实体主体将使用HttpMessageConverters转换为响应流。
  • HttpHeaders对象返回一个没有正文的响应。
  • 一个Callable 当应用程序希望在Spring MVC管理的线程中异步生成返回值时,可以返回Callable
  • 一个DeferredResult当应用程序希望从自己选择的线程生成返回值时,可以返回。
  • 一个ListenableFuture < ?>或CompletableFuture/CompletionStage当应用程序希望从线程池提交中生成值时,可以返回。
  • 可以返回ResponseBodyEmitter来异步地向响应写入多个对象;也支持作为ResponseEntity中的主体。
  • 可以返回SseEmitter ,将服务器发送的事件异步写入响应;也支持作为ResponseEntity中的主体。
  • StreamingResponseBody可以被返回以异步写入响应OutputStream;也支持作为ResponseEntity中的主体。
  • 任何其他返回类型都被认为是要公开给视图的单个模型属性,使用方法级别上通过@ModelAttribute指定的属性名(或者基于返回类型类名的默认属性名)。使用命令对象和@ModelAttribute注释的引用数据访问器方法的结果隐式地丰富了模型。

 Binding request parameters to method parameters with @RequestParam

使用@RequestParam注释将请求参数绑定到控制器中的方法参数。

下面的代码片段展示了它的用法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

默认情况下,使用这个注释的参数是必需的,但是您可以通过将@RequestParam的required属性设置为false(例如,@RequestParam(name="id", required=false)来指定参数是可选的。

如果目标方法参数类型不是字符串,则自动应用类型转换。

当在Map或MultiValueMap参数上使用@RequestParam注释时,将使用所有请求参数填充映射。

Mapping the request body with the @RequestBody annotation

@RequestBody方法参数注释指出方法参数应该绑定到HTTP请求体的值。例如:

@PutMapping("/something")
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

您可以使用HttpMessageConverter将请求体转换为方法参数。HttpMessageConverter负责将HTTP请求消息转换为对象,并将对象转换为HTTP响应体。RequestMappingHandlerAdapter使用以下默认httpmessageconverter支持@RequestBody注释:

  • ByteArrayHttpMessageConverter  转换字节数组
  • StringHttpMessageConverter 转换字符串
  • FormHttpMessageConverter  从MultiValueMap转换表单数据或者表单数据转换成MultiValueMap
  • SourceHttpMessageConverter  converts to/from a javax.xml.transform.Source.

有关这些转换器的更多信息,请参见消息转换器。还要注意,如果使用MVC名称空间或MVC Java配置,默认情况下会注册更广泛的消息转换器。有关更多信息,请参见22.16.1节“启用MVC Java配置或MVC XML命名空间”。

如果您打算读写XML,您将需要使用来自org.springframework的特定编组器和反编组器实现来配置MarshallingHttpMessageConverter。oxm包。下面的示例展示了如何在配置中直接这样做,但是如果您的应用程序是通过MVC名称空间或MVC Java配置配置的,请参见22.16.1节,“启用MVC Java配置或MVC XML名称空间”。


    
        
            
            
        
    




    
    


@RequestBody方法参数可以用@Valid注释,在这种情况下,将使用配置的验证器实例对其进行验证。在使用MVC名称空间或MVC Java配置时,会自动配置JSR-303验证器,假设类路径上有可用的JSR-303实现。

就像使用@ModelAttribute参数一样,可以使用错误参数检查错误。如果没有声明这样的参数,将引发MethodArgumentNotValidException。异常在DefaultHandlerExceptionResolver中处理,它将一个400错误发送回客户机。

Mapping the response body with the @ResponseBody annotation

@ResponseBody注释类似于@RequestBody。可以将此注释放在方法上,并指示返回类型应直接写入HTTP响应体(而不是放在模型中,或解释为视图名称)。例如:

@GetMapping("/something")
@ResponseBody
public String helloWorld() {
    return "Hello World";
}

上面的示例将导致文本Hello World被写入HTTP响应流。

与@RequestBody一样,Spring使用HttpMessageConverter将返回的对象转换为响应体。有关这些转换器的更多信息,请参见上一节和消息转换器。

Creating REST Controllers with the @RestController annotation

让控制器实现REST API是非常常见的用例,因此只提供JSON、XML或自定义媒体类型内容。为了方便起见,您可以用@RestController注释控制器类,而不是用@ResponseBody注释所有@RequestMapping方法。

@RestController是一个原型注释,它结合了@ResponseBody和@Controller。不仅如此,它还赋予了您的控制器更多的意义,并且可能在框架的未来版本中带有额外的语义。

与普通的@Controllers一样,@RestController可以由@ControllerAdvice或@RestControllerAdvice bean辅助。有关详细信息,请参见“用@ControllerAdvice和@RestControllerAdvice通知控制器”一节。

Using HttpEntity

HttpEntity类似于@RequestBody和@ResponseBody。HttpEntity(和response-specific子类ResponseEntity)除了可以访问请求和响应体之外,还可以访问请求和响应头,如下所示:

@RequestMapping("/something")
public ResponseEntity handle(HttpEntity requestEntity) throws UnsupportedEncodingException {
    String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
    byte[] requestBody = requestEntity.getBody();

    // do something with request header and body

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity("Hello World", responseHeaders, HttpStatus.CREATED);
}

上面的示例获取MyRequestHeader请求头的值,并将主体作为字节数组读取。它将MyResponseHeader添加到响应,将Hello World写入响应流,并将响应状态代码设置为201 (Created)。

与@RequestBody和@ResponseBody一样,Spring使用HttpMessageConverter来转换请求流和响应流。有关这些转换器的更多信息,请参见上一节和消息转换器。

Using @ModelAttribute on a method

@ModelAttribute注释可以用于方法或方法参数。本节解释它在方法上的用法,下一节解释它在方法参数上的用法。

方法上的@ModelAttribute表示该方法的目的是添加一个或多个模型属性。这些方法支持与@RequestMapping方法相同的参数类型,但不能直接映射到请求。相反,控制器中的@ModelAttribute方法在同一控制器中的@RequestMapping方法之前调用。举几个例子:

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

@ModelAttribute方法用于用通常需要的属性填充模型,例如用状态或pet类型填充下拉列表,或者检索像Account这样的命令对象,以便使用它表示HTML表单上的数据。下一节将进一步讨论后一种情况。

注意@ModelAttribute方法的两种风格。在第一种方法中,该方法通过返回隐式地添加属性。在第二种方法中,该方法接受一个模型并向其添加任意数量的模型属性。您可以根据自己的需要在这两种风格之间进行选择。

一个控制器可以有任意数量的@ModelAttribute方法。所有这些方法都是在同一个控制器的@RequestMapping方法之前调用的。

 @ModelAttribute方法也可以在@ControllerAdvice-annotated class中定义,这些方法适用于许多控制器。有关详细信息,请参见“用@ControllerAdvice和@RestControllerAdvice通知控制器”一节。

如果没有显式指定模型属性名,会发生什么情况?在这种情况下,将根据模型属性的类型为其分配一个默认名称。例如,如果该方法返回Account类型的对象,则使用的默认名称是“Account”。您可以通过@ModelAttribute注释的值来更改它。如果直接向模型添加属性,则使用适当的重载addAttribute(..)方法——即,带或不带属性名。

@ModelAttribute注释也可以用于@RequestMapping方法。在这种情况下,@RequestMapping方法的返回值被解释为一个模型属性,而不是一个视图名。然后根据视图名称约定派生视图名称,这与返回void的方法非常相似——请参见22.13.3节“默认视图名称”。

Using @ModelAttribute on a method argument

正如前一节所解释的,@ModelAttribute可以用于方法或方法参数。本节解释它在方法参数上的用法。

方法参数上的@ModelAttribute表示应该从模型中检索参数。如果在模型中不存在,则应该首先实例化参数,然后将其添加到模型中。一旦在模型中出现,参数的字段应该从所有具有匹配名称的请求参数中填充。这在Spring MVC中称为数据绑定,这是一种非常有用的机制,它使您不必单独解析每个表单字段。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

在上面的例子中,Pet实例从何而来?有几个选择:

  • 由于使用@SessionAttributes,它可能已经在模型中了——请参阅“使用@SessionAttributes在请求之间的HTTP会话中存储模型属性”一节。
  • 由于同一控制器中的@ModelAttribute方法,它可能已经在模型中了——如前一节所述。
  • 它可以基于URI模板变量和类型转换器进行检索(下面将详细解释)。
  • 可以使用其默认构造函数实例化它。

 @ModelAttribute方法是从数据库检索属性的常用方法,可以通过使用@SessionAttributes在请求之间存储属性。在某些情况下,通过使用URI模板变量和类型转换器来检索属性可能比较方便。下面是一个例子:

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

在本例中,模型属性的名称(即)匹配URI模板变量的名称。如果您注册转换器,它可以将字符串帐户值转换为帐户实例,那么上面的示例就可以正常工作,而不需要@ModelAttribute方法。

下一步是数据绑定。WebDataBinder类根据名称将请求参数名称(包括查询字符串参数和表单字段)匹配到模型属性字段。匹配字段是在必要时应用类型转换(从字符串到目标字段类型)之后填充的。第9章验证、数据绑定和类型转换将讨论数据绑定和验证。在“定制WebDataBinder初始化”一节中介绍了为控制器级别定制数据绑定过程。

由于数据绑定,可能会出现诸如缺少必需字段或类型转换错误等错误。要检查此类错误,请在@ModelAttribute参数之后立即添加BindingResult参数:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

有了BindingResult,您可以检查是否发现了错误,在这种情况下,使用Spring的表单标记显示错误的表单很常见。

注意,在某些情况下,在没有数据绑定的情况下访问模型中的属性可能是有用的。在这种情况下,您可以将模型注入控制器,或者在注释上使用绑定标志:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) {

    // ...
}

除了数据绑定之外,您还可以使用自己的自定义验证器调用验证,该验证器传递用于记录数据绑定错误的BindingResult。这使得数据绑定和验证错误可以累积在一个地方,然后报告给用户:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    new PetValidator().validate(pet, result);
    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

或者您可以通过添加JSR-303 @Valid注释来自动调用验证:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

Using @SessionAttributes to store model attributes in the HTTP session between requests

类型级别的@SessionAttributes注释声明特定处理程序使用的会话属性。这通常会列出模型属性的名称或模型属性的类型,它们应该透明地存储在会话或某些会话存储中,作为后续请求之间的表单支持bean。

下面的代码片段展示了这个注释的用法,指定了模型属性名:

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

Using @SessionAttribute to access pre-existing global session attributes

如果您需要访问全局管理的已存在的会话属性,即控制器外部(例如通过筛选器),并且可能存在也可能不存在,请使用方法参数上的@SessionAttribute注释:

@RequestMapping("/")
public String handle(@SessionAttribute User user) {
    // ...
}

对于需要添加或删除会话属性的用例,考虑注入org.springframework.web.context.request.WebRequest或javax.servlet.http.HttpSession进入控制器方法。

对于作为控制器工作流一部分的会话中模型属性的临时存储,请考虑使用SessionAttributes,如“使用@SessionAttributes在请求之间的HTTP会话中存储模型属性”一节中所述。

Using @RequestAttribute to access request attributes

类似于@SessionAttribute, @RequestAttribute注释可以用来访问过滤器或拦截器创建的预先存在的请求属性:

@RequestMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}

Working with "application/x-www-form-urlencoded" data

前几节讨论了使用@ModelAttribute来支持来自浏览器客户机的表单提交请求。对于来自非浏览器客户机的请求,也建议使用相同的注释。然而,在处理HTTP PUT请求时,有一个显著的不同。浏览器可以通过HTTP GET或HTTP POST提交表单数据。非浏览器客户端也可以通过HTTP PUT提交表单。这带来了一个挑战,因为Servlet规范要求ServletRequest.getParameter*()方法家族只支持HTTP POST而不是HTTP PUT的表单字段访问。

为了支持HTTP PUT和PATCH 请求,spring-web模块提供了过滤器HttpPutFormContentFilter,该过滤器可以在web.xml中配置:


    httpPutFormFilter
    org.springframework.web.filter.HttpPutFormContentFilter



    httpPutFormFilter
    dispatcherServlet



    dispatcherServlet
    org.springframework.web.servlet.DispatcherServlet

上面的过滤器使用内容类型application/x-www-form-urlencode拦截HTTP PUT和PATCH 请求,从请求体读取表单数据,并包装ServletRequest,以便通过ServletRequest.getParameter*()方法家族提供表单数据。

由于HttpPutFormContentFilter使用请求体,因此不应该将其配置为PUT url或PATCH  url,这些url依赖于应用程序/x-www-form-urlencode的其他转换器。这包括@RequestBody MultiValueMap和HttpEntity>。

 Mapping cookie values with the @CookieValue annotation

@CookieValue注释允许将方法参数绑定到HTTP cookie的值。

让我们考虑以下cookie已收到http请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代码示例演示了如何获取JSESSIONID cookie的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
    //...
}

如果目标方法参数类型不是字符串,则自动应用类型转换。参见“方法参数和类型转换”一节。

Servlet和Portlet环境中带注释的处理程序方法支持此注释。

Mapping request header attributes with the @RequestHeader annotation

@RequestHeader注释允许将方法参数绑定到请求头。

下面是一个示例请求头:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

下面的代码示例演示了如何获取Accept-Encoding和Keep-Alive标头的值:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

如果方法参数不是字符串,则自动应用类型转换。参见“方法参数和类型转换”一节。

当在Map, MultiValueMap,或HttpHeaders参数上使用@RequestHeader注释时,映射将填充所有header值。

内置支持将逗号分隔的字符串转换为字符串数组/集合或类型转换系统已知的其他类型。例如,用@RequestHeader("Accept")注释的方法参数可以是String类型,也可以是String[]或List

Servlet和Portlet环境中带注释的处理程序方法支持此注释。

Method Parameters And Type Conversion

从请求中提取的基于字符串的值,包括请求参数、路径变量、请求头和cookie值,可能需要将它们转换为方法参数或字段的目标类型(例如,将请求参数绑定到@ModelAttribute参数中的字段)。如果目标类型不是字符串,Spring将自动转换为适当的类型。支持所有简单类型,如int、long、Date等。您可以通过WebDataBinder(参见“定制WebDataBinder初始化”一节)或通过向FormattingConversionService注册格式化程序(参见9.6节“Spring字段格式化”)来进一步定制转换过程。

Customizing WebDataBinder initialization

使用@InitBinder注释控制器方法允许您在控制器类中直接配置web数据绑定。@InitBinder标识初始化WebDataBinder的方法,这些方法将用于填充带注释的处理程序方法的命令和对象参数。

这样的init-binder方法支持@RequestMapping方法支持的所有参数,除了命令/表单对象和相应的验证结果对象。int -binder方法不能有返回值。因此,它们通常被声明为void。典型的参数包括WebDataBinder与WebRequest或java.util.Locale,允许代码注册特定于上下文的编辑器。

下面的示例演示了如何使用@InitBinder为所有java.util.Date配置一个CustomDateEditor

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

或者,从Spring 4.2开始,考虑使用addCustomFormatter来指定格式化程序实现,而不是使用PropertyEditor实例。如果在共享的FormattingConversionService中碰巧也有基于格式的设置,并且使用相同的方法对绑定规则的特定于控制器的调整进行重用,那么这一点尤其有用。

@Controller
public class MyFormController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

Configuring a custom WebBindingInitializer

要外部化数据绑定初始化,可以提供WebBindingInitializer接口的自定义实现,然后通过为AnnotationMethodHandlerAdapter提供自定义bean配置来启用该接口,从而覆盖默认配置。

下面来自PetClinic应用程序的示例展示了使用WebBindingInitializer接口的自定义实现的配置,该接口是org.springframework.samples.petclinic.web.ClinicBindingInitializer,它配置了几个PetClinic控制器所需的propertyeditor。


    
    
        
    

@InitBinder方法也可以在@ControllerAdvice注释的类中定义,在这种情况下,它们适用于匹配的控制器。

Advising controllers with @ControllerAdvice and @RestControllerAdvice

@ControllerAdvice注释是一个组件注释,允许通过类路径扫描自动检测实现类。当使用MVC名称空间或MVC Java配置时,它会自动启用。

用@ControllerAdvice注释的类可以包含@ExceptionHandler、@InitBinder和@ModelAttribute注释的方法,这些方法将应用于跨所有控制器层次结构的@RequestMapping方法,而不是声明它们的控制器层次结构。

@RestControllerAdvice是一种替代方法,其中@ExceptionHandler方法默认假设@ResponseBody语义。

@ControllerAdvice和@RestControllerAdvice都可以针对控制器的子集:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

查看@ControllerAdvice文档了解更多细节。

Jackson Serialization View Support

有时,根据上下文过滤将序列化到HTTP响应体的对象是有用的。为了提供这种功能,Spring MVC内置了使用Jackson的序列化视图进行呈现的支持。

要将它与@ResponseBody控制器方法或返回ResponseEntity的控制器方法一起使用,只需添加@JsonView注释,并用一个类参数指定要使用的视图类或接口:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

注意,尽管@JsonView允许指定多个类,但是控制器方法上的使用只支持一个类参数。如果需要启用多个视图,请考虑使用复合接口。

对于依赖于视图解析的控制器,只需将序列化视图类添加到模型中:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

Jackson JSONP Support

为了启用对@ResponseBody和ResponseEntity方法的JSONP支持,声明一个@ControllerAdvice bean,它扩展了AbstractJsonpResponseBodyAdvice,如下所示,其中构造函数参数指示JSONP查询参数名称:

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

对于依赖于视图解析的控制器,当请求具有名为JSONP或callback的查询参数时,将自动启用JSONP。可以通过jsonpParameterNames属性自定义这些名称。

在Spring Framework 4.3.18中,JSONP支持已被弃用,并将在Spring Framework 5.1中被删除,取而代之的应该是CORS。

22.3.4 Asynchronous Request Processing 异步请求处理

Spring MVC 3.2引入了基于Servlet 3的异步请求处理。与通常返回值不同,控制器方法现在可以返回java.util.concurrent.Callable可调用并从Spring MVC托管线程生成返回值。同时,主Servlet容器线程被退出并释放,并允许处理其他请求。Spring MVC在TaskExecutor的帮助下在单独的线程中调用可调用的对象,当可调用的对象返回时,请求被分派回Servlet容器,使用可调用对象返回的值恢复处理。下面是这样一个控制器方法的例子:

@PostMapping
public Callable processUpload(final MultipartFile file) {

    return new Callable() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

另一个选项是控制器方法返回一个DeferredResult实例。在这种情况下,返回值也将由任何线程生成,即不受Spring MVC管理的线程。例如,可以在响应某些外部事件(如JMS消息、计划的任务等)时生成结果。下面是这样一个控制器方法的例子:

@RequestMapping("/quotes")
@ResponseBody
public DeferredResult quotes() {
    DeferredResult deferredResult = new DeferredResult();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// In some other thread...
deferredResult.setResult(data);

如果不了解Servlet 3.0异步请求处理特性,这可能很难理解。读懂这一点肯定会有帮助。下面是一些关于基本机制的基本事实:

 

  • 可以通过调用request.startAsync()将ServletRequest置于异步模式。这样做的主要效果是Servlet和任何过滤器都可以退出,但是响应将保持开放,以便稍后完成处理。
  • startAsync()调用返回AsyncContext,该上下文可用于进一步控制异步处理。例如,它提供了方法分派,这类似于Servlet API的转发,只是它允许应用程序在Servlet容器线程上恢复请求处理。
  • ServletRequest提供对当前DispatcherType的访问,可用于区分处理初始请求、异步分派、转发和其他分派器类型。

考虑到上述情况,下面是异步请求处理的事件序列,具有一个可调用的:

  • Controller returns a Callable.
  • Spring MVC启动异步处理,并将可调用的任务提交给TaskExecutor,以便在单独的线程中进行处理。
  • DispatcherServlet和all Filter退出Servlet容器线程,但响应仍然打开。
  • Callable生成一个结果,Spring MVC将请求发送回Servlet容器以继续处理。
  • 再次调用DispatcherServlet,并使用可调用的异步生成的结果继续处理。

DeferredResult的序列非常类似,只是由应用程序从任何线程生成异步结果:

  • Controller返回一个DeferredResult,并将其保存在内存中的某个队列或列表中,以便访问。
  • Spring MVC启动异步处理。
  • DispatcherServlet和所有配置的过滤器都退出请求处理线程,但响应仍然打开。
  • 应用程序从某个线程设置DeferredResult, Spring MVC将请求发送回Servlet容器。
  • 再次调用DispatcherServlet并使用异步生成的结果继续处理。

有关异步请求处理动机以及何时或为什么使用异步请求处理的更多背景知识,请阅读本系列博客。

Exception Handling for Async Requests

如果从控制器方法返回的可调用对象在执行时引发异常,会发生什么情况?简短的回答与控制器方法引发异常时发生的情况相同。它通过常规的异常处理机制。更详细的解释是,当一个可调用程序引发异常时,Spring MVC会将异常发送到Servlet容器,从而导致使用异常恢复请求处理,而不是控制器方法返回值。When using a DeferredResult you have a choice whether to call setResult or setErrorResult with an Exception instance.

Intercepting Async Requests

HandlerInterceptor还可以实现AsyncHandlerInterceptor来实现afterConcurrentHandlingStarted回调,当异步处理开始时,调用这个回调而不是postHandle和afterCompletion。

HandlerInterceptor还可以注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor,以便更深入地与异步请求的生命周期集成,例如处理超时事件。有关详细信息,请参见AsyncHandlerInterceptor的Javadoc。

DeferredResult类型还提供onTimeout(Runnable)和onCompletion(Runnable)等方法。有关详细信息,请参见DeferredResult的Javadoc。

在使用Callable时,可以用WebAsyncTask的实例包装它,该实例还提供超时和完成的注册方法。

HTTP Streaming

controller 方法可以使用DeferredResult和Callable异步生成其返回值,还可以使用它来实现长轮询等技术,其中服务器可以尽快将事件推送到客户机。

如果您想在一个HTTP响应上推送多个事件,该怎么办?这是一种与“长轮询”相关的技术,称为“HTTP流”。Spring MVC通过ResponseBodyEmitter返回值类型实现了这一点,该返回值类型可用于发送多个对象,而不是像@ResponseBody通常的情况那样,在@ResponseBody中,发送的每个对象都用HttpMessageConverter写入响应。

下面是一个例子:

@RequestMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

注意,ResponseBodyEmitter也可以用作ResponseEntity中的主体,以定制响应的状态和头部。

HTTP Streaming With Server-Sent Events

SseEmitter是ResponseBodyEmitter的一个子类,它为服务器发送的事件提供支持。服务器发送的事件只是相同的“HTTP流”技术的另一种变体,只不过从服务器推送的事件是根据W3C服务器发送的事件规范格式化的。

服务器发送的事件可以用于它们预期的目的,即将事件从服务器推送到客户机。这在Spring MVC中非常容易实现,只需返回SseEmitter类型的值即可。

但是请注意,Internet Explorer不支持服务器发送的事件,对于更高级的web应用程序消息传递场景,如在线游戏、协作、财务applicatinos,另外,最好考虑Spring的WebSocket支持,其中包括sockjs风格的WebSocket模拟,这种模拟可以追溯到非常广泛的浏览器(包括Internet Explorer),还包括用于通过更以消息为中心的体系结构中的发布-订阅模型与客户端交互的更高级的消息传递模式。有关这方面的更多背景信息,请参见下面的博客文章。

HTTP Streaming Directly To The OutputStream

ResponseBodyEmitter允许通过HttpMessageConverter向响应写入对象来发送事件。这可能是最常见的情况,例如在编写JSON数据时。然而,有时绕过消息转换直接写入响应OutputStream(例如下载文件)是有用的。这可以通过StreamingResponseBody返回值类型来实现。

下面是一个例子:

@RequestMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

请注意,StreamingResponseBody还可以用作ResponseEntity中的主体,以定制响应的状态和头部。

Configuring Asynchronous Request Processing

Servlet Container Configuration

用于配置了web的应用程序。请务必将xml更新到3.0版本:



    ...

必须通过web.xml中的true子元素在DispatcherServlet上启用异步支持。此外,必须将参与asyncrequest处理的任何筛选器配置为支持异步分派器类型。为Spring框架提供的所有筛选器启用异步分派器类型应该是安全的,因为它们通常扩展OncePerRequestFilter,并在运行时检查筛选器是否需要参与异步分派。

下面是一些web示例。xml配置:



    
        Spring OpenEntityManagerInViewFilter
        org.springframework.~.OpenEntityManagerInViewFilter
        true
    

    
        Spring OpenEntityManagerInViewFilter
        /*
        REQUEST
        ASYNC
    

如果使用Servlet 3(例如通过WebApplicationInitializer进行基于Java的配置),您还需要设置“asyncSupported”标志和ASYNC dispatcher类型,就像使用web.xml一样。为了简化所有这些配置,可以考虑扩展AbstractDispatcherServletInitializer,或者更好的AbstractAnnotationConfigDispatcherServletInitializer,它自动设置这些选项,使注册过滤器实例变得非常容易。

Spring MVC Configuration

MVC Java配置和MVC命名空间提供了配置异步请求处理的选项。WebMvcConfigurer有configureAsyncSupport方法,而有一个子元素。

它们允许您配置异步请求使用的默认超时值,如果没有设置该超时值,则取决于底层Servlet容器(例如Tomcat上的10秒)。您还可以配置AsyncTaskExecutor来执行从控制器方法返回的可调用实例。强烈建议配置此属性,因为默认情况下Spring MVC使用SimpleAsyncTaskExecutor。MVC Java配置和MVC命名空间还允许您注册CallableProcessingInterceptor和DeferredResultProcessingInterceptor实例。

如果需要覆盖特定DeferredResult的默认超时值,可以通过使用适当的类构造函数来实现。类似地,对于可调用的,可以将其包装在WebAsyncTask中,并使用适当的类构造函数自定义超时值。WebAsyncTask的类构造函数还允许提供AsyncTaskExecutor。

22.3.5 Testing Controllers

spring-test模块为测试带注释的控制器提供了一流的支持。参见第15.6节“Spring MVC测试框架”。

22.4 Handler mappings

在Spring的早期版本中,用户需要在web应用程序上下文中定义一个或多个HandlerMapping bean,以便将传入的web请求映射到适当的处理程序。通过引入带注释的控制器,通常不需要这样做,因为RequestMappingHandlerMapping会自动在所有@Controller bean上查找@RequestMapping注释。但是,请记住,从AbstractHandlerMapping扩展而来的所有HandlerMapping类都具有以下属性,您可以使用这些属性来定制它们的行为:

  • interceptors 要使用的拦截器的列表。HandlerInterceptor在22.4.1节“使用HandlerInterceptor拦截请求”中进行了讨论。
  • defaultHandler 当此处理程序映射未产生匹配处理程序时,使用的默认处理程序。
  • order 基于order属性的值(参见org.springframework.core.Ordered 接口), Spring对上下文中可用的所有处理程序映射进行排序,并应用第一个匹配的处理程序。
  • alwaysUseFullPath  如果为真,Spring将使用当前Servlet上下文中的完整路径来查找适当的处理程序。如果为false(默认值),则使用当前Servlet映射中的路径。例如,如果Servlet使用/testing/*进行映射,并且alwaysUseFullPath属性设置为true, 使用/testing/viewPage.html,而如果属性设置为false,则使用/viewPage.html。
  • urlDecode 默认为true,从Spring 2.5开始。如果您喜欢比较编码的路径,请将此标志设置为false。然而,HttpServletRequest总是以解码的形式公开Servlet路径。请注意,与编码的路径相比,Servlet路径将不匹配。

 下面的例子展示了如何配置拦截器:


    
        
            
        
    

22.4.1 Intercepting requests with a HandlerInterceptor

Spring的处理程序映射机制包括处理程序拦截器,当您想将特定的功能应用于特定的请求(例如,检查主体)时,这些拦截器非常有用。

位于处理程序映射中的拦截器必须实现HandlerInterceptor从org.springframe.web.servlet包。该接口定义了三个方法:在实际处理程序执行之前调用preHandle(..);在处理程序执行后调用postHandle(..);在完成请求之后调用afterCompletion(..)。这三种方法应该提供足够的灵活性来进行各种预处理和后处理。

方法的作用是:返回一个布尔值。可以使用此方法中断或继续执行链的处理。当该方法返回true时,处理程序执行链将继续;当DispatcherServlet返回false时,它假定拦截器本身已经处理了请求(例如,呈现了一个适当的视图),并且没有继续执行执行链中的其他拦截器和实际处理程序。

可以使用Interceptors属性配置拦截器,拦截器属性出现在AbstractHandlerMapping扩展的所有HandlerMapping类上。如下例所示:


    
        
            
                
            
        
    

    
        
        
    
package samples;

public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter {

    private int openingTime;
    private int closingTime;

    public void setOpeningTime(int openingTime) {
        this.openingTime = openingTime;
    }

    public void setClosingTime(int closingTime) {
        this.closingTime = closingTime;
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        Calendar cal = Calendar.getInstance();
        int hour = cal.get(HOUR_OF_DAY);
        if (openingTime <= hour && hour < closingTime) {
            return true;
        }
        response.sendRedirect("http://host.com/outsideOfficeHours.html");
        return false;
    }
}

此映射处理的任何请求都将被TimeBasedAccessInterceptor拦截。如果当前时间不在办公时间内,用户将被重定向到静态HTML文件,该文件表示,例如,您只能在办公时间内访问网站。

当使用RequestMappingHandlerMapping时,实际的处理程序是HandlerMethod的一个实例,它标识将要调用的特定控制器方法。

如您所见,Spring adapter类HandlerInterceptorAdapter使扩展HandlerInterceptor接口变得更加容易。

在上面的示例中,配置的拦截器将应用于使用带注释的控制器方法处理的所有请求。如果您想缩小拦截器所应用的URL路径,您可以使用MVC名称空间或MVC Java配置,或者声明MappedInterceptor类型的bean实例来实现这一点。参见22.16.1节,“启用MVC Java配置或MVC XML命名空间”。

注意,HandlerInterceptor的postHandle方法并不总是最适合与@ResponseBody和ResponseEntity方法一起使用。在这种情况下,HttpMessageConverter在postHandle被调用之前写入并提交响应,这使得不可能更改响应,例如添加头部。相反,应用程序可以实现ResponseBodyAdvice,并将其声明为@ControllerAdvice bean,或者直接在RequestMappingHandlerAdapter上配置它。

22.5 Resolving views

所有用于web应用程序的MVC框架都提供了一种处理视图的方法。Spring提供了视图解析器,它使您能够在浏览器中呈现模型,而不需要绑定到特定的视图技术。例如,Spring允许您使用jsp、Velocity模板和XSLT视图。有关如何集成和使用许多不同的视图技术的讨论,请参见第23章视图技术。

对于Spring处理视图的方式很重要的两个接口是ViewResolver和View。视图解析器提供了视图名称和实际视图之间的映射。视图接口处理请求的准备,并将请求交给视图技术之一。

22.5.1 Resolving views with the ViewResolver interface

正如在第22.3节“实现控制器”中所讨论的,Spring Web MVC控制器中的所有处理程序方法必须解析为一个逻辑视图名,要么显式解析(例如,通过返回字符串、视图或ModelAndView),要么隐式解析(例如,通过返回一个字符串、视图或ModelAndView)。)。Spring中的视图由逻辑视图名寻址,并由视图解析器解析。Spring提供了相当多的视图解析器。这张表列出了其中的大部分;下面是几个例子。

Table 22.3. View resolvers

ViewResolver Description

AbstractCachingViewResolver

缓存视图的抽象视图解析器。视图通常在使用之前需要准备;扩展此视图解析器提供缓存。

XmlViewResolver

ViewResolver的实现,它接受用XML编写的配置文件,其DTD与Spring的XML bean工厂相同。默认配置文件是/WEB-INF/views.xml。

ResourceBundleViewResolver

ViewResolver的实现,它在ResourceBundle中使用bean定义,由bundle基名指定。通常在类路径中的属性文件中定义包。默认文件名是views.properties。

UrlBasedViewResolver

ViewResolver接口的简单实现,它直接将逻辑视图名解析为url,而不需要显式映射定义。如果逻辑名称以一种简单的方式匹配视图资源的名称,而不需要任意映射,那么这种方法是合适的。

InternalResourceViewResolver

UrlBasedViewResolver的方便子类,它支持InternalResourceView(实际上是servlet和jsp)和JstlView和TilesView等子类。您可以使用setViewClass(..)为这个解析器生成的所有视图指定视图类。有关详细信息,请参见UrlBasedViewResolver javadoc。

VelocityViewResolver / FreeMarkerViewResolver

分别支持VelocityView(实际上是Velocity模板)或FreeMarkerView的UrlBasedViewResolver的方便子类,以及它们的自定义子类。

ContentNegotiatingViewResolver

ViewResolver接口的实现,该接口基于请求文件名或Accept头解析视图。参见22.5.4节“contentnegotiation atingviewresolver”。

例如,使用JSP作为视图技术,您可以使用UrlBasedViewResolver。此视图解析器将视图名称转换为URL,并将请求交给RequestDispatcher来呈现视图。


    
    
    

当以逻辑视图名称返回test时,此视图解析器将请求转发给RequestDispatcher,后者将请求发送到/WEB-INF/jsp/test.jsp。

当您在web应用程序中组合不同的视图技术时,您可以使用ResourceBundleViewResolver:


    
    

ResourceBundleViewResolver检查由basename标识的ResourceBundle,对于它要解析的每个视图,它使用属性[viewname].(class)的值作为视图类和属性[viewname]的值。url作为视图url。示例可以在下一章中找到,该章将介绍视图技术。如您所见,您可以标识一个父视图,属性文件中的所有视图都从该父视图“扩展”。例如,通过这种方式可以指定默认视图类。

它们解析的AbstractCachingViewResolver的子类缓存视图实例。缓存提高了某些视图技术的性能。可以通过将缓存属性设置为false来关闭缓存。此外,如果必须在运行时刷新某个视图(例如修改Velocity模板时),可以使用removeFromCache(String viewName, Locale loc)方法。

22.5.2 Chaining ViewResolvers

Spring支持多个视图解析器。因此,您可以链接解析器,例如,在某些情况下覆盖特定的视图。通过向应用程序上下文中添加多个解析器,以及在必要时通过设置order属性来指定order,来链接视图解析器。记住,阶属性越高,视图解析器在链中的位置就越晚。

在下面的示例中,视图解析器链由两个解析器组成,一个是InternalResourceViewResolver,它总是自动定位为链中的最后一个解析器,另一个是XmlViewResolver,用于指定Excel视图。Excel视图不受InternalResourceViewResolver的支持。


    
    
    



    
    





    

如果某个特定的视图解析器没有生成视图,Spring将检查其他视图解析器的上下文。如果存在其他视图解析器,Spring将继续检查它们,直到一个视图被解析。如果没有视图解析器返回视图,Spring将抛出ServletException。

视图解析器的契约指定视图解析器可以返回null,以指示找不到视图。然而,并非所有的视图解析器都这样做,因为在某些情况下,解析器根本无法检测视图是否存在。例如,InternalResourceViewResolver在内部使用RequestDispatcher,而分派是判断JSP是否存在的唯一方法,但是此操作只能执行一次。同样的道理也适用于VelocityViewResolver和其他一些应用程序。检查特定视图解析器的javadoc,看看它是否报告不存在的视图。因此,将一个InternalResourceViewResolver放在链中最后一个结果之外的位置会导致链没有被完全检查,因为InternalResourceViewResolver总是返回一个视图!

22.5.3 Redirecting to Views

如前所述,控制器通常返回逻辑视图名,视图解析器将逻辑视图名解析为特定的视图技术。对于通过Servlet或JSP引擎处理的JSP等视图技术,这种解析通常通过InternalResourceViewResolver和InternalResourceView的组合来处理,后者通过Servlet API的RequestDispatcher.forward(..)方法或RequestDispatcher.include()方法发出内部转发或包含。对于其他视图技术,如Velocity、XSLT等,视图本身直接将内容写入响应流。

在呈现视图之前,有时需要向客户机发出HTTP重定向。这是可取的,例如,当一个控制器被POST数据调用时,响应实际上是另一个控制器的委托(例如成功提交表单时)。在这种情况下,一个正常的内部转发意味着另一个控制器也将看到相同的POST数据,如果它可能与其他预期数据混淆,这可能是有问题的。在显示结果之前执行重定向的另一个原因是消除用户多次提交表单数据的可能性。在这个场景中,浏览器将首先发送一个初始POST;然后它将收到一个响应,以重定向到另一个URL;最后,浏览器将对重定向响应中指定的URL执行后续GET。因此,从浏览器的角度来看,当前页面并不反映POST的结果,而是反映GET的结果。最终的结果是,用户不可能通过执行刷新意外地重新发布相同的数据。刷新强制获取结果页,而不是重新发送初始POST数据。

RedirectView

在控制器响应的结果中强制重定向的一种方法是,控制器创建并返回Spring的RedirectView实例。在这种情况下,DispatcherServlet不使用常规的视图解析机制。更确切地说,因为已经给了它(重定向)视图,DispatcherServlet只是指示视图完成它的工作。RedirectView反过来调用HttpServletResponse.sendRedirect()将HTTP重定向发送到客户机浏览器。

如果您使用RedirectView,而视图是由控制器本身创建的,建议您将重定向URL配置为注入到控制器中,这样它就不会被烘烤到控制器中,而是与视图名称一起在上下文中配置。名为“重定向:前缀”的部分有助于这种解耦。

Passing Data To the Redirect Target

默认情况下,所有模型属性都被认为是在重定向URL中作为URI模板变量公开的。在其余的属性中,那些基元类型或基元类型的集合/数组将自动作为查询参数追加。

如果模型实例是专门为重定向准备的,那么添加原始类型属性作为查询参数可能是理想的结果。然而,在带注释的控制器中,模型可能包含为呈现目的而添加的额外属性(例如下拉字段值)。为了避免在URL中出现此类属性的可能性,@RequestMapping方法可以声明RedirectAttributes类型的参数,并使用它指定RedirectView可用的确切属性。如果方法确实重定向,则使用RedirectAttributes的内容。否则将使用模型的内容。

RequestMappingHandlerAdapter提供了一个名为“ignoreDefaultModelOnRedirect”的标志,如果控制器方法重定向,该标志可用于指示不应该使用默认模型的内容。相反,控制器方法应该声明RedirectAttributes类型的属性,或者如果它不这样做,就不应该将属性传递给RedirectView。MVC命名空间和MVC Java配置都将此标志设置为false,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为true

注意,当前请求中的URI模板变量在展开重定向URL时自动可用,不需要通过模型或RedirectAttributes显式地添加。例如:

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

另一种向重定向目标传递数据的方法是通过Flash属性。与其他重定向属性不同,flash属性保存在HTTP会话中(因此不会出现在URL中)。有关更多信息,请参见第22.6节“使用flash属性”。

The redirect: prefix

虽然使用RedirectView工作得很好,但是如果控制器本身创建了RedirectView,则无法避免这样一个事实,即控制器知道正在发生重定向。这确实不是最优的,而且把事情联系得太紧密了。控制器不应该真正关心如何处理响应。一般来说,它应该只根据注入它的视图名来操作。

特殊的重定向:前缀允许您完成此任务。如果返回一个带有前缀redirect:的视图名,UrlBasedViewResolver(和所有子类)会将其识别为需要重定向的特殊指示。视图名称的其余部分将被视为重定向URL。

净效果与控制器返回RedirectView相同,但是现在控制器本身可以根据逻辑视图名进行操作。逻辑视图名(如redirect:/myapp/some/resource)将相对于当前Servlet上下文重定向,而名称(如redirect: http://myhost.com/some/武断y/path)将重定向到绝对URL。

注意,控制器处理程序使用@ResponseStatus注释,注释值优先于RedirectView设置的响应状态。

The forward: prefix

对于最终由UrlBasedViewResolver和子类解析的视图名,也可以使用特殊的forward:前缀。这将围绕视图名称的其余部分创建一个InternalResourceView(它最终执行RequestDispatcher.forward()),视图名称被认为是一个URL。因此,这个前缀对于InternalResourceViewResolver和InternalResourceView(例如对于jsp)是没有用的。但是,当您主要使用另一种视图技术,但仍然希望Servlet/JSP引擎处理资源的转发时,前缀可能会有所帮助。(请注意,您也可以链接多个视图解析器。)

与重定向:前缀一样,如果带有forward:前缀的视图名被注入到控制器中,控制器不会检测到在处理响应方面发生了什么特殊的事情。

22.5.4 ContentNegotiatingViewResolver

 

22.6 Using flash attributes

 

22.7 Building URIs

Spring MVC提供了一种使用UriComponentsBuilder和UriComponents构建和编码URI的机制。

例如,您可以展开和编码URI模板字符串:

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}").build();

URI uri = uriComponents.expand("42", "21").encode().toUri();

注意,UriComponents 是不可变的,如果需要,expand()和encode()操作将返回新的实例。

 您还可以使用单个URI组件展开和编码:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

在Servlet环境中,ServletUriComponentsBuilder子类提供静态工厂方法来从Servlet请求中复制可用的URL信息:

HttpServletRequest request = ...

// Re-use host, scheme, port, path and query string
// Replace the "accountId" query param

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

或者,您可以选择将可用信息的子集复制到并包括上下文路径:

// Re-use host, port and context path
// Append "/accounts" to the path

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()

或者在DispatcherServlet按名称映射的情况下(例如/main/*),您也可以包含servlet映射的文字部分:

// Re-use host, port, context path
// Append the literal part of the servlet mapping to the path
// Append "/accounts" to the path

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()

22.7.1 Building URIs to Controllers and methods

Spring MVC提供了一种机制来准备到控制器方法的链接。例如,下面的MVC控制器很容易创建链接:

@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

    @GetMapping("/bookings/{booking}")
    public ModelAndView getBooking(@PathVariable Long booking) {
        // ...
    }
}

你可以准备一个链接,通过引用方法的名称:

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

在上面的例子中,我们提供了实际的方法参数值,在本例中是long值21,它被用作路径变量并插入到URL中。此外,我们提供了值42,以填充任何剩余的URI变量,例如从类型级请求映射继承的“hotel”变量。如果方法有更多的参数,您可以为URL不需要的参数提供null。通常只有@PathVariable和@RequestParam参数与构造URL相关。

使用MvcUriComponentsBuilder还有其他方法。例如,您可以使用类似于通过代理进行模拟测试的技术来避免按名称引用控制器方法(该示例假设静态导入MvcUriComponentsBuilder.on):

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

控制器方法签名在其设计中受到限制,当它们被认为可以用于使用fromMethodCall创建链接时。除了需要正确的参数签名之外,返回类型还有一个技术限制:即为链接生成器调用生成运行时代理,因此返回类型不能是final。特别是,视图名的公共字符串返回类型在这里不起作用;使用ModelAndView甚至普通对象(带有字符串返回值)代替。

上面的例子使用了MvcUriComponentsBuilder中的静态方法。在内部,它们依赖于ServletUriComponentsBuilder从当前请求的模式、主机、端口、上下文路径和servlet路径准备一个基本URL。这种方法在大多数情况下都很有效,但有时可能不够。例如,您可能在请求的上下文之外(例如,准备链接的批处理进程),或者您可能需要插入路径前缀(例如,从请求路径中删除并需要重新插入链接的区域设置前缀)。

在这种情况下,您可以使用静态的“fromXxx”重载方法来接受UriComponentsBuilder来使用基本URL。或者您可以使用基本URL创建MvcUriComponentsBuilder的实例,然后使用基于实例的“withXxx”方法。例如:

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

22.7.2 Working with "Forwarded" and "X-Forwarded-*" Headers

当请求通过负载平衡器等代理时,主机、端口和模式可能会发生变化,这对需要创建到资源的链接的应用程序来说是一个挑战,因为从客户端角度看,链接应该反映原始请求的主机、端口和模式。

RFC 7239定义了代理用来提供关于原始请求的信息的“转发的”HTTP头。还使用了其他非标准头文件,如“x - forwarding - host”、“x - forwarding - port”和“x - forwarding - proto”。

ServletUriComponentsBuilder和MvcUriComponentsBuilder都可以检测、提取和使用来自“转发”报头的信息,或者来自“x - forwarding - host”、“x - forwarding - port”和“x - forwarding - proto”(如果“转发”不存在)的信息,那么得到的链接将反映原始请求。

ForwardedHeaderFilter为整个应用程序提供了一种一次性全局执行相同操作的替代方法。过滤器包装请求,以覆盖主机、端口和方案信息,并“隐藏”任何转发的头以供后续处理。

请注意,在使用RFC 7239第8节中解释的转发头时,存在安全考虑。在应用程序级别,很难确定转发的头是否可信。这就是为什么应该正确地配置网络上游,以便从外部过滤不受信任的转发头。

没有代理且不需要使用转发头的应用程序可以配置ForwardedHeaderFilter来删除和忽略这些头。

22.7.3 Building URIs to Controllers and methods from views

您还可以从JSP、Thymeleaf、FreeMarker等视图构建到带注释控制器的链接。这可以使用MvcUriComponentsBuilder中的fromMappingName方法来实现,该方法通过名称引用映射。

每个@RequestMapping都根据类的大写字母和完整方法名分配一个默认名称。例如,类FooController中的getFoo方法的名称是“FC#getFoo”。可以通过创建HandlerMethodMappingNamingStrategy的实例并将其插入RequestMappingHandlerMapping来替换或定制该策略。默认的策略实现还会查看@RequestMapping上的name属性,并在出现时使用该属性。这意味着如果分配的默认映射名称与另一个(例如重载方法)冲突,您可以在@RequestMapping上显式地分配名称。

所分配的请求映射名在启动时在跟踪级别记录。

Spring JSP标记库提供了一个名为mvcUrl的函数,可以使用该函数准备到基于此机制的控制器方法的链接。

例如给定:

@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

    @RequestMapping("/{country}")
    public HttpEntity getAddress(@PathVariable String country) { ... }
}

您可以从JSP准备以下链接:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
Get Address

上面的示例依赖于Spring标记库中声明的mvcUrl JSP函数(即META-INF/ Spring .tld)。对于更高级的情况(如前一节中解释的自定义基URL),很容易定义自己的函数或使用自定义标记文件,以便使用具有自定义基URL的MvcUriComponentsBuilder的特定实例。

22.8 Using locales

Spring架构的大多数部分都支持国际化,就像Spring web MVC框架所做的那样。DispatcherServlet使您能够使用客户机的地区自动解析消息。这是用LocaleResolver对象完成的。

当一个请求进来时,DispatcherServlet会寻找一个区域设置解析器,如果找到,它会尝试使用它来设置区域设置。使用RequestContext.getLocale()方法,您总是可以检索由地区解析器解析的地区。

除了自动区域设置解析之外,您还可以将一个拦截器附加到处理程序映射(有关处理程序映射拦截器的更多信息,请参见22.4.1节“使用HandlerInterceptor拦截请求”),以在特定情况下更改区域设置,例如,基于请求中的参数。

区域设置解析器和拦截器在org.springframework.web.servlet.i18n包,并在应用程序上下文中以正常方式配置。下面是Spring中包含的区域设置解析器的一个选择。

22.8.1 Obtaining Time Zone Information

除了获取客户机的语言环境之外,了解它们的时区通常也很有用。LocaleContextResolver接口提供了LocaleResolver的扩展,允许解析器提供更丰富的LocaleContext,其中可能包括时区信息。

当可用时,可以使用RequestContext.getTimeZone()方法获得用户的时区。在Spring的ConversionService中注册的日期/时间转换器和格式化程序对象将自动使用时区信息。

22.8.2 AcceptHeaderLocaleResolver

此区域设置解析器检查客户端(例如web浏览器)发送的请求中的accept-language报头。通常,这个头字段包含客户机操作系统的区域设置。请注意,此解析器不支持时区信息。

22.8.3 CookieLocaleResolver

此区域设置解析器检查客户端上可能存在的Cookie,以查看是否指定了区域设置或时区。如果是,则使用指定的详细信息。使用此区域设置解析器的属性,可以指定cookie的名称和最大年龄。下面是一个定义CookieLocaleResolver的例子。



    

    
    

Table 22.4. CookieLocaleResolver properties

Property Default Description

cookieName

classname + LOCALE

The name of the cookie

cookieMaxAge

Servlet container default

cookie在客户机上保持持久性的最长时间。如果指定-1,cookie将不会被持久化;它只在客户端关闭浏览器之前可用。

cookiePath

/

将cookie的可见性限制在站点的某个部分。当指定cookiePath时,cookie只对该路径及其下面的路径可见。

 

22.8.4 SessionLocaleResolver

SessionLocaleResolver允许您从可能与用户请求关联的会话中检索地区和时区。与CookieLocaleResolver不同,此策略将本地选择的区域设置存储在Servlet容器的HttpSession中。因此,这些设置只是每个会话的临时设置,因此在每个会话结束时将丢失。

请注意,与外部会话管理机制(如Spring会话项目)没有直接关系。这个SessionLocaleResolver将根据当前HttpServletRequest简单地计算和修改相应的HttpSession属性。

22.8.5 LocaleChangeInterceptor

您可以通过将LocaleChangeInterceptor添加到一个处理程序映射(参见第22.4节,“处理程序映射”)来启用区域设置的更改。它将检测请求中的参数并更改语言环境。它在上下文中也存在的LocaleResolver上调用setLocale()。下面的示例显示了对all *的调用。现在,包含名为siteLanguage参数的视图资源将更改语言环境。例如,请求以下URL http://www.sf.net/home.view?siteLanguage=nl将把站点语言改为荷兰语。


    





    
        
            
        
    
    
        /**/*.view=someController
    

22.9 Using themes

 

 

22.10 Spring’s multipart (file upload) support

22.10.1 Introduction

Spring内置的多部分支持处理web应用程序中的文件上传。您可以使用org.springframework.web.multipart中定义的可插入的MultipartResolver对象来启用这种多部分支持。Spring为Commons FileUpload和Servlet 3.0多部分请求解析提供了一个多部分解决程序实现。

默认情况下,Spring不进行多部分处理,因为一些开发人员希望自己处理多部分。通过向web应用程序的上下文中添加多部分解析器,可以启用Spring多部分处理。检查每个请求,看它是否包含多个部分。如果没有找到多部分,则请求按预期继续。如果在请求中找到多部分,则使用在上下文中声明的多部分解决程序。在此之后,请求中的multipart属性将像其他属性一样被处理。

22.10.2 Using a MultipartResolver with Commons FileUpload

下面的例子展示了如何使用CommonsMultipartResolver:



    
    

当然,您还需要将适当的jar放到类路径中,以便多部分解析器正常工作。对于CommonsMultipartResolver,您需要使用commons-fileupload.jar。

当Spring DispatcherServlet检测到一个多部分请求时,它将激活在上下文中声明的解析器并提交请求。然后,解析器将当前HttpServletRequest封装到支持多部分文件上传的MultipartHttpServletRequest中。使用MultipartHttpServletRequest,您可以获得关于此请求所包含的多部分的信息,并实际访问控制器中的多部分文件本身。

22.10.3 Using a MultipartResolver with Servlet 3.0

为了使用基于Servlet 3.0的多部分解析,您需要在DispatcherServlet上标记web中的“multipart-config”部分,或者使用编程Servlet注册中的javax.servlet.MultipartConfigElement,或者自定义Servlet类,可能带有javax.servlet.annotation.Servlet类上的MultipartConfig注释。需要在Servlet注册级别应用诸如最大大小或存储位置等配置设置,因为Servlet 3.0不允许从MultipartResolver进行这些设置。

一旦Servlet 3.0多部分解析以上述方式之一启用,您就可以将StandardServletMultipartResolver添加到Spring配置中:


22.10.4 Handling a file upload in a form

在MultipartResolver完成它的工作之后,将像处理其他任何请求一样处理请求。首先,使用允许用户上传表单的文件输入创建一个表单。编码属性(enctype="multipart/form-data")让浏览器知道如何将表单编码为multipart请求:


    
        Upload a file please
    
    
        

Please upload a file

下一步是创建一个处理文件上传的控制器。这个控制器非常类似于一个普通的带注释的@Controller,除了我们在方法参数中使用MultipartHttpServletRequest或MultipartFile:

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

注意@RequestParam方法参数如何映射到表单中声明的输入元素。在本例中,字节[]不做任何处理,但实际上可以将其保存在数据库中,存储在文件系统中,等等。

在使用Servlet 3.0多部分解析时,还可以使用javax.servlet.http.Part方法参数:

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

22.10.5 Handling a file upload request from programmatic clients

在RESTful服务场景中,还可以从非浏览器客户机提交多部分请求。上面的所有示例和配置也适用于此。然而,与通常提交文件和简单表单字段的浏览器不同,编程客户端还可以发送特定内容类型的更复杂的数据——例如带有文件的多部分请求和带有JSON格式数据的第二部分:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
	"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

您可以使用@RequestParam(“meta-data”)字符串元数据控制器方法参数访问名为“元数据”的部分。然而,您可能更愿意接受从请求部分主体中的JSON格式数据初始化的强类型对象,这与@RequestBody在HttpMessageConverter的帮助下将非多部分请求的主体转换为目标对象的方式非常相似。

为此,可以使用@RequestPart注释而不是@RequestParam注释。它允许您通过HttpMessageConverter传递特定多部分的内容,同时考虑到多部分的“内容类型”标头:

@PostMapping("/someUrl")
public String onSubmit(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {

    // ...

}

注意,可以通过@RequestParam或@RequestPart交替访问多部分文件方法参数。然而,在本例中,@RequestPart(“meta-data”)MetaData 方法参数基于其“内容类型”头被读取为JSON内容,并在MappingJackson2HttpMessageConverter的帮助下进行转换。

22.11 Handling exceptions

22.11.1 HandlerExceptionResolver

Spring HandlerExceptionResolver实现处理在控制器执行期间发生的意外异常。HandlerExceptionResolver有点类似于您可以在web应用程序描述符web.xml中定义的异常映射。但是,它们提供了一种更灵活的方法。例如,它们提供关于抛出异常时哪个处理程序正在执行的信息。此外,以编程方式处理异常为您提供了更多选项,可以在请求转发到另一个URL之前适当地进行响应(与使用Servlet特定异常映射时的最终结果相同)。

除了实现HandlerExceptionResolver接口(这只是实现resolveException(Exception, Handler)方法和返回ModelAndView的问题)之外,您还可以使用提供的SimpleMappingExceptionResolver或创建@ExceptionHandler方法。SimpleMappingExceptionResolver使您能够获取可能抛出的任何异常的类名,并将其映射到视图名。这在功能上等价于Servlet API的异常映射特性,但是也可以实现来自不同处理程序的更细粒度的异常映射。

另一方面,@ExceptionHandler注释可以用于应该调用来处理异常的方法。这些方法可以在@Controller中本地定义,也可以在@ControllerAdvice类中定义时应用于许多@Controller类。下面几节将对此进行更详细的解释。

22.11.2 @ExceptionHandler

HandlerExceptionResolver接口和SimpleMappingExceptionResolver实现允许您在转发到这些视图之前,以声明的方式将异常映射到特定的视图以及一些可选的Java逻辑。然而,在某些情况下,特别是依赖于@ResponseBody方法而不是视图解析时,直接设置响应的状态并选择性地将错误内容写入响应的主体可能更方便。

您可以使用@ExceptionHandler方法来实现这一点。当在控制器中声明时,这些方法适用于由该控制器(或其任何子类)的@RequestMapping方法引发的异常。

您还可以在@ControllerAdvice类中声明@ExceptionHandler方法,在这种情况下,它将处理来自许多控制器的@RequestMapping方法的异常。下面是controller-local @ExceptionHandler方法的示例:

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity handle(IOException ex) {
        // ...
    }
}

异常可以匹配正在传播的顶级异常(即直接抛出的IOException),也可以匹配顶级包装器异常中的直接原因(例如,在IllegalStateException中包装的IOException)。

对于匹配异常类型,最好将目标异常声明为方法参数,如上所示。当多个异常方法匹配时,根异常匹配通常优先于原因异常匹配。更具体地说,ExceptionDepthComparator用于根据抛出的异常类型的深度对异常进行排序。

或者,@ExceptionHandler值可以设置为异常类型数组。如果抛出的异常与列表中的一种类型匹配,那么将调用用匹配的@ExceptionHandler注释的方法。如果未设置注释值,则将使用声明的方法参数类型进行匹配。

对于@ExceptionHandler方法,根异常匹配比仅在特定控制器或通知bean的处理程序方法中匹配当前异常的原因更可取。但是,与低优先级通知bean上的任何匹配(无论是根级别还是原因级别)相比,@ControllerAdvice上的原因匹配仍然是首选的。因此,在使用多通知安排时,请在具有相应顺序的优先级通知bean上声明您的主根异常映射!

与使用@RequestMapping注释注释的标准控制器方法非常相似,@ExceptionHandler方法的方法参数和返回值可以是灵活的。例如,HttpServletRequest可以在Servlet环境中访问,PortletRequest可以在Portlet环境中访问。返回类型可以是一个字符串,它被解释为视图名、ModelAndView对象、ResponseEntity,或者您也可以添加@ResponseBody来让方法返回值通过消息转换器转换并写入响应流。

最后但并非最不重要的是,@ExceptionHandler方法实现可以选择通过重新抛出给定异常实例的原始形式来退出对它的处理。如果您只对根级别的匹配感兴趣,或者对不能静态确定的特定上下文中的匹配感兴趣,那么这种方法非常有用。重新抛出的异常将通过其余的解析链传播,就像如果给定的@ExceptionHandler方法一开始就不匹配一样。

22.11.3 Handling Standard Spring MVC Exceptions

Spring MVC在处理请求时可能会引发许多异常。SimpleMappingExceptionResolver可以根据需要轻松地将任何异常映射到默认错误视图。然而,当使用以自动化方式解释响应的客户机时,您将希望在响应上设置特定的状态代码。根据引发的异常,状态代码可能指示客户端错误(4xx)或服务器错误(5xx)。

DefaultHandlerExceptionResolver将Spring MVC异常转换为特定的错误状态代码。默认情况下,它由MVC名称空间、MVC Java配置以及DispatcherServlet(即不使用MVC名称空间或Java配置时)注册。以下是本解析器处理的一些异常和相应的状态码:

Exception HTTP Status Code

BindException

400 (Bad Request)

ConversionNotSupportedException

500 (Internal Server Error)

HttpMediaTypeNotAcceptableException

406 (Not Acceptable)

HttpMediaTypeNotSupportedException

415 (Unsupported Media Type)

HttpMessageNotReadableException

400 (Bad Request)

HttpMessageNotWritableException

500 (Internal Server Error)

HttpRequestMethodNotSupportedException

405 (Method Not Allowed)

MethodArgumentNotValidException

400 (Bad Request)

MissingPathVariableException

500 (Internal Server Error)

MissingServletRequestParameterException

400 (Bad Request)

MissingServletRequestPartException

400 (Bad Request)

NoHandlerFoundException

404 (Not Found)

NoSuchRequestHandlingMethodException

404 (Not Found)

TypeMismatchException

400 (Bad Request)

DefaultHandlerExceptionResolver通过设置响应的状态透明地工作。然而,当您的应用程序可能需要为每个错误响应添加开发人员友好的内容时(例如在提供REST API时),它不会将任何错误内容写入响应体中。您可以准备一个模型和视图,并通过视图解析呈现错误内容。通过配置ContentNegotiatingViewResolver、MappingJackson2JsonView等等。但是,您可能更喜欢使用@ExceptionHandler方法。

如果您喜欢通过@ExceptionHandler方法编写错误内容,您可以扩展ResponseEntityExceptionHandler。这是@ControllerAdvice类的一个方便的基础,它提供了一个@ExceptionHandler方法来处理标准Spring MVC异常并返回ResponseEntity。这允许您自定义响应并使用消息转换器编写错误内容。有关详细信息,请参见ResponseEntityExceptionHandler javadoc。

22.11.4 Annotating Business Exceptions With @ResponseStatus

业务异常可以用@ResponseStatus进行注释。当异常被触发时,ResponseStatusExceptionResolver通过相应地设置响应的状态来处理它。默认情况下,DispatcherServlet注册ResponseStatusExceptionResolver,并且可以使用它。

22.11.5 Customizing the Default Servlet Container Error Page

当响应的状态设置为错误状态代码并且响应的主体为空时,Servlet容器通常呈现HTML格式的错误页面。要定制容器的默认错误页面,可以在web.xml中声明元素。在Servlet 3之前,该元素必须映射到特定的状态代码或异常类型。从Servlet 3开始,不需要映射错误页面,这意味着指定的位置将定制默认的Servlet容器错误页面。


    /error

注意,错误页面的实际位置可以是JSP页面或容器中的其他URL,包括通过@Controller方法处理的URL:

在编写错误信息时,可以通过控制器中的请求属性访问HttpServletResponse上的状态代码和错误消息集:

@Controller
public class ErrorController {

    @RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Map handle(HttpServletRequest request) {

        Map map = new HashMap();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));

        return map;
    }

}

or in a JSP:

<%@ page contentType="application/json" pageEncoding="UTF-8"%>
{
    status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
    reason:<%=request.getAttribute("javax.servlet.error.message") %>
}

22.12 Web Security

Spring Security项目提供了一些特性来保护web应用程序免受恶意攻击。请参阅“CSRF保护”、“安全响应头文件”以及“Spring MVC集成”小节中的参考文档。请注意,并非所有特性都需要使用Spring安全性来保护应用程序。例如,可以通过向配置中添加CsrfFilter和csrfrequency estdatavalueprocessor来添加CSRF保护。有关示例,请参见Spring MVC展示。

另一种选择是使用专门用于Web安全的框架。HDIV就是这样一个框架,并与Spring MVC集成。

22.13 Convention over configuration support

对于许多项目来说,遵循已建立的约定并具有合理的默认值正是它们(项目)所需要的,Spring Web MVC现在明确支持约定优于配置。这意味着如果你建立一个命名约定等,可以大大减少所需的配置,设置处理程序映射,视图解析器,ModelAndView实例,等等。这是一个很好的恩赐对于快速原型,也可以提供一定程度的一致性(总是好)代码库应该选择推进到生产。

约定优于配置支持解决MVC的三个核心领域:模型、视图和控制器。

22.13.1 The Controller ControllerClassNameHandlerMapping

ControllerClassNameHandlerMapping类是一个HandlerMapping实现,它使用约定来确定请求url和处理这些请求的控制器实例之间的映射。

考虑下面的简单控制器实现。特别注意类的名称。

public class ViewShoppingCartController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // the implementation is not hugely important for this example...
    }

}

下面是相应Spring Web MVC配置文件的一个片段:




    

ControllerClassNameHandlerMapping会找到在其应用程序上下文中定义的所有各种处理程序(或控制器)bean,并将Controller从名称中去掉,以定义其处理程序映射。因此,ViewShoppingCartController映射到/viewshoppingcart*请求URL。

让我们看更多的例子,这样中心思想马上就熟悉了。(请注意url中的所有小写字母,与大小写混合的控制器类名形成对比。)

 

对于MultiActionController处理程序类,生成的映射稍微复杂一些。以下示例中的控制器名称被假设为MultiActionController实现:

 

如果您遵循将控制器实现命名为xxxController的惯例,ControllerClassNameHandlerMapping将为您省去定义和维护一个潜在的非常长的SimpleUrlHandlerMapping(或类似的)的麻烦。

ControllerClassNameHandlerMapping类扩展了AbstractHandlerMapping基类,因此可以像定义许多其他HandlerMapping实现一样定义HandlerInterceptor实例和其他所有东西。

 

  • WelcomeController maps to the /welcome* request URL
  • HomeController maps to the /home* request URL
  • IndexController maps to the /index* request URL
  • RegisterController maps to the /register* request URL
  • AdminController maps to the /admin/* request URL
  • CatalogController maps to the /catalog/* request URL

22.13.2 The Model ModelMap (ModelAndView)

ModelMap类本质上是一个美化过的映射,它可以使视图中(或视图上)显示的添加对象遵守公共命名约定。考虑以下控制器实现;注意,对象被添加到ModelAndView中,而没有指定任何关联的名称。

public class DisplayShoppingCartController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {

        List cartItems = // get a List of CartItem objects
        User user = // get the User doing the shopping

        ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- the logical view name

        mav.addObject(cartItems); <-- look ma, no name, just the object
        mav.addObject(user); <-- and again ma!

        return mav;
    }
}

ModelAndView类使用一个ModelMap类,它是一个自定义映射实现,当对象被添加到其中时,它会自动为对象生成一个键。对于标量对象(如User),确定添加对象的名称的策略是使用该对象的类的简短类名。下面的示例是为放入模型映射实例中的标量对象生成的名称。

 

什么,没有自动多元化?

 

  • An x.y.User instance added will have the name user generated.
  • An x.y.Registration instance added will have the name registration generated.
  • An x.y.Foo instance added will have the name foo generated.
  • java.util.HashMap instance added will have the name hashMap generated. You probably want to be explicit about the name in this case because hashMap is less than intuitive.
  • Adding null will result in an IllegalArgumentException being thrown. If the object (or objects) that you are adding could be null, then you will also want to be explicit about the name.

Spring Web MVC的约定优于配置支持不支持自动多元化。也就是说,您不能将Person对象列表添加到ModelAndView并将生成的名称设置为people。这一决定是在经过一番辩论后做出的,最终以“最小意外原则”胜出。

在添加集合或列表之后生成名称的策略是查看集合,获取集合中第一个对象的简短类名,并将其与添加到名称的List一起使用。这同样适用于数组,尽管使用数组不需要查看数组内容。下面的几个示例将使集合的名称生成语义更加清晰:

 

22.13.3 Default view name 

  • An x.y.User[] array with zero or more x.y.User elements added will have the name userList generated.
  • An x.y.Foo[] array with zero or more x.y.User elements added will have the name fooList generated.
  • java.util.ArrayList with one or more x.y.User elements added will have the name userList generated.
  • java.util.HashSet with one or more x.y.Foo elements added will have the name fooList generated.
  • An empty java.util.ArrayList will not be added at all (in effect, the addObject(..) call will essentially be a no-op).

RequestToViewNameTranslator接口在没有显式提供逻辑视图名时确定逻辑视图名。它只有一个实现,DefaultRequestToViewNameTranslator类。

DefaultRequestToViewNameTranslator将请求url映射到逻辑视图名,如下例所示:

public class RegistrationController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
        // process the request...
        ModelAndView mav = new ModelAndView();
        // add data as necessary to the model...
        return mav;
        // notice that no View or logical view name has been set
    }

}



    
    

    
        
    

    
    

    
        
        
    

注意,在handleRequest(..)方法的实现中,在返回的ModelAndView上从未设置视图或逻辑视图名称。DefaultRequestToViewNameTranslator的任务是从请求的URL生成逻辑视图名。上面的RegistrationController与ControllerClassNameHandlerMapping一起使用,它是http://localhost/registration的请求URL。html的结果是由DefaultRequestToViewNameTranslator生成注册的逻辑视图名。然后将这个逻辑视图名解析为/WEB-INF/jsp/registration。jsp视图,由InternalResourceViewResolver bean创建。

您不需要显式地定义DefaultRequestToViewNameTranslator bean。如果您喜欢DefaultRequestToViewNameTranslator的默认设置,那么您可以依赖Spring Web MVC DispatcherServlet来实例化这个类的实例(如果没有显式配置的话)。

当然,如果您需要更改默认设置,那么您确实需要显式地配置您自己的DefaultRequestToViewNameTranslator bean。有关可以配置的各种属性的详细信息,请参考全面的DefaultRequestToViewNameTranslator javadoc。

22.14 HTTP caching support

一个好的HTTP缓存策略可以显著提高web应用程序的性能和客户机的体验。‘Cache-Control’HTTP响应头主要负责这个,还有‘Last-Modified’和‘ETag’等条件头。

“Cache-Control”HTTP响应头为私有缓存(例如浏览器)和公共缓存(例如代理)提供建议,告诉它们如何缓存HTTP响应以供进一步重用。

ETag(实体标记)是HTTP/1.1兼容的web服务器返回的HTTP响应头,用于确定给定URL上内容的更改。可以认为它是最后修改的头的更复杂的继承。当服务器返回带有ETag报头的表示时,客户端可以在后续的get中使用这个报头,在一个if - nonmatch报头中。如果内容没有更改,服务器返回304:not Modified。

本节描述在Spring Web MVC应用程序中配置HTTP缓存的不同选择。

22.14.1 Cache-Control HTTP header

Spring Web MVC支持许多用例和为应用程序配置“Cache-Control”头文件的方法。虽然RFC 7234第5.2.2节完整地描述了这个头及其可能的指令,但是有几种方法可以处理最常见的情况。

Spring Web MVC在它的几个api中使用了一个配置约定:setCachePeriod(int seconds):

 

CacheControl builder类简单地描述了可用的“Cache-Control”指令,使构建自己的HTTP缓存策略变得更容易。一旦构建完成,CacheControl实例就可以作为几个Spring Web MVC api中的参数接受。

 

  • -1值不会生成“Cache-Control”响应头。
  • 0值将阻止使用“Cache-Control: no-store”指令进行缓存。
  • 一个n > 0值将使用“cache - control: max-age=n”指令缓存给定的响应n秒。
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
                                    .noTransform().cachePublic();

22.14.2 HTTP caching support for static resources

静态资源应该使用适当的“缓存控制”和条件头来提供,以获得最佳的性能。配置一个ResourceHttpRequestHandler来为静态资源提供服务,它不仅通过读取文件的元数据本地地写“Last-Modified”报头,而且如果配置得当,还会写“Cache-Control”报头。

您可以在ResourceHttpRequestHandler上设置cachePeriod属性,或者使用CacheControl实例,它支持更具体的指令:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public-resources/")
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
    }

}
And in XML:

    

22.14.3 Support for the Cache-Control, ETag and Last-Modified response headers in Controllers

controllers可以支持“Cache-Control”、“ETag”和/或“If-Modified-Since”HTTP请求;如果要在响应上设置一个“Cache-Control”头,那么确实建议这样做。这包括计算给定请求的lastModified long和/或Etag值,将其与“If-Modified-Since”请求头值进行比较,并可能返回状态码为304(未修改)的响应。

如“使用HttpEntity”一节所述,控制器可以使用HttpEntity类型与请求/响应进行交互。返回ResponseEntity的控制器可以在响应中包含HTTP缓存信息,如下所示:

@GetMapping("/book/{id}")
public ResponseEntity showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
                .ok()
                .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
                .eTag(version) // lastModified is also available
                .body(book);
}

这样做不仅会在响应中包含“ETag”和“Cache-Control”报头,而且如果客户端发送的条件报头与控制器设置的缓存信息匹配,它还会将响应转换为一个HTTP 304 not Modified响应,且该响应的主体为空。

@RequestMapping方法也可能希望支持相同的行为。这可以实现如下:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long lastModified = // 1. application-specific calculation

    if (request.checkNotModified(lastModified)) {
        // 2. shortcut exit - no further processing necessary
        return null;
    }

    // 3. or otherwise further request processing, actually preparing content
    model.addAttribute(...);
    return "myViewName";
}

这里有两个关键元素:调用request.checkNotModified(lastModified)和返回null。前者在返回true之前设置适当的响应状态和标题。后者与前者结合在一起,导致Spring MVC不进一步处理请求。

注意,有3种变体:

 

当接收到条件“GET”/“HEAD”请求时,checkNotModified将检查资源是否被修改,如果被修改,它将导致HTTP 304 not modified响应。对于有条件的“POST”/“PUT”/“DELETE”请求,checkNotModified将检查资源是否已被修改,如果已修改,则会导致HTTP 409 Precondition Failed response,以防止并发修改。

 

  • request.checkNotModified(lastModified) 将lastModified与“If-Modified-Since”或“If-Unmodified-Since”请求头进行比较
  • request.checkNotModified(eTag) 将eTag与“if - not - match”请求头进行比较
  • request.checkNotModified(eTag, lastModified) 这意味着两个条件都是有效的

22.14.4 Shallow ETag support

 

22.15 Code-based Servlet container initialization 基于代码的Servlet容器初始化

在Servlet 3.0+环境中,您可以选择以编程方式配置Servlet容器,作为替代方法,或者与web结合使用。xml文件。下面是一个注册DispatcherServlet的例子:

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }

}

WebApplicationInitializer是Spring MVC提供的接口,它可以确保检测到您的实现,并自动用于初始化任何Servlet 3容器。WebApplicationInitializer的抽象基类实现AbstractDispatcherServletInitializer使注册DispatcherServlet变得更加容易,方法是简单地覆盖指定servlet映射和DispatcherServlet配置位置的方法。

建议使用基于java的Spring配置的应用程序:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[] { MyWebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

如果使用基于xml的Spring配置,您应该直接从AbstractDispatcherServletInitializer扩展:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

}

AbstractDispatcherServletInitializer还提供了一种方便的方法来添加筛选器实例,并将它们自动映射到DispatcherServlet:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }

}

每个筛选器都使用基于其具体类型的默认名称添加,并自动映射到DispatcherServlet。

AbstractDispatcherServletInitializer的isAsyncSupported受保护方法提供了一个单独的位置来启用DispatcherServlet和映射到它的所有过滤器上的异步支持。默认情况下,此标志设置为true。

最后,如果需要进一步定制DispatcherServlet本身,可以覆盖createDispatcherServlet方法。

22.16 Configuring Spring MVC

22.2.1节“WebApplicationContext中的特殊Bean类型”和22.2.2节“默认DispatcherServlet配置”解释了Spring MVC的特殊Bean和DispatcherServlet使用的默认实现。在本节中,您将了解另外两种配置Spring MVC的方法。即MVC Java配置和MVC XML命名空间。

MVC Java配置和MVC命名空间提供了类似的默认配置,覆盖了DispatcherServlet默认设置。其目标是使大多数应用程序不必创建相同的配置,并为配置Spring MVC提供更高层次的结构,这些结构作为一个简单的起点,只需要很少或根本不需要底层配置的知识。

您可以根据自己的偏好选择MVC Java配置或MVC命名空间。另外,正如您将在下面看到的,使用MVC Java配置更容易看到底层配置以及直接对创建的Spring MVC bean进行细粒度定制。让我们从头开始。

22.16.1 Enabling the MVC Java Config or the MVC XML Namespace

要启用MVC Java config,请将注释@EnableWebMvc添加到您的@Configuration类中:

@Configuration
@EnableWebMvc
public class WebConfig {
}

要在XML中实现同样的目的,可以在DispatcherServlet上下文中(或者在没有定义DispatcherServlet上下文的根上下文中)使用mvc:annotation-driven的元素:




    

上面的代码注册了RequestMappingHandlerMapping、RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver等,以支持使用带有注释的控制器方法(如@RequestMapping、@ExceptionHandler等)处理请求。

它还可以实现以下功能:

  1. 除了用于数据绑定的JavaBeans PropertyEditors之外,还通过一个ConversionService实例进行Spring 3样式类型转换。
  2. 通过ConversionService支持使用@NumberFormat注释格式化数字字段。
  3. 支持使用@DateTimeFormat注释格式化日期、日历、Long和Joda-Time字段。
  4. 如果类路径中存在JSR-303提供程序,则支持使用@Valid验证@Controller输入。
  5. HttpMessageConverter支持@RequestBody方法参数和@ResponseBody方法从@RequestMapping或@ExceptionHandler方法返回值。

         这是mvc:注解驱动的HttpMessageConverter的完整列表:

 

 有关如何自定义这些默认转换器的更多信息,请参见22.16.12节“消息转换器”。

Jackson JSON和XML转换器使用由Jackson2ObjectMapperBuilder创建的ObjectMapper实例创建,以便提供更好的默认配置。

这个构建器使用以下属性定制Jackson的默认属性:

  •  ByteArrayHttpMessageConverter converts byte arrays.
  • StringHttpMessageConverter converts strings.
  • ResourceHttpMessageConverter converts to/from org.springframework.core.io.Resource for all media types.
  • SourceHttpMessageConverter converts to/from a javax.xml.transform.Source.
  • FormHttpMessageConverter converts form data to/from a MultiValueMap.
  • Jaxb2RootElementHttpMessageConverter converts Java objects to/from XML — added if JAXB2 is present and Jackson 2 XML extension is not present on the classpath.
  • MappingJackson2HttpMessageConverter converts to/from JSON — added if Jackson 2 is present on the classpath.
  • MappingJackson2XmlHttpMessageConverter converts to/from XML — added if Jackson 2 XML extension is present on the classpath.
  • AtomFeedHttpMessageConverter converts Atom feeds — added if Rome is present on the classpath.
  • RssChannelHttpMessageConverter converts RSS feeds — added if Rome is present on the classpath.
  • DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES is disabled.
  • MapperFeature.DEFAULT_VIEW_INCLUSION is disabled.

 如果在类路径中检测到以下知名模块,它还会自动注册这些模块:

 

 22.16.2 Customizing the Provided Configuration 定制提供的配置

要在Java中自定义默认配置,只需实现WebMvcConfigurer接口,或者更有可能扩展类WebMvcConfigurerAdapter并覆盖所需的方法:

 

  1. jackson-datatype-jdk7: support for Java 7 types like java.nio.file.Path.
  2. jackson-datatype-joda: support for Joda-Time types.
  3. jackson-datatype-jsr310: support for Java 8 Date & Time API types.
  4. jackson-datatype-jdk8: support for other Java 8 types like Optional.
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // Override configuration methods...
}

要定制的默认配置,请检查它支持哪些属性和子元素。您可以查看Spring MVC XML模式,或者使用IDE的代码完成特性来发现哪些属性和子元素是可用的。

22.16.3 Conversion and Formatting

默认情况下,会安装数字和日期类型的格式化程序,包括对@NumberFormat和@DateTimeFormat注释的支持。如果类路径中存在Joda-Time,还将安装对Joda-Time格式化库的完全支持。要注册自定义格式化程序和转换器,请覆盖addFormatters方法:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }
}

在MVC命名空间中,当添加时,应用相同的缺省值。注册自定义格式器和转换器只需提供一个转换服务:




    

    
        
            
                
            
        
        
            
                
                
            
        
        
            
                
            
        
    

 

有关何时使用FormatterRegistrar的更多信息,请参见第9.6.4节“FormatterRegistrar SPI”和FormattingConversionServiceFactoryBean。

22.16.4 Validation

Spring提供了一个验证器接口,可以在应用程序的所有层中进行验证。在Spring MVC中,您可以将其配置为一个全局验证器实例,在遇到@Valid或@Validated控制器方法参数时使用,或者通过@InitBinder方法将其配置为控制器中的本地验证器。可以组合全局和本地验证器实例来提供复合验证。

Spring还通过LocalValidatorFactoryBean支持JSR-303/JSR-349 Bean验证,该Bean适应Spring org.springframework.validation.Validator接口到Bean验证javax.validation.Validator的合同。这个类可以作为全局验证器插入Spring MVC,如下所述。

默认情况下,使用@EnableWebMvc或会在类路径中检测到Bean验证提供者(如Hibernate验证器)时,通过LocalValidatorFactoryBean在Spring mvc中自动注册Bean验证支持。

有时将LocalValidatorFactoryBean注入到控制器或其他类中是很方便的。最简单的方法是声明您自己的@Bean,并将其标记为@Primary,以避免与MVC Java配置提供的@Bean发生冲突。

如果您更喜欢使用MVC Java配置中的方法,则需要覆盖WebMvcConfigurationSupport中的mvcValidator方法,并声明该方法以显式返回LocalValidatorFactory而不是Validator。有关如何切换以扩展所提供的配置的信息,请参见22.16.13节“使用MVC Java Config的高级定制”。

或者,您可以配置自己的全局验证器实例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public Validator getValidator(); {
        // return "global" validator
    }
}

and in XML:




    

要将全局验证与本地验证结合起来,只需添加一个或多个本地验证器:

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }

}

使用这种最小配置,每当遇到@Valid或@ validation方法参数时,将由配置的验证器进行验证。任何违反验证的行为都将自动暴露为BindingResult中的错误,BindingResult可以作为方法参数访问,并且可以在Spring MVC HTML视图中呈现。

22.16.5 Interceptors

您可以将HandlerInterceptors或WebRequestInterceptors配置为应用于所有传入请求或限制于特定URL路径模式。

在Java中注册拦截器的一个例子:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleInterceptor());
        registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }

}

在XML中使用元素:


    
    
        
        
        
    
    
        
        
    

 

你可能感兴趣的:(spring)