1. 程式人生 > >Spring Web MVC框架(八) 配置Spring Web MVC

Spring Web MVC框架(八) 配置Spring Web MVC

這一篇文章對應於Spring參考文件 Configuring Spring MVC,講的是Spring Web MVC各部分的配置方法,包括Java程式碼配置和XML檔案配置以及MVC名稱空間的使用方法。

啟用MVC Java配置和XML名稱空間

預設配置

要啟用MVC Java配置(@Controller等各種註解)和XML名稱空間,如果使用的是Java配置,在配置類上再新增@EnableWebMvc註解即可。

@Configuration
@EnableWebMvc
public class WebAppConfig {

}

如果使用XML配置檔案的話,新增下面一行即可。

<mvc:annotation-driven/>

不論使用哪種方式,都會在Spring中註冊一些元件來提供最基本的MVC功能。這些功能在文件中說的很清楚。我簡單翻譯了一下:

上面的配置會註冊一個RequestMappingHandlerMapping,一個RequestMappingHandlerAdapter和一個ExceptionHandlerExceptionResolver來提供註解控制器和註解方法(比如@RequestMapping和@ExceptionHandler等)處理請求的功能。

還會啟用以下功能:

  • 通過一個ConversionService
    例項,來進行Spring 3 方式的型別轉換及資料繫結支援。
  • @NumberFormat格式化數字欄位的支援
  • @DateTimeFormat格式化DateCalendarLong、JodaTime型別欄位的支援。
  • 在控制器方法上使用@Valid驗證Bean的支援,如果檢測到JSR-303 Bean驗證的實現。

通過這些預設配置,我們即可開始最基本的Spring MVC使用。

自定義配置

上面提供了最基本的配置。如果需要自定義某些配置也可以。如果使用Java配置的話,讓配置類實現WebMvcConfigurer介面,更常用的辦法是繼承WebMvcConfigurerAdapter

基類,通過重寫基類中的方法即可配置相關功能。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

        //有很多個方法可以重寫,來提供自定義功能

}

如果使用XML配置檔案,通過IDE的自動補全功能檢視一下<mvc:annotation-driven/>有哪些子屬性和子元素。

型別轉換和格式化

預設情況下Spring註冊了Number(包括所有基本數字型別)和java.util.Date的型別轉換和格式化功能。要提供型別的轉換和格式化功能,就需要自己註冊相應的型別轉換器和格式化器。

如果使用Java配置的話,重寫addFormatters(FormatterRegistry registry)方法並新增相應功能即可。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // Add formatters and/or converters
    }

}

如果使用XML配置的話,需要註冊一個ConversionService,然後新增到<mvc:annotation-driven>節點中。

    <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>

驗證功能

Spring自己提供了一組介面和類提供了一套驗證功能。不過更通用的方法是使用Bean Validation進行Java物件的驗證,Bean Validation的一個實現就是Hibernate Validator。如果想簡單瞭解一下Hibernate Validator,可以看一下我的文章Hibernate Validator簡介。如果需要詳細使用方法請檢視相關文件部落格。

預設情況下當@EnableWebMvc<mvc:annotation-driven/>配置之後,如果Spring檢測到Bean Validation,就會自動註冊一個LocalValidatorFactoryBean來提供驗證功能。如果我們希望手動處理驗證過程,可能希望將驗證器例項注入到控制器中,這時候就不能使用自動註冊的LocalValidatorFactoryBean了。這時候我們可以選擇手動註冊一個LocalValidatorFactoryBeanBean例項,然後註解@Primary讓自定義LocalValidatorFactoryBean被優先使用。

還有一種辦法就是直接覆蓋Spring的預設驗證器配置。如果使用Java配置的話,重寫getValidator()方法即可。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

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

}

如果使用XML配置檔案,定義一個Validator然後新增到<mvc:annotation-driven>中。

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

上面定義的都是全域性驗證器,我們也可以在某個控制器中定義一個區域性驗證器,然後和全域性驗證器結合起來使用。這時候需要使用@InitBinder註解方法。

@Controller
public class MyController {

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

}

配置好驗證器之後。當Spring識別到@Valid註解的方法引數之後,就會執行驗證,將驗證結果繫結到BindingResult上,我們可以在方法中訪問BindingResult來獲取驗證結果。

攔截器

我們實現了攔截器之後,就可以將其應用到Web程式中。使用Java配置的話,重寫addInterceptors(InterceptorRegistry registry)方法,然後在其中新增自己的攔截器即可。如果要配置攔截路徑和排除路徑也可以在這裡配置。

@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配置檔案的話可以使用MVC名稱空間,配置也比較簡單。

<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>

檢視控制器

這是一種定義ParameterizableViewController的簡單方式,當該控制器被請求的時候不會執行任何邏輯操作,直接轉到相應檢視。檢視控制器的常見用法是將網站的首頁直接和/請求對映。

使用Java配置可以這樣寫,下面的配置將/對映到名為index的檢視。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

}

使用XML配置也很簡單。

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

檢視解析器

使用Java配置,只需要重寫configureViewResolvers(ViewResolverRegistry registry)方法即可。下面配置了JSP檢視。如果需要其它檢視解析器可以參見其相應文件,以及ViewResolverRegistry的JavaDoc。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp()
                .prefix("/WEB-INF/jsp/")
                .suffix(".jsp")
                .viewClass(JstlView.class);
    }

}

如果使用XML配置檔案可以使用MVC名稱空間簡化配置。除了內建的JSP解析器外,其它檢視解析器可能還需要額外的配置,這裡不再細述。

<mvc:view-resolvers>
    <mvc:jsp prefix="/WEB-INF/jsp/"
             suffix=".jsp"
             view-class="org.springframework.web.servlet.view.JstlView"/>
</mvc:view-resolvers>

資源處理

靜態資源處理

這裡說的主要是靜態資源的處理。前面說了很多關於控制器、檢視的知識,但是如何對映CSS、JS檔案,前面沒有說明。配置方法在這裡說明。

使用Java配置的話,重寫addResourceHandlers(ResourceHandlerRegistry registry)方法,然後新增相應的對映即可。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/resources/static/");
    }

}

使用XML配置檔案 也同樣簡單。

<mvc:resources mapping="/static/**" location="/resources/static/"/>

這樣對映之後,假如我們有/resources/static/bootstrap.css檔案,那麼就可以使用/static/bootstrap.css路徑來訪問該檔案了。同樣的在檢視檔案中也可以如此引用。還可以使用cache-period設定資源的過期時間,單位是秒。如果需要指定多個資源位置,可以使用逗號分隔。

資源的版本控制

有些頻繁更新的資源可能需要版本控制,強制讓客戶端使用最新的資源。Spring框架也支援資源的版本控制,我們需要定義資源鏈來實現這個功能。資源鏈由一個ResourceResolver例項和多個ResourceTransformer例項組成。內建的VersionResourceResolver能滿足我們的大部分需求,它可以定義一些策略來配置版本控制,例如FixedVersionStrategy會依據日期、版本號或者其他東西作為版本;ContentVersionStrategy會計算資源的MD5值。

ContentVersionStrategy策略是一個不錯的策略,不過由於它會計算MD5,所以開銷比較大, 因此在使用這種策略的時候最好開啟快取來提高效能。

如果使用Java配置的話,和前面的例子差不多,只不過需要多呼叫resourceChain(true)等方法並新增相應的版本資源解析器和版本策略。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

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

}

下面是使用XML配置的例子。

<mvc:resources mapping="/static/**" location="/resources/static/">
    <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>

預設Servlet

開啟這個選項可以讓DispatcherServlet處理根路徑/下的靜態資源請求,說的詳細點就是假如靜態檔案是webapp/css/site.css,那麼我們可以直接通過/css/site.css來訪問這個檔案。如果不啟用這個功能,那麼靜態檔案就只能對映到其他路徑下比如/static

這個配置項實際上會配置一個DefaultServletHttpRequestHandler,對映到路徑/**,並具有最低的優先順序。由於DefaultServletHttpRequestHandler會將所有請求轉發到預設Servlet,所以它必須被配置為最後一個處理對映才行。

使用Java配置的話,重寫configureDefaultServletHandling(...)方法並開啟該選項。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

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

}

如果使用XML配置的話,新增以下行。

<mvc:default-servlet-handler/>

訊息轉換

如果我們需要覆蓋Spring預設的訊息轉換器,可以重寫configureMessageConverters(List<HttpMessageConverter<?>> converters)方法,然後向converters引數新增我們自己的訊息轉換器。如果僅僅希望增加自己的型別轉換器,重寫extendMessageConverters()方法。

使用XML配置檔案的話,可以使用MVC名稱空間。

<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>

高階自定義配置

上面的配置使用Spring提供的簡化類或者MVC名稱空間,幫助我們快速配置功能。有時候可能需要更高階的功能定製,這樣就需要自己處理這些底層Bean的初始化和屬性設定。

Java配置自定義

我們先來看一看@EnableWebMvc註解的定義。可以看到它還使用了一個@Import註解,引用了DelegatingWebMvcConfiguration類。當我們註解@EnableWebMvc的時候,實際上初始化和配置的底層類就是DelegatingWebMvcConfiguration

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

因此我們如果要自定義MVC的話,第一件事就是移除註解@EnableWebMvc。然後繼承DelegatingWebMvcConfiguration類並實現它的requestMappingHandlerAdapter()方法。

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    @Override
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        // 在這裡進行高階配置
    }

}

在專案中DelegatingWebMvcConfiguration子類和@EnableWebMvc註解配置類只能存在一個,因為它們做的事情實際上是一樣的。而且這裡的配置並不影響Spring MVC的其他配置。

自定義MVC名稱空間配置

這裡的自定義配置更困難,因為Spring沒有提供相應的配置機制。如果實在需要自定義MVC名稱空間配置,可以考慮使用Spring提供的BeanPostProcessor機制,在檢測到Bean之後修改它的值。

@Component
public class MyPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        if (bean instanceof RequestMappingHandlerAdapter) {
            // 在這裡自定義屬性
        }
    }

}