一、GroovyWebApplicationContext html
在Spring 4.1以前沒有提供Web集成的ApplicationContext,在《Spring4新特性——Groovy Bean定義DSL》中咱們本身去實現的com.sishuok.spring4.context.support.WebGenricGroovyApplicationContext,而4.1其已經提供了相應實現,直接把《Spring4新特性——Groovy Bean定義DSL》配置中的相應類改掉便可。java
二、視圖解析器標籤git
以前咱們都是這樣定義視圖解析器:github
<bean id="mvcVelocityEngine" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> spring
<property name="resourceLoaderPath" value="/WEB-INF/vm/,classpath:com/github/zhangkaitao" /> json
</bean> 安全
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> mvc
<property name="prefix" value=""/> app
<property name="suffix" value=".vm"/>
<property name="cache" value="false"/>
</bean>
<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: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: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
這種方式有一個很大的問題就是隻能作默認配置,若是想自定義其屬性值就搞不定了,估計當時開發的人考慮不全或沒有經驗。
三、控制器標籤
Spring 4.1提供了更豐富的控制器標籤:
3.一、重定向視圖控制器標籤
<mvc:redirect-view-controller
path="/redirect"
redirect-url="/status"
context-relative="true"
status-code="301"
keep-query-params="true"/>
<mvc:redirect-view-controller path="/redirect" redirect-url="/status" context-relative="true" status-code="301" keep-query-params="true"/>
3.二、狀態控制器標籤
<mvc:status-controller path="/status" status-code="200"/>
<mvc:status-controller path="/status" status-code="200"/>
3.三、帶狀態的視圖控制器標籤
<mvc:view-controller path="/error/**" status-code="200"/>
<mvc:view-controller path="/error/**" status-code="200"/>
四、Groovy Template引擎集成
Spring 4.1提供了對Groovy Template模板引擎的集成,其是一種DSL風格的模板引擎,其也是最先在Spring Boot中引入的。
4.一、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>
<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.二、模板heelo.tpl
yieldUnescaped '<!DOCTYPE html>'
html {
head {
title('hello groovy templates')
}
body {
div("hello $user.name")
}
}
yieldUnescaped '<!DOCTYPE html>' html { head { title('hello groovy templates') } body { div("hello $user.name") } }
具體語法請參考官方文檔。
五、 Jackson @JsonView支持
可使用@JsonView來分組渲染JSON數據,按需展現JSON數據。
5.一、模型
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;
……
}
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.二、控制器
@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");
}
}
@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控制渲染哪些數據。
六、Jsonp支持
6.一、MappingJackson2JsonView提供的支持
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
<property name="jsonpParameterNames">
<set>
<value>jsonp</value>
<value>callback</value>
</set>
</property>
</bean>
<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.二、對使用HttpMessageConverter的@ResponseBody的支持
@Order(2)
@ControllerAdvice(basePackages = "com.github")
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback", "jsonp"); //指定jsonpParameterNames
}
}
@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.三、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;
}
}
@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; } }
一、supports指定支持哪些類型的方法進行處理,此處是返回值爲User的;二、咱們獲得User對象而後在名字先後拼上」---「 ;三、能夠指定多個ResponseBodyAdvice,使用@Order指定順序。訪問http://localhost:8080/jackson2?callback=callback能夠看到效果。
七、Gson HttpMessageConverter
7.一、Spring配置
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.GsonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<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>版本。
八、Protobuf HttpMessageConverter
8.一、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>
<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.二、定義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;
}
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.三、添加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>
<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.四、測試控制器
@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());
}
}
@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.五、測試用例(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());
}
@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)
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);
//setProtoHeader(outputMessage, message);
九、RequestEntity/ResponseEntity
Spring 4.1提供了ResponseEntity配對的RequestEntity,使用方式和HttpEntity同樣。具體能夠參考com.github.zhangkaitao.web.controller.RequestResponseEntityController。
十、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();
}
}
@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()}
${s:mvcUrl('MUCBC#mvcUriComponentsBuilder2').arg(0,"123").build()}
如上方式只能在正常EL 3.0的容器中運行,可參考《Expression Language 3.0新特性》。
十一、MockRestServiceServer
MockRestServiceServer目前提供了對AsyncRestTemplate的支持,使用方式和RestTemplate同樣。可參考《Spring MVC測試框架詳解——客戶端測試》。
十二、MockMvcConfigurer
Spring 4.1提供了MockMvcConfigurer用於進行一些通用配置,使用方式以下:
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(defaultSetup()).build();
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(defaultSetup()).build();
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;
}
};
}
};
}
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。