Spring MVC 功能支持

Spring Web MVC 是一个基于Servlet API构建的原始框架。 从一开始就包含在Spring框架中。“spring web mvc”的正式名称来自其源模块(spring-webmvc)的名称,但它通常被称为“spring mvc”。

一、DispatcherServlet

spring mvc和其他许多web框架一样,都是围绕front controller(前端控制器)模式设计的,其中一个中央Servlet DispatcherServlet为请求处理提供了一个共享算法,而实际工作则由可配置的委托组件执行。该模型具有灵活性,支持多种工作流。

DispatcherServlet和任何Servlet一样,都需要使用Java配置或web.xml文件. 反过来,DispatcherServlet使用Spring配置来发现请求映射、视图解析、异常处理等所需的委托组件。

下面的Java配置示例注册并初始化DispatcherServlet,它由Servlet容器自动检测:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

除了直接使用ServletContext API外,还可以扩展AbstractAnnotationConfigDispatcherServletInitializer并重写特定的方法。

以下示例web.xml文件配置注册并初始化DispatcherServlet:

<web-app>

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

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

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>
上下文结构层次

DispatcherServlet 期望一个WebapplicationContext为其自身配置。WebApplicationContext有一个指向ServletContext及其关联的Servlet的链接。它还绑定到ServletContext,以便应用程序在需要访问WebApplicationContext时可以对RequestContextUtils使用静态方法来查找它。

对于许多应用程序来说,只有一个WebApplicationContext是简单且足够的。还可以有一个上下文层次结构,其中一个根WebApplicationContext跨多个DispatcherServlet(或其他Servlet)实例共享,每个实例都有自己的子WebApplicationContext配置。

根WebApplicationContext通常包含基础结构bean,例如需要跨多个Servlet实例共享的数据存储库和业务服务。这些bean被有效地继承,并且可以在特定于Servlet的子WebApplicationContext中被重写(即重新声明),后者通常包含给定Servlet的本地bean。下图显示了这种关系:

以下示例配置WebApplicationContext层次结构:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

如果不需要应用程序上下文层次结构,则应用程序可以通过getRootConfigClasses()返回所有配置,从getServletConfigClasses()返回null。

上面配置等价于web.xml的配置如下:

<web-app>

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

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

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

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>

如果不需要应用程序上下文层次结构,则应用程序可能只配置“根”上下文,并将contextConfigLocation Servlet参数留空。

特殊Bean类型

DispatcherServlet委托给特殊的bean来处理请求并呈现适当的响应。我们所说的“特殊bean”是指实现框架契约的Spring管理对象实例。它们通常带有内置的契约,但你可以自定义它们的属性并扩展或替换它们。

下表列出了DispatcherServlet所用到的特殊bean:

HandlerMapping

将请求与拦截器列表一起映射到处理程序,以进行预处理和后处理。映射基于一些条件,这些条件的细节因handler映射实现而异。

两个主要的HandlerMapping实现是RequestMappingHandlerMapping(它支持@RequestMapping带注解的方法)和SimpleUrlHandlerMapping(维护处理程序的URI路径模式的显式注册)。

HandlerAdapter

帮助DispatcherServlet调用映射到请求的处理程序,而不管处理程序实际是如何调用的。例如,调用带注解的控制器需要解析注解。HandlerAdapter的主要目的是屏蔽DispatcherServlet更多细节。

HandlerExceptionResolver

解决异常的策略,可能将它们映射到处理程序、HTML错误视图或其他目标。

ViewResolver

将从处理程序返回的基于逻辑字符串的视图名称解析为要呈现给响应的实际视图。

LocaleResolver, LocaleContextResolver

解析客户机正在使用的区域设置以及可能的时区,以便能够提供国际化的视图。

ThemeResolver

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

MultipartResolver

使用一些multipart转换工具转换一个multi-part请求,(例如,浏览器表单文件上传)。

FlashMapManager

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

Web MVC配置

应用程序可以声明处理请求所需的特殊Bean类型中列出的基础结构Bean。DispatcherServlet检查每个特殊bean的WebApplicationContext。如果没有匹配的bean类型,则返回中列出的默认类型DispatcherServlet.properties.

