要求:RestFul风格:统一返回对象格式,统一异常对象处理。
类似于一个Controller返回一个Object,然后统一封装成我们自定义的对象AppResponse。
我一开始的思路如下图:
结果老大对拓展类型转换器产生浓厚的兴趣(典型的自己给自己挖坑)。
直接上用法,再解释原理。
第一步,依赖,我直接贴全部了:
2.0.5.RELEASE
6.0.8.Final
5.1.0
1.1.0
5.0.4.RELEASE
2.9.4
3.0.1
6.0.6
5.2.13.Final
2.7.8
1.2.3
3.0
1.8.8
1.2.2
org.springframework
spring-context
${org.springframework.version}
runtime
org.springframework
spring-webmvc
${org.springframework.version}
com.fasterxml.jackson.core
jackson-core
${jackson.version}
com.fasterxml.jackson.core
jackson-databind
${jackson.version}
javax.servlet
javax.servlet-api
${servlet.version}
provided
org.springframework.data
spring-data-jpa
${spring.data.jpa.version}
mysql
mysql-connector-java
${mysql.connector.version}
org.hibernate
hibernate-core
${hibernate.version}
org.hibernate
hibernate-entitymanager
${hibernate.version}
org.hibernate
hibernate-ehcache
${hibernate.version}
org.hibernate
hibernate-validator
${hibernate.validator.version}
com.zaxxer
HikariCP
${HikariCP.version}
ch.qos.logback
logback-classic
${logback.version}
org.junit.jupiter
junit-jupiter-api
${junit.jupiter.version}
test
org.junit.platform
junit-platform-runner
${junit.platform.version}
test
org.junit.jupiter
junit-jupiter-engine
${junit.jupiter.version}
org.apache.commons
commons-lang3
${common.lang3.version}
org.aspectj
aspectjweaver
${aspectjweaver.version}
org.apache.shiro
shiro-core
${shiro.core.version}
demo
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
第二步:创建你自定义的类型转换器:
import java.io.IOException;
import java.lang.reflect.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import com.kushou.demo.common.AppResponse;
import com.kushou.demo.common.ExceptionInfo;
@Component
public class AppResponseMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(AppResponseMappingJackson2HttpMessageConverter.class);
@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 做数据处理
if (object == null) {
//继续原有的方法
super.writeInternal(object, type, outputMessage);
}
AppResponse appResponse = new AppResponse<>();
appResponse.setData(object);
appResponse.setStatus(1);
ExceptionInfo exceptionInfo = new ExceptionInfo();
appResponse.setExceptionInfo(exceptionInfo);
//继续原有的方法
super.writeInternal(appResponse, type, outputMessage);
}
}
第三步:把你自定义的类型转换器添加到springMVC的配置文件中:
ok,然后写一个简单的controller测试一下:
@RequestMapping(value = "/getAllUser.action", method = { RequestMethod.GET, RequestMethod.POST })
public Paging getAllUser(HttpServletRequest request) {
// 分页
List users;
int count;
users = userService.getAllUser();
count = userService.getAllUserCount();
LOGGER.info("获取所有用户信息成功");
if (users == null) {
return null;
}
Paging paging = new Paging<>();
paging.setData(users);
paging.setTotal(count);
return paging;
}
我这里用的是@RestController,如果Class上标注的是@Controller注意添加@ResponseBody。
看一下结果:
{
"status": 1,
"data": {
"total": 5,
"data": [{
"id": 1,
"username": "loren",
"password": "123456",
"phone": null
}, {
"id": 2,
"username": "loren2",
"password": "e10adc3949ba59abbe56e057f20f883e",
"phone": null
}, {
"id": 3,
"username": "loren3",
"password": "25f9e794323b453885f5181f1b624d0b",
"phone": null
}, {
"id": 4,
"username": "loren4",
"password": "e10adc3949ba59abbe56e057f20f883e",
"phone": null
}],
"empty": false
},
"exceptionInfo": {
"code": null,
"message": null
}
}
似乎完成了返回对象的包装,然而这样做其实有一个问题,那就是如果我的controller返回值是null,就会有问题。看过源码的就会知道这个问题,不过等下再解释,先解决问题。
其实再spring源码RequestResponseBodyMethodProcessor的handleReturnValue这个方法里有这么一句话:
// Try even with null return value. ResponseBodyAdvice could get involved.
你可以用ResponseBodyAdvice来处理controller方法返回值为null的情况。而这个ResponseBodyAdvice方法更加简单。
第一步:自定义一个advice实现ResponseBodyAdvice:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice
没了,根本不需要第二步。如果我还有其他比如异常要处理,再定义一个advice实现ResponseBodyAdvice就可以了。这里注意一下advice的执行顺序,就是你的package下advice类的排列顺序,其实就是字符串的顺序。
好了,按上述做法基本就可以完成功能。其实我之前对类型转换器做扩展完全没有必要,所有的封装都可以放到advice中完成,不过我是通过扩展类型转换器发现advice的,所以接下来就跟着源码简单走一遍我的思路:
第一步:RequestResponseBodyMethodProcessor的handleReturnValue方法:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
看到调用了AbstractMessageConverterMethodProcessor的writeWithMessageConverters()方法:
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class extends HttpMessageConverter>>) converter.getClass(),
inputMessage, outputMessage);//这里通过advice给outputValue重新赋值了
if (outputValue != null) {//如果返回为null,直接return掉了
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {//重写Converter方法的write方法调用的writeInternal()方法即可
genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);
}
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + converter + "]");
}
}
return;
}
}
}
关于advice的调用顺序,我们跟一下给outputValue赋值的RequestResponseBodyAdviceChain.beforeBodyWrite()方法:
@Override
@Nullable
public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType contentType,
Class extends HttpMessageConverter>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
return processBody(body, returnType, contentType, converterType, request, response);
}
调用processBody()方法:
@SuppressWarnings("unchecked")
@Nullable
private Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
Class extends HttpMessageConverter>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
for (ResponseBodyAdvice> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {//遍历getMatchingAdvice()方法
if (advice.supports(returnType, converterType)) {
body = ((ResponseBodyAdvice) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
}
}
return body;
}
我们发现遍历getMatchingAdvice()方法:
@SuppressWarnings("unchecked")
private List getMatchingAdvice(MethodParameter parameter, Class extends A> adviceType) {
List
这里通过getAdvice()获取advice集合:
private List
显然这里是通过自己的成员变量来获取,说明在初始化服务的时候就加载了advice集合。
看一下构造器:
/**
* Create an instance from a list of objects that are either of type
* {@code ControllerAdviceBean} or {@code RequestBodyAdvice}.
*/
public RequestResponseBodyAdviceChain(@Nullable List
通过debug发现参数List
/**
* Constructor with converters and {@code Request~} and {@code ResponseBodyAdvice}.
* @since 4.2
*/
public AbstractMessageConverterMethodArgumentResolver(List> converters,
@Nullable List
发现AbstractMessageConverterMethodArgumentResolver的子类RequestPartMethodArgumentResolver调用了它的构造器:
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
/**
* Basic constructor with converters only.
*/
public RequestPartMethodArgumentResolver(List> messageConverters) {
super(messageConverters);
}
/**
* Constructor with converters and {@code Request~} and
* {@code ResponseBodyAdvice}.
*/
public RequestPartMethodArgumentResolver(List> messageConverters,
List
然后RequestPartMethodArgumentResolver在RequestMappingHandlerAdapter中被创建:
private List getDefaultArgumentResolvers() {
List resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));//创建
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
看到这个this.requestResponseBodyAdvice,想到要么是RequestMappingHandlerAdapter在哪里被创建,要么是set进来的。
然而它的构造器是这样的:
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
明显没有advice,那么去set方法看在WebMvcConfigurationSupport.requestMappingHandlerAdapter()方法被设置值:
/**
* Returns a {@link RequestMappingHandlerAdapter} for processing requests
* through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
*
* - {@link #addArgumentResolvers} for adding custom argument resolvers.
*
- {@link #addReturnValueHandlers} for adding custom return value handlers.
*
- {@link #configureMessageConverters} for adding custom message converters.
*
*/
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
这里加进来的是JsonViewResponseBodyAdvice();而且只有一个,肯定不对。然后到前面的RequestMappingHandlerAdapter看一下:
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isInfoEnabled()) {
logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
}
List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
List
这里获取了有@ControllerAdvice的bean,并加入到requestResponseBodyAdvice中去了。
既然找到了,那么看一下它到底是怎样排序的。
进入AnnotationAwareOrderComparator.sort(adviceBeans):
/**
* Sort the given List with a default AnnotationAwareOrderComparator.
* Optimized to skip sorting for lists with size 0 or 1,
* in order to avoid unnecessary array extraction.
* @param list the List to sort
* @see java.util.List#sort(java.util.Comparator)
*/
public static void sort(List> list) {
if (list.size() > 1) {
list.sort(INSTANCE);
}
}
原来直接调用了list.sort(compare);
那么只要观察这个compare的compare()方法是怎么实现的就可以了。
在它的父类OrderComparator中:
@Override
public int compare(@Nullable Object o1, @Nullable Object o2) {
return doCompare(o1, o2, null);
}
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {//o1是PriorityOrdered类型而p2不是
return -1;
}
else if (p2 && !p1) {//o2是PriorityOrdered类型而o1不是
return 1;
}
//很明显我们自定义的两个advice都实现了ResponseBodyAdvice,所以肯定是同一类型的,所以只会走下面的方法。
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider);
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;//i1i2则返回1 相等返回0
}
接下来看这个getOrder()方法,由于这里sourceProvider是null,所以直接进单参数的那个方法中:
protected int getOrder(@Nullable Object obj) {
if (obj != null) {//我们自定义的对象一般不是null
Integer order = findOrder(obj);
if (order != null) {
return order;//这里基本就看findOrder返回什么了
}
}
return Ordered.LOWEST_PRECEDENCE;//若findOrder返回null,则返回integer的最大值
}
再看这个findOrder方法:
@Nullable
protected Integer findOrder(Object obj) {
return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}
再次判断对象是不是Ordered类型,是返回getOrder的值,否返回null。
由此我们有一个注解@Order:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
/**
* The order value.
* Default is {@link Ordered#LOWEST_PRECEDENCE}.
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;
}
着这个注解上设置值,越小就会先被执行。
ok结束。