Java for Web 学习笔记(八一):RESTful (1 )设置 Rest Context 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 相关文章