在大多数情况下,MVC配置是最好的起点。它用Java或XML声明所需的bean,并提供更高级别的配置回调API来定制它。

Servlet配置

在Servlet3.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是SpringMVC提供的一个接口,它确保检测到你的实现并自动用于初始化任何Servlet3容器。WebApplicationInitializer的抽象基类实现名为AbstractDispatcherServletInitializer,它通过重写方法来指定servlet映射和DispatcherServlet配置的位置,使注册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 处理过程

DispatcherServlet处理请求如下:

  • WebApplicationContext在请求中使用控制器和进程中其他元素可以使用的属性进行搜索和绑定。默认情况下,它在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE绑定。
  • 语言环境解析程序绑定到请求,以便进程中的元素解析处理请求(呈现视图、准备数据等)时要使用的区域设置。如果不需要区域设置解析,则不需要区域设置解析程序。
  • 主题解析器绑定到请求,以让视图等元素确定要使用哪个主题。如果不使用主题,可以忽略它。
  • 如果指定multipart 文件解析程序,则会检查请求是否存在multiparts 。如果找到了multiparts ,则请求被包装在MultipartttpservletRequest中,供进程中的其他元素进一步处理。
  • 搜索适当的处理程序。如果找到处理程序,则执行与处理程序(预处理器、后处理器和控制器)关联的执行链,以便准备模型或呈现。或者,对于带注解的控制器,可以呈现响应(在HandlerAdapter中),而不是返回视图。
  • 如果返回模型,则渲染视图。如果没有返回任何模型(可能是由于预处理器或后处理器拦截了请求,也可能是出于安全原因),则不会呈现任何视图,因为请求可能已经完成。

在WebApplicationContext中声明的HandlerExceptionResolver bean用于解析请求处理期间引发的异常。这些异常解析器允许定制处理异常的逻辑。

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

你可以通过在web.xml的Servlet定义中添加初始化参数(init-param),自定义DispatcherServlet实例

DispatcherServlet 初始化参数:

  • contextClass:该类实现ConfigurableWebApplicationContext,将由该Servlet实例化和本地配置。默认情况下,使用XmlWebApplicationContext。
  • contextConfigLocation:传递给上下文实例(由contextClass指定)的字符串,以指示可以在何处找到上下文。字符串可能由多个字符串(使用逗号作为分隔符)组成,以支持多个上下文。如果多个上下文位置包含定义了两次的bean,则最新的位置优先。
  • namespace:WebApplicationContext的命名空间。默认为[servlet name]-servlet。
  • throwExceptionIfNoHandlerFound:在找不到请求的处理程序时是否引发NoHandlerFoundException。然后可以使用HandlerExceptionResolver(例如,通过使用@ExceptionHandler控制器方法)捕获异常,并将其作为任何其他方法进行处理。默认情况下,被设置为false,在这种情况下,DispatcherServlet将响应状态设置为404(NOT_FOUND),而不会引发异常。

注意,如果还配置了默认的servlet处理,那么未解析的请求总是被转发到默认的servlet,并且不会引发404。

所有HandlerMapping实现都支持拦截器,当你希望将特定功能应用于某些请求时,这些拦截器非常有用,例如,检查主体。拦截器必须实现org.springframework.web.servlet包的HandlerInterceptor,包含三个方法,这些方法提供了足够的灵活性来执行各种预处理和后处理:

  • preHandle(..):在执行实际处理程序之前
  • postHandle(..):在执行处理程序之后
  • afterCompletion(..):完成请求后

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

请注意,对于@ResponseBody和ResponseEntity方法,postHandle没有那么有用,因为它们在HandlerAdapter和postHandle之前为其编写和提交响应。这意味着对响应进行任何更改(例如添加额外的头)已经太晚了。对于这种情况,你可以实现ResponseBodyAdvice,并将其声明为spring bean,或者直接在RequestMappingHandlerAdapter上配置它。

异常

如果在请求映射过程中发生异常或从请求处理程序(如@Controller)抛出异常,那么DispatcherServlet将委托给HandlerExceptionResolver bean链来解析异常并提供替代处理,这通常是一个错误响应。

