Spring4.1新特性——Spring MVC增強


目錄

Spring4.1新特性——綜述

Spring4.1新特性——Spring核心部分及其他

Spring4.1新特性——Spring緩存框架增強

Spring4.1新特性——異步調用和事件機制的異常處理

Spring4.1新特性——數據庫集成測試腳本初始化

Spring4.1新特性——Spring MVC增強

Spring4.1新特性——頁面自動化測試框架Spring MVC Test HtmlUnit簡介

Spring4.1新特性——靜態資源處理增強

 

Spring 4.1對Spring MVC部分做的增強是最多的,提供了一些視圖解析器的mvc標簽實現簡化配置、提供了GroovyWebApplicationContext用於 Groovy web集成、提供了Gson、protobuf的HttpMessageConverter、提供了對groovy-templates模板的支持、 JSONP的支持、對Jackson的@JsonView的支持等。

 

1、GroovyWebApplicationContext 

在Spring 4.1之前沒有提供Web集成的ApplicationContext,在《Spring4新特性——Groovy Bean定義DSL》中我們自己去實現的com.sishuok.spring4.context.support.WebGenricGroovyApplicationContext,而4.1其已經提供了相應實現,直接把《Spring4新特性——Groovy Bean定義DSL》配置中的相應類改掉即可。

 

2、視圖解析器標簽

之前我們都是這樣定義視圖解析器:

    <bean id="mvcVelocityEngine" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">  
        <property name="resourceLoaderPath" value="/WEB-INF/vm/,classpath:com/github/zhangkaitao" />  
    </bean>  
    <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">  
        <property name="prefix" value=""/>  
        <property name="suffix" value=".vm"/>  
        <property name="cache" value="false"/>  
    </bean>  

而現在我們可以使用MVC標簽定義:

<mvc:velocity-configurer resource-loader-path="/WEB-INF/vm/,classpath:com/github/zhangkaitao"/>  
<mvc:view-resolvers>  
    <mvc:velocity cache-views="false" prefix="" suffix=".vm"/>  
</mvc:view-resolvers> 

再來看一個更復雜的例子:

<mvc:velocity-configurer resource-loader-path="/WEB-INF/vm/,classpath:com/github/zhangkaitao"/>  
<mvc:groovy-configurer resource-loader-path="classpath:templates/" cache-templates="false"/>  
<mvc:view-resolvers>  
    <mvc:content-negotiation>  
        <mvc:default-views>  
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">  
                <property name="jsonpParameterNames">  
                    <set>  
                        <value>jsonp</value>  
                        <value>callback</value>  
                    </set>  
                </property>  
            </bean>  
        </mvc:default-views>  
    </mvc:content-negotiation>  
    <mvc:velocity cache-views="false" prefix="" suffix=".vm"/>  
    <mvc:groovy cache-views="false" suffix=".tpl"/>  
</mvc:view-resolvers>

mvc:content-negotiation用於定義內容協商的視圖解析器,且內部可以定義默認視圖;然后我們又定義了mvc:velocity和mvc:groovy兩個視圖解析器;它們會按照順序進行解析。另外幾個視圖解析器是:

 

mvc:freemarker

mvc:bean-name

mvc:jsp

 

這種方式有一個很大的問題就是只能做默認配置,如果想自定義其屬性值就搞不定了,估計當時開發的人考慮不全或沒有經驗。

 

3、控制器標簽

Spring 4.1提供了更豐富的控制器標簽:

3.1、重定向視圖控制器標簽

    <mvc:redirect-view-controller  
            path="/redirect"  
            redirect-url="/status"  
            context-relative="true"  
            status-code="301"  
            keep-query-params="true"/>  

3.2、狀態控制器標簽

<mvc:status-controller path="/status" status-code="200"/> 

3.3、帶狀態的視圖控制器標簽

    <mvc:view-controller path="/error/**" status-code="200"/>  

4、Groovy Template引擎集成

Spring 4.1提供了對Groovy Template模板引擎的集成,其是一種DSL風格的模板引擎,其也是最早在Spring Boot中引入的。

