Spring MVC

当发送一个请求时,第一站是Spring的DispatcherServlet。与大多数基于Java的Web框架一样,Spring MVC所有的请求都会通过一个前端控制器Servlet。前端控制器是常用的Web应用程序模式.Spring MVC中,DispatcherServlet就是前端控制器。DispatcherServlet的任务是将请求发送给Spring MVC控制器。控制器是一个用于处理请求的Spring组件。DispatcherServlet以会查询一个或多个处理器映射(handler mapping)来确定请求的下一站在哪里。处理器映射会根据请求所携带的URL信息来进行决策。一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示,这些信息被称为模型(model)。信息需要发送给一个视图(view),通常是JSP。控制器所做的最后一件事就是将模型数据打包,并且表示出用于渲染输出的视图名。它接下来会将请求连同模型和视图名发送回DispatcherServlet。这样,控制器就不会与特定的视图相耦合,传递个DispatcherServlet的视图名并不直接表示某个特定的JSP/html。它仅仅传递一个逻辑名称,这个名称将会用来查找产生结果的真正试图。DispatcherServlet将会使用试图解析器(view resolver)来将逻辑试图名匹配为一个特定的试图实现。最后一站是试图的实现。在这里DispatcherServlet交付模型数据。试图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端。

配置DispatcherServlet

  Spring提供了AbstractAnnotationConfigDispatcherServletInitializer的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,也就是AbstractAnnotationConfigDispatherServletInitializer。

AbstractAnnotationConfigDispatherServletInitializer有三个方法:

getServletMappings():将一个或多个路径映射到DispatcherServlet上

getServletConfigClasses():要求DispatcherServlet加载应用上下文

  当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。

getRootConfigClasses():返回配置ContextLoaderListener创建的应用上下文中的bean

  ContextLoaderListener会加载包含Web组件的bean,如控制器,视图解析器以及处理器映射import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {WebConfig.class};
    }

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

@Override
protected Filter[] getServletFilters()[
return new Filter[]{new MyFilter[]};
} }

通过调用DefaultServletHanlderConfigurer的enable()方法,要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本来来处理此类请求。

Spittr应用有两个基本的领域概念:Spitter(应用的用户)和Spittle(用户发布的简短状态更新)。

SpringMVC允许以多种方式将客户端中国的数据传送到控制器的处理器方法中,包括:查询参数(Query Parameter),表单参数(Form Parameter)和路径变量(Path Variable)。

Spring支持Java校验API(Java Valildation API,又称JSR-303)。在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java API的实现即可。

Java校验API定义了多个注解

@AssertFalse  所注解的元素必须是Boolean类型,且值为false

@AssertTrue   所注解的元素必须是Boolean类型,且值为true

@DecimalMax   所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString值

@DecimalMin  所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString值

@Digits       所注解的元素必须是数字,并且它的值必须有指定的位数

@Future         所注解的元素的值必须是一个将来的日期    

@Max       所注解的元素必须是数字,并且他的值要小于或等于给定的值

@Min        所注解的元素必须是数字,并且它的值要大于或等于给定的值

@NotNull    所注解元素的值必须不能为null

@Null       所注解元素的值必须为null

@Past      所注解的元素的值必须是一个已过去的日期

@Pattern           所注解的元素的值必须匹配给定的正则表达式

@Size        所注解的元素的值必须是String,集合或数组,并且它的长度要符合给定的范围

AbstractAnnotationConfigDispatcherServletInitializer可以做一些额外的配置

  在AbstractAnnotationConfigDispatcherServletInitilizer将DispatcherServlet注册到Servlet容器后,会调用customizeRegistration(),并将Servlet注册后得到的Registration.Dynamic传递进来,通过重载customizeRegistration()方法,可以对DispatcherServlet进行额外的配置。

@Override
protected void customizeRegistration(Dynamic registration){
    registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}

  AbstractAnnotationConfigDispatcherServletInitializer会创建DispathcerServlet和ContextLoaderListener。当我们想向Web容器中注册其他组件时,只需要创建一个新的初始化容器就可以了,最简单的方式就是实现Spring的WebApplicationInitializer接口。

public class MyServletInitializer implements WebApplicationInitializer{
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
        Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class);   
        myServlet.addMapping("/custom/**");
filter.addMappingForUrlPatterns(null, false, "/custom/**"); } }

在web.xml中搭建Spring MVC

<?xml version="1.0" encoding="UTF-8" ?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/nx/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name>Archetype Created Web Application</display-name>
  
  <context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
  </context-param>

  <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>/WEB-INF/spring/root-context.xml</param-value>  
  </context-param>    

  <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
     <servlet-name>appServlet</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
      <load-on-startup>1</load-on-startup>
  </servlet>    

  <servlet-mapping>
     <servlet-name>appServlet</servlet-name>
     <url-pattern>/<url-pattern>
  </servlet-mapping>

</web-app>

contextLoaderListener和DispatcherServlet会各自加载一个Spring应用上下文。上下文参数contextConfigLocation指定了一个XML文件(root-context.xml),它会被ContextLoaderListener加载。

DispatcherServlet会根据Servlet的名字找到一个文件,并基于该文件加载应用上下文。Servlet的名字是appServlet,因为DispatcherServlet会从“/WEB-INF/appServlet.xml”文件中加载器应用上下文。也可在Servlet上指定一个contextConfigLocation初始化参数。

  <servlet>
     <servlet-name>appServlet</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>    

设置web.xml使用基于Java的配置

<?xml version="1.0" encoding="UTF-8" ?>
<web-app version="2.5"
    xmlns="http://java.sun.com/xml/nx/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name>Archetype Created Web Application</display-name>
  
  <context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
  </context-param>
  
  <context-param>
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>
  
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>cherry.config.RootConfig</param-value>
  </context-param>
  
  <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
  <servlet>
      <servlet-name>appServlet</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
          <param-name>contextClass</param-name>
          <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </init-param>
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>cherry.config.WebConfig</param-value>
          <load-on-startup>1</load-on-startup>
      </init-param>
  </servlet>
  
  <servlet-mapping>
      <servlet-name>appServlet</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

DispatcherServlet并没有实现任何解析multipart请求数据的功能。它将该任务委托给了Spring中MultipartResolver策略接口的实现,通过这个实现类来解析multipart请求中的内容。从Spring3.1开始,Spring内置了两个MultipartResolver的实现供我们选择:

  CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求

  StandardServletMultipartResolver:依赖于Servlet3.0对multipart请求的支持

使用Servlet3.0解析multipart请求

  1.在Spring应用上下文中声明bean

@Bean
public MultipartResolver multipartResolver() throws IOException(){
    return new StandardServletMultipartResolver();
}

  2.在AnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer中重载customizeRegistration()方法来配置multipart具体细节--临时路径,上传文件最大容量,整个multipart请求最大容量,如果文件达到了一个指定最大容量,将会写入到临时路径中

@Override
protected void customizeRegistration(Dynamic regsitration){
    registration.setMultipartConfig(new MultipartConfigElement("tmp/spittr/uploads", 2097152, 4194304, 0));
}

  3.使用web.xml配置MultipartConfigElement

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <multipart-config>
        <location>/tmp/spittr/uploads</location>
        <max-file-size>2097152</max-file-size>
        <max-request-size>4194304</max-request-size>
    </multipart-config>
</servlet>

配置Jakarta Commons FileUpload multipart解析器

  在Spring应用上下文中声明bean并对其进行配置

@Bean
public MultipartResolver multipartResolver(){
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setUploadTempDir("tmp/spittr/uploads");
multipartResolver.setMaxUploadSize(2097152);
multipartResolver.setMaxInMemorySize(0);
retrun multipartResolver;

}

处理异常

  Servlet请求的输出是一个Servlet相应,在处理异常时,输出仍是Servlet响应,一场必须要以某种方式转换为响应。

Spring提供了多种方式将异常转换为响应:

  特定的Spring异常将会自动映射为指定的HTTP状态码

  异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码

  在方法上可以添加@ExceptionHandler注解,使其用来处理异常

异常处理的最简单方式是将其映射到HTTP状态码上,进而放到响应之中。

Spring会将自身的一些异常自动转换为合适的状态码

BindException                400 - Bad Request