下表列出了可用的HandlerExceptionResolver实现:

  • SimpleMappingExceptionResolver:异常类名和错误视图名之间的映射。用于在浏览器应用程序中呈现错误页。
  • DefaultHandlerExceptionResolver:解决spring mvc引发的异常,并将它们映射到HTTP状态代码。
  • ResponseStatusExceptionResolver:使用@ResponseStatus注释解析异常,并根据注释中的值将它们映射到HTTP状态代码。
  • ExceptionHandlerExceptionResolver:通过调用@Controller或@ControllerAdvice类中的@ExceptionHandler方法来解决异常。
Resolvers 链

通过在Spring配置中声明多个HandlerExceptionResolver bean并根据需要设置它们的order属性,可以形成一个异常解析器链。order属性越高,异常解决程序的定位就越晚。

HandlerExceptionResolver明确规定可以返回:

  • 指向错误视图的ModelAndView
  • 如果异常是在解析程序(resolver)中处理的,则为空的ModelAndView。
  • 如果异常仍未解决,则为空,供后续解析器(resolver)尝试;如果异常仍在结尾,则允许它冒泡到Servlet容器。

容器错误页

如果任何HandlerExceptionResolver仍然无法解决异常,则需要进行传播,或者如果响应状态设置为错误状态(即4xx、5xx),Servlet容器可以用HTML呈现默认的错误页。若要自定义容器的默认错误页,可以在中声明错误页映射web.xml文件. 下面的示例演示如何执行此操作:

<error-page>
    <location>/error</location>
</error-page>

在前面的示例中,当出现异常或响应具有错误状态时,Servlet容器在容器内对配置的URL进行错误分派(例如/error)。然后由DispatcherServlet处理,可能将其映射到@Controller,可以实现该操作以返回带有模型的错误视图名称或呈现JSON响应,如下例所示:

@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));
        return map;
    }
}

Servlet API不提供在Java中创建错误页映射的方法。但是,你可以同时使用WebApplicationInitializer和web.xml文件.

视图(View)解决方案

spring mvc定义了ViewResolver和视图接口,这些接口允许你在浏览器中呈现模型,而无需将你绑定到特定的视图技术。ViewResolver提供视图名称和实际视图之间的映射。视图处理在移交给特定视图技术之前的数据准备。

下表列出了ViewResolver 的实现类:

  • AbstractCachingViewResolver:解析AbstractCachingViewResolver缓存视图实例的子类。缓存可以提高某些视图技术的性能。可以通过将cache属性设置为false来关闭缓存。此外,如果必须在运行时刷新某个视图(例如,在修改FreeMarker模板时),则可以使用removeFromCache(String viewName,Locale loc)方法。
  • XmlViewResolver:接受用XML编写的配置文件,其DTD与Spring的xml配置相同。默认配置文件是/WEB-INF/views.xml。
  • ResourceBundleViewResolver:使用由bundle基名称指定的ResourceBundle中的bean定义。对于它应该解析的每个视图,它使用属性[viewname].(class)的值作为视图类,将属性[viewname].url的值用作视图url。
  • UrlBasedViewResolver:ViewResolver接口的简单实现,该接口将逻辑视图名称直接解析为URL,而无需显式映射定义。如果你的逻辑名称与视图资源的名称以直接的方式匹配,而不需要任意映射,那么这是合适的。
  • InternalResourceViewResolver:UrlBasedViewResolver的子类,支持InternalResourceView(实际上是servlet和JSP)和JstlView、TilesView等子类。你可以使用setViewClass(..)为这个解析器生成的所有视图指定视图类。
  • FreeMarkerViewResolver:UrlBasedViewResolver的子类,支持FreeMarkerView及其自定义子类。
  • ContentNegotiatingViewResolver:基于请求文件名或Accept头解析视图。
处理(Handling)

你可以通过声明多个解析器bean来链接视图解析器,如果需要,还可以通过设置order属性来指定顺序。请记住,order属性越高,视图解析器在链中的位置就越晚。

ViewResolver的协定指定它可以返回null以指示找不到视图。但是,对于JSP和InternalResourceViewResolver,确定JSP是否存在的唯一方法是通过RequestDispatcher执行调度。因此,必须始终将InternalResourceViewResolver配置为视图解析程序总体顺序中的最后一个。
配置视图解析就像在Spring配置中添加ViewResolver bean一样简单。