4.1、Spring配置文件

    <mvc:groovy-configurer resource-loader-path="classpath:templates/" cache-templates="false"/>  
    <mvc:view-resolvers>  
        <mvc:groovy cache-views="false" suffix=".tpl"/>  
    </mvc:view-resolvers>  

4.2、模板heelo.tpl

    yieldUnescaped '<!DOCTYPE html>'  
    html {  
      head {  
        title('hello groovy templates')  
      }  
      body {  
          div("hello $user.name")  
      }  
    }  

具體語法請參考官方文檔。

 

5、 Jackson @JsonView支持 

可以使用@JsonView來分組渲染JSON數據,按需展示JSON數據。

5.1、模型

    public class User implements Serializable {  
        public static interface OnlyIdView {}  
        public static interface OnlyNameView {}  
        public static interface AllView extends OnlyIdView, OnlyNameView {}  
      
        @JsonView(OnlyIdView.class)  
        private Long id;  
      
        @JsonView(OnlyNameView.class)  
        private String name;    
        ……  
    }  

定義了三個視圖:OnlyIdView、OnlyNameView和AllView。

 

5.2、控制器

    @RestController  
    public class JacksonJsonViewController {  
      
        @RequestMapping("/jackson1")  
        @JsonView(User.OnlyIdView.class)  
        public User test1() {  
            return new User(1L, "zhangsan");  
        }  
      
        @RequestMapping("/jackson2")  
        @JsonView(User.OnlyNameView.class)  
        public User test2() {  
            return new User(1L, "zhangsan");  
        }  
      
        @RequestMapping("/jackson3")  
        @JsonView(User.AllView.class) //可以省略  
        public User test3() {  
            return new User(1L, "zhangsan");  
        }  
    }  

使用@JsonView控制渲染哪些數據。

 

6、Jsonp支持  

6.1、MappingJackson2JsonView提供的支持

<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">  
    <property name="jsonpParameterNames">  
        <set>  
            <value>jsonp</value>  
            <value>callback</value>  
        </set>  
   </property>  
</bean> 

然后訪問如http://localhost:8080/json?callback=callback即可得到JSONP響應:callback({"user":{"id":1,"name":"zhangsan"}});。

 

6.2、對使用HttpMessageConverter的@ResponseBody的支持

    @Order(2)  
    @ControllerAdvice(basePackages = "com.github")  
    public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {  
        public JsonpAdvice() {  
            super("callback", "jsonp"); //指定jsonpParameterNames  
        }  
    }  

訪問http://localhost:8080/jackson1?callback=callback即可看到JSONP響應。 

 

@ContollerAdvice的作用請參考《Spring3.2新注解@ControllerAdvice》,basePackages用於指定對哪些包里的Controller起作用。

 

6.3、ResponseBodyAdvice