HttpMessageNotReadbleException      400 - Bad Request

MethodArgumentNotValidException        400 - Bad Request

MissingServletRequestParameterException   400 - Bad Request

MissingServletRequestPartException      400 - Bad Request

TypeMismatcheException           400 - Bad Request

NoSuchRequestHandlingMethodException  404 - Not Found

HttpRequestMethodNotSupportedException 405 - Method Not Allowed

HttpMediaTypeNotAcceptableException         406 - Not Acceptable

HttpMediaTypeNotSupportedException    415 - Unsupported Media Type

ConversionNotSupportedException              500 - Internal Server Error

HttpMessageNotWritableException              500 - Internal Server Error

DispatcherServlet处理过程中或执行检验时出现问题时,Spring会抛出异常。如果DispatcherServlet无法找到合适处理请求的控制器方法,将会抛出NoSuchRequestHandlingMethodException异常,产生404状态码的响应

@ResponseStatus:将异常映射为特定的状态码

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException{}

@ExceptionHandler会处理同意控制器中所有处理器方法所抛出的异常

@ExceptionHandler
public String handleSpittleNotFoundException(){
    return "error/spittleNotFound";
}

控制器通知(controller advice)是任意带有@ControllerAdvice注解的类。这个类会包含一个或多个如下类型的方法:

  @ExceptionHandler注解标注的方法;

  @InitBinder注解标注的方法;

  @ModelAttribute注解标注的方法;

在带有ControllerAdvice注解的类中,上述方法会运用都整个应用程序素有控制器中带有@RequestMapping注解的方法上

@ControllerAdvice
public class AppWebExceptionHandler {

    @ExceptionHandler(DuplicateSpittleException.class)
    public String handleDuplicationSpittle(){
        return "error/duplicate";
    }
    
}

重定向

  在处理完POST请求后,执行重定向可以防止用户点击浏览器的刷新按钮或后腿箭头时,客户端重新执行危险的POST请求。当控制器方法返回的String值以"redirect:"开头时,这个String不是用来查找视图的,而是用来知道浏览器进行重定向的路径。

  当一个处理器方法完成之后,该方法所指定的模型数据将会复制到请求中,并作为请求中的属性,请求会转发(forward)到视图上进行渲染。因为控制器方法和视图所处理的是同一个请求,所有在转发的过程中,请求属性能够得以保存。

  对于发起重定向的方法处理数据有两种方式:

    使用URL模板以路径和/或查询参数的形式传递数据

    通过flash属性发送数据

通过URL模板进行重定向

  使用String直接连接是很危险的,如 return "redirect:/spitter/{username}";

  Spring提供了使用模板的方法来定义重定向URL。

  username作为占位符填充到了URL模板中,而不是直接连接到重定向String中。firstName属性没有匹配重定向URL中的任何占位符,所以它会自动以查询参数的形式附加到重定向URL上。所以下面URL的路径是"/spitter/username?firstName=firstName"

@RequestMapping(value = "register", method = RequestMethod.POST)
public String processRegistration(Spitter spitter, Model model){
    spitterRepository.save(spitter);
    model.addAttribute("username", spitter.getUsername);
    model.addAttribute("firstName", spitter.getFirstName);
    return "redirect:/spitter/{username}";
}

使用flash

  Spring提供了将数据发送为flash属性的功能。flash属性会一直携带这些数据知道下一次请求,然后会消失。在重定向执行之前,所有的flash属性都会复制到会话中。在重定向后,存在会话中的flash属性会被取出,并从会话转移到模型之中。

@RequestMapping(value = "register", method = RequestMethod.POST)
public String processRegistration(Spitter spitter, RedirectAttributes model){
    spitterRepository.save(spitter);
    model.addAttribute("username", spitter.getUsername());
    model.addFlashAttribute("spitter", spitter);
    return "redirect:/spitter/{username}";
}

@RequestMapping(value = "/{username}" , method = RequestMethod.GET)
public String showSpitterProfile(@PathVariable String username. Model model){
    if(!model.containsAttribute("spitter")){
        model.addAttribure(new Spitter());
    }
    return "profile";
}
原文地址:https://www.cnblogs.com/forerver-elf/p/6383530.html