重定向(Redirecting)

视图名称中的redirect: 前缀允许执行重定向。UrlBasedViewResolver(及其子类)将其识别为需要重定向的指令。视图名称的其余部分是重定向URL。

这个效果与控制器返回了RedirectView相同,但现在控制器本身可以按逻辑视图名称进行操作。逻辑视图名称(例如redirect:/myapp/some/resource)相对于当前Servlet上下文重定向,而像这样的名称重定向:https://myhost.com/some/anytrary/path重定向到绝对URL。

请注意,如果控制器方法用@ResponseStatus注释,则注释值优先于RedirectView设置的响应状态。

转发(Forwarding)

视图名称中的forward: 前缀允许执行转发,UrlBasedViewResolver(及其子类)对它进行识别。这将创建一个InternalResourceView,它执行RequestDispatcher.forward(). 因此,此前缀对于InternalResourceViewResolver和InternalResourceView(对于JSP)没有用处,但是如果你使用另一种视图技术,但仍然希望强制由Servlet/JSP引擎处理资源的转发,则此前缀可能会有帮助。请注意,你也可以链接多个视图解析器。

内容协商(Content Negotiation)

ContentNegotiatingViewResolver本身不解析视图,而是委托给其他视图解析程序并选择与客户端请求的表示类似的视图。表示可以从Accept头或查询参数(如:"/path?format=pdf")

