Java for Web 學(xué)習筆記(八一):RESTful (1 )設置 Rest Context Wei:我想了想,還是小步快跑,控制每篇學(xué)習筆記的篇幅。
我們將給出 Rest Context 和 web Context 共存的例子。Service 是方在 Root Context 的,Controller 則是位于下一級的 Rest Context 或者 web context,需要能夠識別掃描,最簡(jiǎn)單的,我們可以將它們分別放在不同的 package 中,通過(guò)@ComponentScan 中的 basePackages,掃描不同的位置來(lái)實(shí)現。這種方式能適應大部分的場(chǎng)景。但在小例子中,我們采用另一種方式,通過(guò)定義不同的標記來(lái)進(jìn)行區分這個(gè) controller 是屬于 rest 的還是 web 的。
定 義繼承@Controller 的標記:@WebController 和@RestEndpoint 我們定義@WebController,用來(lái)標記 web context 下的 controller。標記在之前的validator 中已經(jīng)學(xué)過(guò)。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
//繼承@Controller
public @interface WebController {
//和其他的 spring 的標記一樣,提供一個(gè)可以覆蓋的方法,用于設置 bean 名字。
String value() default "";
}
定義@RestEndpoint,用于 Restful 網(wǎng)絡(luò )接口 @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
public @interface RestEndpoint {
String value() default "";
}
實(shí)現 REST 上下文的配置 REST 用于機器對機器,其上下文環(huán)境更為簡(jiǎn)單,不需要那么多的 message converters,小例子只需要對 json 或者 xml 進(jìn)行解析和封裝,不需要ViewResolver,RequestToViewNameResolver。
//【1】掃描帶有@RestEndpoint 標記
@Configuration
@EnableWebMvc
@ComponentScan(
basePackages = "cn.wei.chapter17.site",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter({RestEndpoint.class}))
public class RestServletContextConfiguration extends WebMvcConfigurerAdapter{
// 之前已經(jīng)在強大的生態(tài)中講過(guò)對于 Json 和 XML,并不需要手動(dòng)設置 codec 的轉換器,Spring 能夠自動(dòng)在 lib 中找到合適的。這里沿用書(shū)中例子
@Inject ObjectMapper objectMapper;
@Inject Marshaller marshaller;
@Inject Unmarshaller unmarshaller;
//【2】驗證在 RESTful 接口很重要,要加上
@Inject SpringValidatorAdapter validator;
//【3】消息格式支持 Json 和 XML 兩種
//(3.1) 根據 Content-Type 加上相關(guān)的轉換器作。
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new SourceHttpMessageConverter<>());
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter();
xmlConverter.setSupportedMediaTypes(Arrays.asList( new MediaType("application", "xml"),
new MediaType("text", "xml")));
xmlConverter.setMarshaller(this.marshaller);
xmlConverter.setUnmarshaller(this.unmarshaller);
converters.add(xmlConverter);
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json"),
new MediaType("text", "json")));
jsonConverter.setObjectMapper(this.objectMapper);
converters.add(jsonConverter);
}
//(3.2) 對媒體格式的協(xié)商。如果請求中 Accept: application/xml,則采用 xml 的方式,若支持 json 和 xml,其先后順序具有優(yōu)先級別;若無(wú)相關(guān)信息,采用缺省的 json
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).favorParameter(false)
.ignoreAcceptHeader(false)
.defaultContentType(MediaType.APPLICATION_JSON); //缺
省采用 json 方式
}
//(2.1) 設置 validator
@Override
public Validator getValidator() {
return this.validator;
}
//(2.2) 設置 locale 解析器,可對 Restful 接口的返回信息進(jìn)行本地化,通常是錯誤信息。REST 沒(méi)有 session,根據 Accept-Language 獲取
@Bean
public LocaleResolver localeResolver(){
return new AcceptHeaderLocaleResolver();
}
}
WebServletContextConfiguration 和以前學(xué)習的沒(méi)有什么區別,只是在掃描是針對@WebController。
依次啟動(dòng)不同的上下文 我們將在 Bootstrap 類(lèi)中一次啟動(dòng) Root 上下文,Web 上下文,和 Rest 上下文
public class Bootstrap implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext container) throws ServletException {
container.getServletRegistration("default").addMapping("/resource/*");
//(1)啟動(dòng) root 上下文
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootContextConfiguration.class);
container.addListener(new ContextLoaderListener(rootContext));
//(2)啟動(dòng) web 上下文
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
webContext.register(WebServletContextConfiguration.class);
ServletRegistration.Dynamic dispatcher = container.addServlet(
"springWebDispatcher", new DispatcherServlet(webContext));
dispatcher.setLoadOnStartup(1);
dispatcher.setMultipartConfig(new MultipartConfigElement(null, 20_971_520L, 41_943_040L, 512_000));
dispatcher.addMapping("/");
//(3)啟動(dòng) Rest 上下文,spring 將采用最佳匹配的方式,和 web 上下文的"/"不矛盾。
AnnotationConfigWebApplicationContext restContext = new AnnotationConfigWebApplicationContext();
restContext.register(RestServletContextConfiguration.class);
// 小例子測試 OPTIONS,需要支持 OPTIONS,將此開(kāi)關(guān)打開(kāi)。FrameworkServlet 中此值為 false,但在 spring 4.3,已經(jīng)內置為 true。我們使用了 4.3.9.RELEASE,實(shí)際上無(wú)需手動(dòng)設置為 true。
// ? false 為采用自動(dòng)處理,將會(huì )回復 200OK,body 長(cháng)度為 0;
// ? true 將轉到我們的代碼處理,如果 url 沒(méi)有匹配,會(huì )回復 404。
// DispatcherServlet restServlet = new DispatcherServlet(restContext);
// restServlet.setDispatchOptionsRequest(true);
// dispatcher = container.addServlet("springRestDispatcher", restServlet);
dispatcher = container.addServlet("springRestDispatcher", new DispatcherServlet(restContext));
dispatcher.setLoadOnStartup(2);
dispatcher.addMapping("/services/Rest/*");
//我們希望對無(wú)效的 url 進(jìn)行自定義的 404 回復(返回一個(gè) json 或者 xml 說(shuō)明),而非缺省方式(缺省返回一個(gè)頁(yè)面),需要將 NoHandlerFoundException 拋出,在代碼中處理,而非默認的 servlet 自行處理。
dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true");
}
}
相關(guān)鏈接:
我的 Professional Java for Web Applications 相關(guān)文章