我們之前實現的JsonpAdvice其繼承自AbstractJsonpResponseBodyAdvice,而AbstractJsonpResponseBodyAdvice繼承自ResponseBodyAdvice,其作用是在響應體寫出之前做一些處理:

    @Order(1)  
    @ControllerAdvice(basePackages = "com.github")  
    public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {  
      
        @Override  
        public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {  
            return methodParameter.getMethod().getReturnType().isAssignableFrom(User.class);  
        }  
      
        @Override  
        public Object beforeBodyWrite(  
                Object obj, MethodParameter methodParameter, MediaType mediaType,  
                Class<? extends HttpMessageConverter<?>> converterType,  
                ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {  
      
            User user = ((User)obj);  
            user.setName("---" + user.getName() + "---");  
            return user;  
        }  
    }  

1、supports指定支持哪些類型的方法進行處理,此處是返回值為User的;2、我們得到User對象然后在名字前后拼上”---“ ;3、可以指定多個ResponseBodyAdvice,使用@Order指定順序。訪問http://localhost:8080 /jackson2?callback=callback可以看到效果。

 

7、Gson HttpMessageConverter

7.1、Spring配置

    <mvc:annotation-driven>  
        <mvc:message-converters>  
            <bean class="org.springframework.http.converter.json.GsonHttpMessageConverter"/>  
        </mvc:message-converters>  
    </mvc:annotation-driven>  

使用方式和Jackson Json類似。本文使用的是<gson.version>2.2.4</gson.version>版本。

 

8、Protobuf HttpMessageConverter

8.1、Spring配置

<mvc:annotation-driven>  
    <mvc:message-converters>  
        <bean class="org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter">  
            <constructor-arg>  
                <bean class="com.github.zhangkaitao.web.controller.MyExtensionRegistryInitializer"/>  
            </constructor-arg>  
        </bean>  
    </mvc:message-converters>  
</mvc:annotation-driven>

8.2、定義protobuf message(proto/user.proto)

    package com.github.zhangkaitao.pb;  
       
     option java_package = "com.github.zhangkaitao.pb";  
     option java_outer_classname = "UserProtos";  
       
     message User {  
       optional int64 id = 1;  
       optional string name = 2;  
     }  

8.3、添加maven插件自動把protobuf message轉化成Java代碼

    <plugin>  
        <groupId>com.google.protobuf.tools</groupId>  
        <artifactId>maven-protoc-plugin</artifactId>  
        <version>0.1.10</version>  
        <executions>  
            <execution>  
                <id>generate-sources</id>  
                <goals>  
                    <goal>compile</goal>  
                </goals>  
                <phase>generate-sources</phase>  
                <configuration>  
                    <protoSourceRoot>${basedir}/src/main/proto/</protoSourceRoot>  
                    <includes>  
                        <param>**/*.proto</param>  
                    </includes>  
                </configuration>  
            </execution>  
        </executions>  
        <configuration>  
            <protocExecutable>D:/software/protoc.exe</protocExecutable>  
        </configuration>  
    </plugin>  

8.4、測試控制器

    @RestController  
    public class ProtobufController {  
        @RequestMapping("/proto/read")  
        public ResponseEntity<UserProtos.User> protoRead() {  
            return ResponseEntity.ok(UserProtos.User.newBuilder().setId(1).setName("zhangsan").build());  
        }  
        @RequestMapping("/proto/write")  
        public ResponseEntity<UserProtos.User> protoRead(RequestEntity<UserProtos.User> requestEntity) {  
            System.out.println("server===\n" + requestEntity.getBody());  
            return ResponseEntity.ok(requestEntity.getBody());  
        }  
    }  

8.5、測試用例(com.github.zhangkaitao.proto.ProtoTest) 

    @Test
    public void testRead() {
        HttpHeaders headers = new HttpHeaders();
        RequestEntity<UserProtos.User> requestEntity =
                new RequestEntity<UserProtos.User>(headers, HttpMethod.POST, URI.create(baseUri + "/proto/read"));

        ResponseEntity<UserProtos.User> responseEntity =
                restTemplate.exchange(requestEntity, UserProtos.User.class);

        System.out.println(responseEntity.getBody());
    }

    @Test
    public void testWrite() {
        UserProtos.User user = UserProtos.User.newBuilder().setId(1).setName("zhangsan").build();
        HttpHeaders headers = new HttpHeaders();
        RequestEntity<UserProtos.User> requestEntity =
                new RequestEntity<UserProtos.User>(user, headers, HttpMethod.POST, URI.create(baseUri + "/proto/write"));

        ResponseEntity<UserProtos.User> responseEntity =
                restTemplate.exchange(requestEntity, UserProtos.User.class);
        System.out.println(responseEntity.getBody());
    }

測試用例知識請參考《Spring MVC測試框架詳解——服務端測試》和《Spring MVC測試框架詳解——客戶端測試》。

測試過程中會拋出:

Caused by: java.lang.UnsupportedOperationException
    at java.util.Collections$UnmodifiableMap.put(Collections.java:1342)
    at org.springframework.http.HttpHeaders.set(HttpHeaders.java:869)
    at org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter.setProtoHeader(ProtobufHttpMessageConverter.java:196)

這是因為ProtobufHttpMessageConverter會修改響應頭,但是ResponseEntity構造時HttpHeaders是不允許修改的。暫時解決辦法是注釋掉:

//setProtoHeader(outputMessage, message);

9、RequestEntity/ResponseEntity

Spring 4.1提供了ResponseEntity配對的RequestEntity,使用方式和HttpEntity一樣。具體可以參考 com.github.zhangkaitao.web.controller.RequestResponseEntityController。

 

10、MvcUriComponentsBuilder

其作用可以參考《Spring4新特性——注解、腳本、任務、MVC等其他特性改進》,Spring 4.1又提供了一個新的方法MvcUriComponentsBuilder.fromMappingName用於根據控制器方法來生成請求URI。

@RestController
public class MvcUriComponentsBuilderController {

    @RequestMapping("/uri")
    public String mvcUriComponentsBuilder1() {
        return MvcUriComponentsBuilder.fromMappingName("MUCBC#mvcUriComponentsBuilder1").build();
    }
    @RequestMapping("/uri/{id}")
    public String mvcUriComponentsBuilder2(@PathVariable Long id) {
        return MvcUriComponentsBuilder.fromMappingName("MUCBC#mvcUriComponentsBuilder2").arg(0, "123").build();
    }
}

規則是“控制器所有大寫字母#方法名”找到相應的方法。 另外可以直接在頁面中使用如下方式獲取相應的URI:

${s:mvcUrl('MUCBC#mvcUriComponentsBuilder2').arg(0,"123").build()}

如上方式只能在正常EL 3.0的容器中運行,可參考《Expression Language 3.0新特性》。 

 

11、MockRestServiceServer

MockRestServiceServer目前提供了對AsyncRestTemplate的支持,使用方式和RestTemplate一樣。可參考《Spring MVC測試框架詳解——客戶端測試》。

 

12、MockMvcConfigurer

Spring 4.1提供了MockMvcConfigurer用於進行一些通用配置,使用方式如下:

mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(defaultSetup()).build();&nbsp;

MockMvcConfigurer實現:

    private MockMvcConfigurer defaultSetup() {
        return new MockMvcConfigurer() {
            @Override
            public void afterConfigurerAdded(ConfigurableMockMvcBuilder<?> configurableMockMvcBuilder) {
                configurableMockMvcBuilder.alwaysExpect(status().isOk());
            }
            @Override
            public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> configurableMockMvcBuilder, WebApplicationContext webApplicationContext) {
                return new RequestPostProcessor() {
                    @Override
                    public MockHttpServletRequest postProcessRequest(MockHttpServletRequest mockHttpServletRequest) {
                        mockHttpServletRequest.setAttribute("aa", "aa");
                        return mockHttpServletRequest;
                    }
                };
            }
        };
    }

可以在如上實現中進行一些通用配置,如安全(往Request中扔安全對象之類的)。測試用例可參考com.github.zhangkaitao.proto.ProtoTest2。

 

 

相關文章

http://beta.groovy-lang.org/docs/groovy-2.3.0-SNAPSHOT/html/documentation/markup-template-engine.html

https://spring.io/blog/2014/05/28/using-the-innovative-groovy-template-engine-in-spring-boot

Spring4新特性——Groovy Bean定義DSL

Spring3.2新注解@ControllerAdvice

Spring MVC測試框架詳解——服務端測試

Spring MVC測試框架詳解——客戶端測試

Spring4新特性——注解、腳本、任務、MVC等其他特性改進

 

Spring4新特性

Spring4新特性——泛型限定式依賴注入

Spring4新特性——核心容器的其他改進

Spring4新特性——Web開發的增強

Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC 

Spring4新特性——Groovy Bean定義DSL

Spring4新特性——更好的Java泛型操作API 

Spring4新特性——JSR310日期API的支持

Spring4新特性——注解、腳本、任務、MVC等其他特性改進 

 

源碼下載

https://github.com/zhangkaitao/spring4-1-showcase/tree/master/spring4.1-groovy

https://github.com/zhangkaitao/spring4-1-showcase/tree/master/spring4.1-mvc

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM