从头开始写项目(Q):通过扩展Convertor玩到ResponseBodyAdvice
要求:RestFul风格:统一返回对象格式,统一异常对象处理。
类似于一个Controller返回一个Object,然后统一封装成我们自定义的对象AppResponse。
我一开始的思路如下图:
结果老大对拓展类型转换器产生浓厚的兴趣(典型的自己给自己挖坑)。
直接上用法,再解释原理。
第一步,依赖,我直接贴全部了:
<properties>
<spring.data.jpa.version>2.0.5.RELEASE</spring.data.jpa.version>
<hibernate.validator.version>6.0.8.Final</hibernate.validator.version>
<junit.jupiter.version>5.1.0</junit.jupiter.version>
<junit.platform.version>1.1.0</junit.platform.version>
<org.springframework.version>5.0.4.RELEASE</org.springframework.version>
<jackson.version>2.9.4</jackson.version>
<servlet.version>3.0.1</servlet.version>
<mysql.connector.version>6.0.6</mysql.connector.version>
<hibernate.version>5.2.13.Final</hibernate.version>
<HikariCP.version>2.7.8</HikariCP.version>
<logback.version>1.2.3</logback.version>
<common.lang3.version>3.0</common.lang3.version>
<aspectjweaver.version>1.8.8</aspectjweaver.version>
<shiro.core.version>1.2.2</shiro.core.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring.data.jpa.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate.validator.version}</version>
</dependency>
<!-- <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId>
<version>3.4</version> </dependency> -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${HikariCP.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${common.lang3.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.core.version}</version>
</dependency>
</dependencies>
<build>
<finalName>demo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
第二步:创建你自定义的类型转换器:
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的配置文件中:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.demo.extension.AppResponseMappingJackson2HttpMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
ok,然后写一个简单的controller测试一下:
@RequestMapping(value = "/getAllUser.action", method = { RequestMethod.GET, RequestMethod.POST })
public Paging<User> getAllUser(HttpServletRequest request) {
// 分页
List<User> users;
int count;
users = userService.getAllUser();
count = userService.getAllUserCount();
LOGGER.info("获取所有用户信息成功");
if (users == null) {
return null;
}
Paging<User> 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<Object>{
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<? 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 <T> 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<T>) advice).beforeBodyWrite((T) body, returnType,
contentType, converterType, request, response);
}
}
return body;
}
我们发现遍历getMatchingAdvice()方法:
@SuppressWarnings("unchecked")
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
List<Object> availableAdvice = getAdvice(adviceType);//这里获取advice集合
if (CollectionUtils.isEmpty(availableAdvice)) {
return Collections.emptyList();
}
List<A> 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<Object> 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<Object> 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<Object> requestResponseBodyAdvice已经排序过了,继续找RequestResponseBodyAdviceChain在AbstractMessageConverterMethodArgumentResolver的构造器里被构造:
/**
* Constructor with converters and {@code Request~} and {@code ResponseBodyAdvice}.
* @since 4.2
*/
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> 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<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
/**
* Constructor with converters and {@code Request~} and
* {@code ResponseBodyAdvice}.
*/
public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
List<Object> requestResponseBodyAdvice) {
super(messageConverters, requestResponseBodyAdvice);
}
然后RequestPartMethodArgumentResolver在RequestMappingHandlerAdapter中被创建:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> 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:
* <ul>
* <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
* <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
* <li>{@link #configureMessageConverters} for adding custom message converters.
* </ul>
*/
@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<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(adviceBeans);
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
Set<Method> 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<Method> 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.
* <p>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;//i1<i2则返回-1 i1>i2则返回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.
* <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;
}
着这个注解上设置值,越小就会先被执行。
ok结束。
上一篇: js定时器实现自动轮播图
下一篇: js实现旋转木马相册