从头开始写项目(Q):通过扩展Convertor玩到ResponseBodyAdvice

要求:RestFul风格:统一返回对象格式,统一异常对象处理。

类似于一个Controller返回一个Object,然后统一封装成我们自定义的对象AppResponse。

我一开始的思路如下图:

从头开始写项目(Q):通过扩展Convertor玩到ResponseBodyAdvice_第1张图片

结果老大对拓展类型转换器产生浓厚的兴趣(典型的自己给自己挖坑)。

直接上用法,再解释原理。

第一步,依赖,我直接贴全部了:

	
		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
					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{
	private static final Logger LOGGER = LoggerFactory.getLogger(MyResponseBodyAdvice.class);
	
	@Override
	public boolean supports(MethodParameter returnType, Class converterType) {
		return true;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
		LOGGER.info("+++++++++++++++++++AppResponseHandler++++++++++++++++++++++");
		if (body == null) {
			return "";
		}
		return body;
	}

} 
  

没了,根本不需要第二步。如果我还有其他比如异常要处理,再定义一个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>) 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> 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> 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 adviceType) {
		List availableAdvice = getAdvice(adviceType);//这里获取advice集合
		if (CollectionUtils.isEmpty(availableAdvice)) {
			return Collections.emptyList();
		}
		List result = new ArrayList<>(availableAdvice.size());
		for (Object advice : availableAdvice) {
			if (advice instanceof ControllerAdviceBean) {
				ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
				if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
					continue;
				}
				advice = adviceBean.resolveBean();
			}
			if (adviceType.isAssignableFrom(advice.getClass())) {
				result.add((A) advice);
			}
		}
		return result;
	} 
  

这里通过getAdvice()获取advice集合:

private List getAdvice(Class adviceType) {
		if (RequestBodyAdvice.class == adviceType) {
			return this.requestBodyAdvice;//自己的成员变量
		}
		else if (ResponseBodyAdvice.class == adviceType) {
			return this.responseBodyAdvice;
		}
		else {
			throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
		}
	} 
  

显然这里是通过自己的成员变量来获取,说明在初始化服务的时候就加载了advice集合。

看一下构造器:

/**
	 * Create an instance from a list of objects that are either of type
	 * {@code ControllerAdviceBean} or {@code RequestBodyAdvice}.
	 */
	public RequestResponseBodyAdviceChain(@Nullable List requestResponseBodyAdvice) {
		if (requestResponseBodyAdvice != null) {
			for (Object advice : requestResponseBodyAdvice) {
				Class beanType = (advice instanceof ControllerAdviceBean ?
						((ControllerAdviceBean) advice).getBeanType() : advice.getClass());
				if (beanType != null) {
					if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
						this.requestBodyAdvice.add(advice);
					}
					else if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
						this.responseBodyAdvice.add(advice);
					}
				}
			}
		}
	} 
  

通过debug发现参数List requestResponseBodyAdvice已经排序过了,继续找RequestResponseBodyAdviceChain在AbstractMessageConverterMethodArgumentResolver的构造器里被构造:

/**
	 * Constructor with converters and {@code Request~} and {@code ResponseBodyAdvice}.
	 * @since 4.2
	 */
	public AbstractMessageConverterMethodArgumentResolver(List> converters,
			@Nullable List requestResponseBodyAdvice) {

		Assert.notEmpty(converters, "'messageConverters' must not be empty");
		this.messageConverters = converters;
		this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
		this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);//这里被构造
	} 
  

发现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 requestResponseBodyAdvice) {

		super(messageConverters, requestResponseBodyAdvice);
	} 
  

然后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 requestResponseBodyAdviceBeans = new ArrayList<>();

		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
			Set attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
				if (logger.isInfoEnabled()) {
					logger.info("Detected @ModelAttribute methods in " + adviceBean);
				}
			}
			Set binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
				if (logger.isInfoEnabled()) {
					logger.info("Detected @InitBinder methods in " + adviceBean);
				}
			}
			if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
				if (logger.isInfoEnabled()) {
					logger.info("Detected RequestBodyAdvice bean in " + adviceBean);
				}
			}
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
				if (logger.isInfoEnabled()) {
					logger.info("Detected ResponseBodyAdvice bean in " + adviceBean);
				}
			}
		}

		if (!requestResponseBodyAdviceBeans.isEmpty()) {
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
	} 
  

这里获取了有@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结束。


你可能感兴趣的:(从头开始写项目)