Wei:我想了想,还是小步快跑,控制每篇学习笔记的篇幅。
我们将给出Rest Context和web Context共存的例子。Service是方在Root Context的,Controller则是位于下一级的Rest Context或者web context,需要能够识别扫描,最简单的,我们可以将它们分别放在不同的package中,通过@ComponentScan中的basePackages,扫描不同的位置来实现。这种方式能适应大部分的场景。但在小例子中,我们采用另一种方式,通过定义不同的标记来进行区分这个controller是属于rest的还是web的。
定义继承@Controller的标记:@WebController和@RestEndpoint
我们定义@WebController,用来标记web context下的controller。标记在之前的validator中已经学过。@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller //继承@Controller
public @interface WebController {
//和其他的spring的标记一样,提供一个可以覆盖的方法,用于设置bean名字。
String value() default "";
}
定义@RestEndpoint,用于Restful网络接口
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
public @interface RestEndpoint {
String value() default "";
}
实现REST上下文的配置
REST用于机器对机器,其上下文环境更为简单,不需要那么多的message converters,小例子只需要对json或者xml进行解析和封装,不需要ViewResolver,RequestToViewNameResolver。//【1】扫描带有@RestEndpoint标记
@Configuration
@EnableWebMvc
@ComponentScan(
basePackages = "cn.wei.chapter17.site",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter({RestEndpoint.class}))
public class RestServletContextConfiguration extends WebMvcConfigurerAdapter{
// 之前已经在强大的生态中讲过对于Json和XML,并不需要手动设置codec的转换器,Spring能够自动在lib中找到合适的。这里沿用书中例子
@Inject ObjectMapper objectMapper;
@Inject Marshaller marshaller;
@Inject Unmarshaller unmarshaller;
//【2】验证在RESTful接口很重要,要加上
@Inject SpringValidatorAdapter validator;
//【3】消息格式支持Json和XML两种
//(3.1) 根据Content-Type加上相关的转换器作。
@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) 对媒体格式的协商。如果请求中Accept: application/xml,则采用xml的方式,若支持json和xml,其先后顺序具有优先级别;若无相关信息,采用缺省的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接口的返回信息进行本地化,通常是错误信息。REST没有session,根据Accept-Language获取
@Bean
public LocaleResolver localeResolver(){
return new AcceptHeaderLocaleResolver();
}
}
WebServletContextConfiguration和以前学习的没有什么区别,只是在扫描是针对@WebController。
依次启动不同的上下文
我们将在Bootstrap类中一次启动Root上下文,Web上下文,和Rest上下文public class Bootstrap implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext container) throws ServletException {
container.getServletRegistration("default").addMapping("/resource/*");
//(1)启动root上下文
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootContextConfiguration.class);
container.addListener(new ContextLoaderListener(rootContext));
//(2)启动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)启动Rest上下文,spring将采用最佳匹配的方式,和web上下文的"/"不矛盾。
AnnotationConfigWebApplicationContext restContext = new AnnotationConfigWebApplicationContext();
restContext.register(RestServletContextConfiguration.class);
// 小例子测试OPTIONS,需要支持OPTIONS,将此开关打开。FrameworkServlet中此值为false,但在spring 4.3,已经内置为true。我们使用了4.3.9.RELEASE,实际上无需手动设置为true。
// ➤ false为采用自动处理,将会回复200OK,body长度为0;
// ➤ true将转到我们的代码处理,如果url没有匹配,会回复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/*");
//我们希望对无效的url进行自定义的404回复(返回一个json或者xml说明),而非缺省方式(缺省返回一个页面),需要将NoHandlerFoundException抛出,在代码中处理,而非默认的servlet自行处理。
dispatcher.setInitParameter("throwExceptionIfNoHandlerFound", "true");
}
}
相关链接: 我的Professional Java for Web Applications相关文章