Spring Core 官方文档阅读笔记(十八)

1. CORS

跨域资源共享(Cross-Origin Resource Sharing,CORS)是大多数浏览器实现的W3C规范,它允许指定授权的跨域请求的类型,也就是用来解决我们经常会遇到的跨域问题。

Spring MVC的HandlerMapping实现提供了对CORS的内置支持,在成功地将请求映射到处理程序之后,HandlerMapping实现检查给定请求和处理程序的CORS配置并采取进一步的操作。

我们可以通过全局配置来实现CORS,或者使用@CrossOrigin注解来对特定请求做处理。

  • @CrossOrigin
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

上面这段摘自Spring官方示例,此时,被@CrossOrigin注解的retrieve方法就允许跨域请求。@CrossOrigin默认允许所有Origin,以及任意的请求头和HttpMethod,但是不会启用allowedCredentials,因为会创建一个信任级别,并公开用户的一些敏感信息。不过,我们可以使用maxAge属性来设置有效期,默认是30分钟。@CrossOrigin也可以被标注在类上:

@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

也可以这样:

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("http://domain2.com")
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}
  • 全局配置
    通常情况下,全局配置的CORS也支持全部Origin,任意的请求头,但是只支持GET、HEAD和POST请求。同样不会默认启用allowedCredentials,并且默认有效期是30分钟。

我们看一下具体怎么配置,先来看看怎么使用java bean来配置:

@Configuration
public class WebMvcAdapter implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/mvc/**")
                .allowedHeaders("Content-Type")
                .allowedMethods("PUT", "POST", "GET", "DELETE")
                .allowedOrigins("*")
                .allowCredentials(true)
                .maxAge(36000);
    }
}

那么相应的XML配置就可以这么来写:

<mvc:cors>

    <mvc:mapping path="/mvc/**"
        allowed-origins="*"
        allowed-methods="GET, PUT, POST, DELETE"
        allowed-headers="Content-Type"
        exposed-headers="header1, header2" 
        allow-credentials="true"
        max-age="36000" />

    <mvc:mapping path="/resources/**"
        allowed-origins="http://domain1.com" />

</mvc:cors>

除了上述两种方法外,还可以通过配置CORS过滤器来达到跨域效果:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // by default uses a Bean by the name of corsConfigurationSource
            .cors().and()
            ...
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

这个配置要在Spring Security中进行,后面整理到安全相关的内容时详细介绍。

也可以在SpringMVC中这样配置:

/**
 * 跨域过滤器
 */
@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/app/**", buildConfig());
    source.registerCorsConfiguration("/api/**", buildConfig());
    return new CorsFilter(source);
}

private CorsConfiguration buildConfig() {
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.addAllowedOrigin("*");
    corsConfiguration.addAllowedHeader("*");
    corsConfiguration.addAllowedMethod("*");
    return corsConfiguration;
}

2. CacheControl

直接看代码吧。。。

// 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();

也可以在Controller里直接设置:

    @GetMapping("/getUserInfo")
    public ResponseEntity<UserInfo> getUserInfo() {
        UserInfo userInfo = new UserInfo();
        // set userInfo ...
        return ResponseEntity
                .ok()
                .cacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS))
                .eTag(UUID.randomUUID().toString())
                .body(userInfo);
    }

3. Enable MVC Configuration

@EnableWebMvc注解可以启用MVC配置:

@Configuration
@EnableWebMvc
public class WebConfig{
    
}

上面的配置就等同于:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

4. MVC Config API

我们可以实现WebMvcConfigurer接口:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    // Implement configuration methods...
}

为什么要实现这个接口呢?我们看一下这个接口的javadoc:

/**
 * Defines callback methods to customize the Java-based configuration for
 * Spring MVC enabled via {@code @EnableWebMvc}.
 *
 * <p>{@code @EnableWebMvc}-annotated configuration classes may implement
 * this interface to be called back and given a chance to customize the
 * default configuration.
 *
 * @author Rossen Stoyanchev
 * @author Keith Donald
 * @author David Syer
 * @since 3.1
 */
public interface WebMvcConfigurer {

    ...
}

简单说就是,这个接口定义了回调方法,为通过@EnableWebMvc启用的SpringMVC定制基于java的配置。可以理解为,只有实现了这个接口中的方法,我们才能自定义SpringMVC的配置。否则,就是使用默认配置。

5. Type Conversion

默认情况下,会安装数字和日期类型的格式化程序,包括对@NumberFormat和@DateTimeFormat注释的支持。如果类路径上存在Joda-time,则还会安装对Joda-time格式库的支持。

在Java配置中,可以注册自定义格式化程序和转换器,如以下示例所示:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