ContentNegotiatingViewResolver通过将请求媒体类型与其每个ViewResolver关联的视图支持的媒体类型(也称为内容类型)进行比较,选择适当的视图来处理请求。列表中具有兼容内容类型的第一个视图将表示形式返回给客户端。如果ViewResolver链无法提供兼容的视图,则会查询通过DefaultViews属性指定的视图列表。后一个选项适用于可以呈现当前资源的适当表示形式的单例视图,而不考虑逻辑视图名称。Accept头可以包含通配符(例如text/*),在这种情况下,内容类型为text/xml的视图是兼容的匹配项。

二、Spring MVC的相关注解

组件型注解
  • @Component 在类定义之前添加@Component注解,他会被spring容器识别,并转为bean。
  • @Repository 对Dao实现类进行注解 (特殊的@Component)
  • @Service 用于对业务逻辑层进行注解, (特殊的@Component)
  • @Controller 用于控制层注解 , (特殊的@Component)

以上四种注解都是注解在类上的,被注解的类将被spring初始话为一个bean,然后统一管理。

请求和参数型注解
@RequestMapping

用于处理请求地址映射,可以作用于类和方法上。

  • value:定义request请求的映射地址
  • method:定义地request址请求的方式,包括【GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.】默认接受get请求,如果请求方式和定义的方式不一样则请求无法成功。
  • params:定义request请求中必须包含的参数值。
  • headers:定义request请求中必须包含某些指定的请求头,如:RequestMapping(value = "/something", headers = "content-type=text/*")说明请求中必须要包含"text/html", "text/plain"这中类型的Content-type头,才是一个匹配的请求。
  • consumes:定义请求提交内容的类型。
  • produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回

注意:@RequestMapping 中还支持通配符“* ”

1、支持的方法参数类型

  •  HttpServlet 对象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 对象。 这些参数Spring 在调用处理器方法的时候会自动给它们赋值,所以当在处理器方法中需要使用到这些对象的时候,可以直接在方法上给定一个方法参数的申明,然后在方法体里面直接用就可以了。但是有一点需要注意的是在使用HttpSession 对象的时候,如果此时HttpSession 对象还没有建立起来的话就会有问题。
  • Spring 自己的WebRequest 对象。 使用该对象可以访问到存放在HttpServletRequest 和HttpSession 中的属性值。
  • InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是针对HttpServletRequest 而言的,可以从里面取数据;OutputStream 和Writer 是针对HttpServletResponse 而言的,可以往里面写数据。
  • 使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 标记的参数。
  • 使用@ModelAttribute 标记的参数。
  • java.util.Map 、Spring 封装的Model 和ModelMap 。 这些都可以用来封装模型数据,用来给视图做展示。
  • 实体类。 可以用来接收上传的参数。
  • Spring 封装的MultipartFile 。 用来接收上传文件的。
  • Spring 封装的Errors 和BindingResult 对象。 这两个对象参数必须紧接在需要验证的实体对象参数之后,它里面包含了实体对象的验证结果。

2、支持的返回类型

  • 一个包含模型和视图的ModelAndView 对象。
  • 一个模型对象,这主要包括Spring 封装好的Model 和ModelMap ,以及java.util.Map ,当没有视图返回的时候视图名称将由RequestToViewNameTranslator 来决定。
  • 一个View 对象。这个时候如果在渲染视图的过程中模型的话就可以给处理器方法定义一个模型参数,然后在方法体里面往模型中添加值。
  • 一个String 字符串。这往往代表的是一个视图名称。这个时候如果需要在渲染视图的过程中需要模型的话就可以给处理器方法一个模型参数,然后在方法体里面往模型中添加值就可以了。
  • 返回值是void 。这种情况一般是我们直接把返回结果写到HttpServletResponse 中了,如果没有写的话,那么Spring 将会利用RequestToViewNameTranslator 来返回一个对应的视图名称。如果视图中需要模型的话,处理方法与返回字符串的情况相同。
  • 如果处理器方法被注解@ResponseBody 标记的话,那么处理器方法的任何返回类型都会通过HttpMessageConverters 转换之后写到HttpServletResponse 中,而不会像上面的那些情况一样当做视图或者模型来处理。
  • 除以上几种情况之外的其他任何返回类型都会被当做模型中的一个属性来处理,而返回的视图还是由RequestToViewNameTranslator 来决定,添加到模型中的属性名称可以在该方法上用@ModelAttribute(“attributeName”) 来定义,否则将使用返回类型的类名称的首字母小写形式来表示。使用@ModelAttribute 标记的方法会在@RequestMapping 标记的方法执行之前执行。

Spring MVC新特性 提供了对Restful风格的支持:

① @GetMapping,处理get请求 
② @PostMapping,处理post请求 
③ @PutMapping,处理put请求 
④ @DeleteMapping,处理delete请求

@RequestParam

用于获取传入参数的值。

  • value:参数的名称
  • required:定义该传入参数是否必须,默认为true,(和@RequestMapping的params属性有点类似)
@RequestMapping("/requestParams1.do")
public String requestParams1(@RequestParam(required = false) String name){
    System.out.println("name = "+name);
    return "index";
}
@RequestMapping("/requestParams2.do")
public String requestParams2(@RequestParam(value = "name",required = false) String names){
    System.out.println("name = "+names);
    return "index";
}

两种请入参方式是一样的,显示声明value的名称时,入参参数名和value一样,没有显示声明的话,像第一种方式声明的,入参参数名和函数参数变量名一样。

@Resource和@Autowired

@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。

1、共同点

两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。

2、不同点

(1) @Autowired

@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。

@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。

(2) @Resource

@Resource默认按照byName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。

注:最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。

@Resource装配顺序:

① 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。

② 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。

③ 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。

④ 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。

@PathVariable

用于定义路径参数值。

  • value:参数的名称
  • required:定义传入参数是否为必须值
@Controller  
public class TestController {  
     @RequestMapping(value="/user/{userId}/roles/{roleId}",method = RequestMethod.GET)  
     public String getLogin(@PathVariable("userId") String userId,  
         @PathVariable("roleId") String roleId){  
         System.out.println("User Id : " + userId);  
         System.out.println("Role Id : " + roleId);  
         return "hello";  
     }  
     @RequestMapping(value="/product/{productId}",method = RequestMethod.GET)  
     public String getProduct(@PathVariable("productId") String productId){  
           System.out.println("Product Id : " + productId);  
           return "hello";  
     }  
     @RequestMapping(value="/javabeat/{regexp1:[a-z-]+}",  
           method = RequestMethod.GET)  
     public String getRegExp(@PathVariable("regexp1") String regexp1){  
           System.out.println("URI Part 1 : " + regexp1);  
           return "hello";  
     }  
}
@ResponseBody

作用于方法上,可以将整个返回结果以某种格式返回,如json或xml格式。

@ModelAttribute

该Controller的所有方法在调用前,先执行此@ModelAttribute方法,可用于注解和方法参数中,可以把这个@ModelAttribute特性,应用在BaseController当中,所有的Controller继承BaseController,即可实现在调用Controller时,先执行@ModelAttribute方法。

三、数据绑定与自定义类型转换器

 

 

 

 

四、@InitBinder注解

该注解被解析的时机,是该匹配Controller的请求执行映射的方法之前; 同时 @InitBinder标注的方法执行是多次的,一次请求来就执行一次。

当某个Controller上的第一次请求由SpringMvc前端控制器匹配到该Controller之后,根据Controller的 class 类型 查找 所有方法上标注了@InitBinder的方法,并且存入RequestMappingHandlerAdapter的 initBinderCache,下次一请求执行对应业务方法之前时,可以走initBinderCache缓存,而不用再去解析@InitBinder; 所以 initBinder是controller级别的,一个controller实例中的所有@initBinder 只对该controller有效;

注册Controller级别的 MVC属性编辑器 (属性编辑器功能就是将Web请求中的属性转成我们需要的类型) 

@InitBinder唯一的一个属性value,作用是限制对哪些 @RequestMapping 方法起作用,具体筛选条件就是通过@RequestMapping方法入参来筛选,默认不写就代表对所有@RequestMapping的方法起作用。

@InitBinder标注的方法, 方法入参和 @RequestMapping方法入参可选范围一样(这里指的是比如HttpServletRequest、ModelMap这些), 通常一个入参 WebDataBinder 就够我们使用了; @InitBinder标注的方法返回值, 必须为null,这里我理解的是运行期的返回值;如果运行时返回值不为null,抛出异常 “@InitBinder methods should return void:”,编译时IDEA会提示@InitBinder应该返回null,但是不影响编译通过;

@InitBinder
public  void initBinder(WebDataBinder binder, HttpServletRequest request){
    System.out.println(request.getParameter("date"));
    binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("MM-dd-yyyy"),false));
}

上面是一个@InitBinder的简单用法, 其中binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("MM-dd-yyyy"),false)); 这样一句话,作用就是将 自定义的MVC属性编辑器PropertyEditor 注册到当前binder的typeConverter的customEditors集合中,每一次请求和后端交互,每一个Controller方法入参都会创建一个Binder对象,binder对象相当于来完成请求和后端之间参数类型转换的职能类;  注意,每次请求都会创建新的binder对象,就是说上次请求的customEditors不可复用 , 每次执行都会添加到当前方法入参交互的binder的customEditors中,而且每次执行真正请求方法之前,会把 匹配上的@InitBinder标注的方法执行一遍才开始处理。

全局级别(所有@Controller)的属性编辑器

xml中注册全局属性编辑器到 ConfigurableWebBindingInitializer上,再将其注册到 RequestMappingHandlerAdapter里;绑定binder的属性编辑器时候,会将当前的 Initializer中的属性编辑器也给注册到binder中,这样就能实现全局的属性编辑器。

<!--<mvc:annotation-driven/>-->
<!--取消注解驱动的话Spring4.3就要手动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter-->

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="webBindingInitializer" ref="initializer1"/>
</bean>

<bean id="initializer1" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    <property name="propertyEditorRegistrars">
       <list>
            <bean class="demo2.MyPropertyEditor"/>
       </list>
    </property>
</bean>
public class MyPropertyEditor implements PropertyEditorRegistrar {
 
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Date.class,new MyDateEditor());
    }
 
    public static class MyDateEditor extends PropertyEditorSupport {
        @Override
        public void setValue(Object value) {
            super.setValue(value);
        }
 
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            Date d=null;
            try {
                System.out.println("我调用自己的全局MVC属性编辑器");
                d=new SimpleDateFormat("MM-dd-yyyy").parse(text);
                setValue(d);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
}

上面两段代码就可以注册自定义的属性编辑器到 所有@Controller中,相当于之前每个 Controller都使用了 @InitBinder;  但是这样写不太友好,SpringMVC<mvc:annotation-driven/>替我们注册的很多东西可能就没法使用了,意义不大,所以简单改造了一下: 在Spring加载初始化容器的流程基础上改造了下:

 

Spring  XML文件仍然使用注解驱动:

<mvc:annotation-driven/>
<bean id="globalBeanDefinitionRegistry" class="demo2.GlobalBeanDefinitionRegistry">
    <property name="editorRegistrars">
        <list>
            <bean class="demo2.MyPropertyEditor"/>
        </list>
    </property>
</bean>

自定义的 GlobalBeanDefinitionRegistry代码如下:

public class GlobalBeanDefinitionRegistry  implements BeanDefinitionRegistryPostProcessor {
        private PropertyEditorRegistrar[]  editorRegistrars;
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if(registry.containsBeanDefinition("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter")){
            BeanDefinition beanDefinition = registry.getBeanDefinition("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter");
            PropertyValue pv = beanDefinition.getPropertyValues().getPropertyValue("webBindingInitializer");
            BeanDefinition intializer= (BeanDefinition) pv.getValue();
            intializer.getPropertyValues().addPropertyValue("propertyEditorRegistrars",editorRegistrars);
        }
    }
 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
 
    }
    public void setEditorRegistrars(PropertyEditorRegistrar[] editorRegistrars) {
        this.editorRegistrars = editorRegistrars;
    }
}

五、数据校验

JSR 303

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中。
JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。

Hibernate Validator 扩展注解

Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:

 

Spring MVC 数据校验 
  • Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。
  • Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验。
  • Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中。
  • Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者的 jar 包放到类路径下。
  • <mvc:annotation-driven/> 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作。
  • 在已经标注了 JSR303 注解的表单/命令对象前标注一个 @Valid,Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验。
  • Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中。
  • 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参。
  • Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)。
  • BindingResult 扩展了 Errors 接口。

在目标方法中获取校验结果

在表单/命令对象类的属性中标注校验注解,在处理方法对应的入参前添加 @Valid,Spring MVC 就会实施校验并将校验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。
常用方法:
FieldError getFieldError(String field)
List<FieldError> getFieldErrors()
Object getFieldValue(String field)
int getErrorCount()

在页面上显示错误 

Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”。
即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。
隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息。
在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息。 

六、MVC 配置接口WebMvcConfigurer

WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;

官方推荐直接实现WebMvcConfigurer接口。

WebMvcConfigurer接口
public interface WebMvcConfigurer {
    void configurePathMatch(PathMatchConfigurer var1);
 
    void configureContentNegotiation(ContentNegotiationConfigurer var1);
 
    void configureAsyncSupport(AsyncSupportConfigurer var1);
 
    void configureDefaultServletHandling(DefaultServletHandlerConfigurer var1);
 
    void addFormatters(FormatterRegistry var1);
 
    void addInterceptors(InterceptorRegistry var1);
 
    void addResourceHandlers(ResourceHandlerRegistry var1);
 
    void addCorsMappings(CorsRegistry var1);
 
    void addViewControllers(ViewControllerRegistry var1);
 
    void configureViewResolvers(ViewResolverRegistry var1);
 
    void addArgumentResolvers(List<HandlerMethodArgumentResolver> var1);
 
    void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> var1);
 
    void configureMessageConverters(List<HttpMessageConverter<?>> var1);
 
    void extendMessageConverters(List<HttpMessageConverter<?>> var1);
 
    void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> var1);
 
    void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> var1);
 
    Validator getValidator();
 
    MessageCodesResolver getMessageCodesResolver();
}

常用的方法:

/* 拦截器配置 */
void addInterceptors(InterceptorRegistry var1);
/* 视图跳转控制器 */
void addViewControllers(ViewControllerRegistry registry);
/*静态资源处理*/
void addResourceHandlers(ResourceHandlerRegistry registry);
/* 默认静态资源处理器 */
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
/* 这里配置视图解析器*/
void configureViewResolvers(ViewResolverRegistry registry);
/* 配置内容裁决的一些选项*/
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
/* 解决跨域问题 */
public void addCorsMappings(CorsRegistry registry) ;
addInterceptors:拦截器
  • addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
  • addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns("/**")对所有请求都拦截
  • excludePathPatterns:用于设置不需要拦截的过滤规则

拦截器主要用途:进行用户登录状态的拦截,日志的拦截等。

@Override
public void addInterceptors(InterceptorRegistry registry) {
    super.addInterceptors(registry);
    registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**").excludePathPatterns("/emp/toLogin","/emp/login","/js/**","/css/**","/images/**");
}
addViewControllers:页面跳转

以前写SpringMVC的时候,如果需要访问一个页面,必须要写Controller类,然后再写一个方法跳转到页面,感觉好麻烦,其实重写WebMvcConfigurer中的addViewControllers方法即可达到效果了

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/toLogin").setViewName("login");
}
addResourceHandlers:静态资源

我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可。

注:如果继承WebMvcConfigurationSupport类实现配置时必须要重写该方法。

  • addResoureHandler:指的是对外暴露的访问路径
  • addResourceLocations:指的是内部文件放置的目录
configureDefaultServletHandling:默认静态资源处理器
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        configurer.enable("defaultServletName");
}

此时会注册一个默认的Handler:DefaultServletHttpRequestHandler,这个Handler也是用来处理静态文件的,它会尝试映射/。当DispatcherServelt映射/时,并且没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler 来处理。注意:这里的静态资源是放置在web根目录下,而非WEB-INF 下。

例如:在webroot目录下有一个图片:1.png 我们知道Servelt规范中web根目录(webroot)下的文件可以直接访问的,但是由于DispatcherServlet配置了映射路径是:/ ,它几乎把所有的请求都拦截了,从而导致1.png 访问不到,这时注册一个DefaultServletHttpRequestHandler 就可以解决这个问题。其实可以理解为DispatcherServlet破坏了Servlet的一个特性(根目录下的文件可以直接访问),DefaultServletHttpRequestHandler是帮助回归这个特性的。

configureViewResolvers:视图解析器

这个方法是用来配置视图解析器的,该方法的参数ViewResolverRegistry 是一个注册器,用来注册你想自定义的视图解析器等。

/**
 * 配置请求视图映射
 * @return
 */
@Bean
public InternalResourceViewResolver resourceViewResolver()
{
    InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
    //请求视图文件的前缀地址
    internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
    //请求视图文件的后缀
    internalResourceViewResolver.setSuffix(".jsp");
    return internalResourceViewResolver;
}
 
/**
 * 视图配置
 * @param registry
 */
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
    super.configureViewResolvers(registry);
    registry.viewResolver(resourceViewResolver());
    /*registry.jsp("/WEB-INF/jsp/",".jsp");*/
}
configureContentNegotiation:配置内容协商的一些参数

在 HTTP 协议中,内容协商是这样一种机制,通过为同一 URI 指向的资源提供不同的展现形式,可以使用户代理选择与用户需求相适应的最佳匹配(例如,文档使用的自然语言,图片的格式,或者内容编码形式)。

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    /* 是否通过请求Url的扩展名来决定media type */
    configurer.favorPathExtension(true)
            /* 不检查Accept请求头 */
            .ignoreAcceptHeader(true)
            .parameterName("mediaType")
            /* 设置默认的media yype */
            .defaultContentType(MediaType.TEXT_HTML)
            /* 请求以.html结尾的会被当成MediaType.TEXT_HTML*/
            .mediaType("html", MediaType.TEXT_HTML)
            /* 请求以.json结尾的会被当成MediaType.APPLICATION_JSON*/
            .mediaType("json", MediaType.APPLICATION_JSON);
}
addCorsMappings:跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
    super.addCorsMappings(registry);
    registry.addMapping("/cors/**")
            .allowedHeaders("*")
            .allowedMethods("POST","GET")
            .allowedOrigins("*");
}
configureMessageConverters:信息转换器
/**
* 消息内容转换配置
 * 配置fastJson返回json转换
 * @param converters
 */
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //调用父类的配置
    super.configureMessageConverters(converters);
    //创建fastJson消息转换器
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
    //创建配置类
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    //修改配置返回内容的过滤
    fastJsonConfig.setSerializerFeatures(
            SerializerFeature.DisableCircularReferenceDetect,
            SerializerFeature.WriteMapNullValue,
            SerializerFeature.WriteNullStringAsEmpty
    );
    fastConverter.setFastJsonConfig(fastJsonConfig);
    //将fastjson添加到视图消息转换器列表内
    converters.add(fastConverter);
 
}

 

posted @ 2020-11-23 14:59  codedot  阅读(205)  评论(0编辑  收藏  举报