前言:
接口类项目开发时,为了便于后期查找问题,一般会拦截器或过滤器中记录每个接口请求的参数与响应值记录,
请求参数很容易从request中获取,但controller的返回值无法从response中获取,有一个简单的方法,在controller接口的最后将返回值保存到request域中,这种方法虽然简单,但是开发起来太麻烦,需要在每个controller的最后添加一行代码,且该功能不属于业务功能,不应该接口中去实现,应该有个全局的处理方法。
ControllerAdvice是springmvc controller增强器
ControllerAdvice三个用处:
1. ModelAttribute: 暴露@RequestMapping 方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。
2. InitBinder : 用于自定义@RequestMapping 方法参数绑定
3. ResponseBodyAdvice : 用于@ResponseBody返回值增加处理
ControllerAdvice初始化:
Spring mvc 启动时调用RequestMappingHandlerAdapter类的initControllerAdviceCache()方法进行初始化
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isInfoEnabled()) {
logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
}
// 获取所有添加ControlerAdvice注解的Bean
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
Collections.sort(beans, new OrderComparator());
List<Object> responseBodyAdviceBeans = new ArrayList<Object>();
for (ControllerAdviceBean bean : beans) {
Set<Method> attrMethods = HandlerMethodSelector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(bean, attrMethods);
logger.info("Detected @ModelAttribute methods in " + bean);
}
Set<Method> binderMethods = HandlerMethodSelector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(bean, binderMethods);
logger.info("Detected @InitBinder methods in " + bean);
}
// 如果ControllerAdvice是ResponseBodyAdvice类型则注入到responseBodyAdviceBeans列表中
if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
responseBodyAdviceBeans.add(bean);
logger.info("Detected ResponseBodyAdvice bean in " + bean);
}
}
if (!responseBodyAdviceBeans.isEmpty()) {
this.responseBodyAdvice.addAll(0, responseBodyAdviceBeans);
}
}
ResponseBodyAdvice : 可以对@ResponseBody的返回值进行加工处理,它是一个接口类,具体处理可以自定义实现类注入到responseBodyAdviceBeans中既可,注入过程由RequestMappingHandlerAdapter类的initControllerAdviceCache去做,开发者只需要自定义实现类实现ResponseBodyAdvice 接口并添加类注解@ControllerAdvice
ResponseBodyAdviceChain : 维护ResponseBodyAdvice列表,循环调用所有的ResponseBodyAdvice
@SuppressWarnings("unchecked")
public <T> T invoke(T body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (this.advice != null) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking ResponseBodyAdvice chain for body=" + body);
}
for (Object advice : this.advice) {
if (advice instanceof ControllerAdviceBean) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
if (!adviceBean.isApplicableToBeanType(returnType.getContainingClass())) {
continue;
}
advice = adviceBean.resolveBean();
}
if (advice instanceof ResponseBodyAdvice) {
ResponseBodyAdvice<T> typedAdvice = (ResponseBodyAdvice<T>) advice;
if (typedAdvice.supports(returnType, selectedConverterType)) {
body = typedAdvice.beforeBodyWrite(body, returnType,
selectedContentType, selectedConverterType, request, response);
}
} else {
throw new IllegalStateException("Expected ResponseBodyAdvice: " + advice);
}
}
if (logger.isDebugEnabled()) {
logger.debug("After ResponseBodyAdvice chain body=" + body);
}
}
return body;
}
例子:自定义ResponseBodyAdvice
/**
* Controller增强器,将Controller接口的响应(Response)放到请求上下文中
* @author zsc
* @datetime 2017年12月11日 下午3:09:08
*/
@ControllerAdvice
@Component
public class RespBodyAdvice implements ResponseBodyAdvice<Object>{
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converter Type) {
// 注意,这里必须返回true,否则不会执行beforeBodyWrite方法
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
// 将返回的body放到请求上下文中
if(null == body) {
return null;
}
if(body instanceof Response) {
RequestContextUtil.getContext().setResponse((Response)body);
}
return body;
}
}