等同于下面的xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven conversion-service="conversionService"/>

    <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.example.MyConverter"/>
            </set>
        </property>
        <property name="formatters">
            <set>
                <bean class="org.example.MyFormatter"/>
                <bean class="org.example.MyAnnotationFormatterFactory"/>
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.example.MyFormatterRegistrar"/>
            </set>
        </property>
    </bean>

</beans>

6. Validation

默认情况下,如果类路径上存在Bean验证(例如,Hibernate Validator),则LocalValidatorFactoryBean注册为全局Validator,以便与Controller中标注@Valid和Valified的方法参数一起使用。

在Java配置中,可以自定义全局Validator实例,如下例所示:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public Validator getValidator(); {
        // ...
    }
}

等同于下面的xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

也可以通过@InitBinder来注册验证器:

@Controller
public class MyController {

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

}

7. Interceptors

添加拦截器,看代码:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

等同于xml:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

8. Content Types

可以配置Spring MVC请求的媒体类型,默认先检查url path的后缀扩展名,其中与json、xml、atom和rss相关的,直接注册为相应的媒体类型,然后再检查Accept头部信息。

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }
}

xml

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

9. Message Converters

可以通过覆盖configureMessageConverters()(替换Spring MVC创建的默认转换器)或覆盖extendMessageConverters()(自定义默认转换器或向默认转换器添加其他转换器)来自定义Java配置中的HttpMessageConverter。

以下示例使用自定义的ObjectMapper(而不是默认的ObjectMapper)添加XML和Jackson JSON转换器:

@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(new ParameterNamesModule());
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
    }
}

等同于xml:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

10. View Controller

SpringMVC提供了ViewController功能,可以简化单纯的页面跳转的处理过程。我们先来看下常规的页面跳转:

@Controller
public class Simple {
    
    @RequestMapping("/toIndex")
    public String index() {
        return "index";
    }
}

上述代码中,toIndex请求只是为了单纯的页面跳转,而如果一个工程中这种跳转的需求很多的话,就会出现很多重复的代码,我们可以用ViewController来简化处理:

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/toIndex").setViewName("index");
    }
}

也可以使用xml来配置:

<mvc:view-controller path="/" view-name="home"/>

11. View Resolvers

Spring MVC简化了视图解析器的配置过程,可以按照下面的方式来配置:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp();
    }
}

xml:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

但是要注意,FreeMarker、Tiles、Groovy标记和脚本模板也需要配置底层视图技术。

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.freeMarker().cache(false);
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/freemarker");
        return configurer;
    }
}

xml:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

12. Static Resource

当需要访问静态资源时,可以按照下面的写法:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
            .addResourceLocations("/public", "classpath:/static/")
            .setCachePeriod(31556926)
            .resourceChain(true)
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }
}

上面的写法等同于xml:

<mvc:resources mapping="/resources/**" location="/public/">
    <mvc:resource-chain>
        <mvc:resource-cache/>
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/**"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

13. DefaultServlet

spring MVC允许将DispatcherServlet映射到/(从而覆盖容器的默认servlet的映射),同时仍然允许容器的默认servlet处理静态资源请求。它配置了一个DefaultServletHttpRequestHandler,其URL映射为/*,并且相对于其他URL映射的优先级最低。

此处理程序将所有请求转发到默认servlet。因此,它必须保持在所有其他URL HandlerMappings顺序的最后。如果使用<mvc:annotation-Driven>,就会出现这种情况。或者,如果您设置了自己的自定义HandlerMapping实例,请确保将其Order属性设置为低于DefaultServletHttpRequestHandler的值,即Integer.MAX_VALUE。

以下示例显示如何使用默认设置启用该功能:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

xml:

<mvc:default-servlet-handler/>

也可以指定具体某个servlet:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable("MyServlet");
    }
}

xml:

<mvc:default-servlet-handler default-servlet-name="MyServlet"/>

14. Patch Matching

可以自定义与URL的路径匹配和处理相关的选项。

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public UrlPathHelper urlPathHelper() {
        //...
    }

    @Bean
    public PathMatcher antPathMatcher() {
        //...
    }

}

xml:

<mvc:annotation-driven>
    <mvc:path-matching
        suffix-pattern="true"
        trailing-slash="false"
        registered-suffixes-only="true"
        path-helper="pathHelper"
        path-matcher="pathMatcher"/>
</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

15. Advanced Java Config

@EnableWebMvc会导入DelegatingWebMvcConfiguration:

  • 为Spring MVC应用程序提供默认Spring配置。
  • 检测并委派WebMvcConfigurer实现来自定义该配置。

对于高级模式,您可以删除@EnableWebMvc并直接从DelegatingWebMvcConfiguration扩展,而不是实现WebMvcConfigurer,如下例所示:

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    // ...

}

可参照@EnableWebMvc的原理
以及Spring MVC 代理配置类 DelegatingWebMvcConfiguration

posted @ 2020-05-22 10:22  kuromaru  阅读(240)  评论(0)    收藏  举报