Skip to content

Commit

Permalink
pref(hydrogen): Make UniformHandler register bean only once other t…
Browse files Browse the repository at this point in the history
…han with `@ControllerAdvice` component scan by default.

[*] `AopContextHolder.getHandlerMetaData` support for automatic identification of aop.
  • Loading branch information
yizzuide committed Apr 12, 2020
1 parent 323d982 commit 3f95c87
Show file tree
Hide file tree
Showing 8 changed files with 65 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
Expand All @@ -17,6 +19,10 @@
* @author yizzuide
* @since 3.0.0
* @version 3.0.1
* @see WebMvcConfigurationSupport#handlerExceptionResolver(org.springframework.web.accept.ContentNegotiationManager)
* #see RequestMappingHandlerAdapter#getDefaultReturnValueHandlers()
* @see AbstractMessageConverterMethodArgumentResolver#AbstractMessageConverterMethodArgumentResolver(java.util.List, java.util.List)
* #see RequestResponseBodyAdviceChain#getAdviceByType(java.util.List, java.lang.Class)
* Create at 2020/03/29 11:30
*/
//@ControllerAdvice // 这种方式默认就会扫描并加载到Ioc,不好动态控制是否加载,但好处是外部API对未来版本的兼容性强
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
Map<String, CometCollectorProperties.Tag> tagMap = cometCollectorProperties.getTags();
this.tagCollectorMap = tagMap.keySet().stream()
.collect(Collectors.toMap(Object::toString, tagName -> applicationContext.getBean(tagName, TagCollector.class)));
threadLocal = new ThreadLocal<>();

aliasNodesMap = new HashMap<>();
for (Map.Entry<String, CometCollectorProperties.Tag> tagCollectorEntry : cometCollectorProperties.getTags().entrySet()) {
Expand All @@ -126,6 +125,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
String tag = tagCollectorEntry.getKey();
aliasNodesMap.put(tag, YmlParser.parseAliasMap(exceptionMonitor));
}
threadLocal = new ThreadLocal<>();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void onApplicationEvent(ContextRefreshedEvent event) {
attrs.put(ATTR_ASYNC, haloListener.async());
metaData.setAttributes(attrs);
return haloListener.value();
}, false, false);
}, false);
}

static Map<String, List<HandlerMetaData>> getTableNameMap() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
package com.github.yizzuide.milkomeda.hydrogen.uniform;

import com.github.yizzuide.milkomeda.universe.polyfill.SpringMvcPolyfill;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
* UniformConfig
*
* @author yizzuide
* @since 3.0.0
* @version 3.0.1
* @see WebMvcConfigurationSupport#handlerExceptionResolver(org.springframework.web.accept.ContentNegotiationManager)
* #see ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache()
* #see ExceptionHandlerExceptionResolver#getExceptionHandlerMethod(org.springframework.web.method.HandlerMethod, java.lang.Exception)
* Create at 2020/03/25 22:46
*/
@Configuration
@EnableConfigurationProperties(UniformProperties.class)
@ConditionalOnProperty(prefix = "milkomeda.hydrogen.uniform", name = "enable", havingValue = "true")
public class UniformConfig {

@Bean
@ConditionalOnMissingBean(name = "uniformHandler")
public UniformHandler uniformHandler() {
return new UniformHandler();
}

@Autowired
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public void configParamResolve(RequestMappingHandlerAdapter adapter) {
public void configParamResolve(HandlerExceptionResolver handlerExceptionResolver) {
SpringMvcPolyfill.addDynamicExceptionAdvice(handlerExceptionResolver, "uniformHandler");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
Expand All @@ -40,7 +39,8 @@
* Create at 2020/03/25 22:47
*/
@Slf4j
@ControllerAdvice // 可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute, 并应用到所有@RequestMapping中
// 可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute, 并应用到所有@RequestMapping中
//@ControllerAdvice // 这种方式默认就会扫描并加载到Ioc,不好动态控制是否加载,但好处是外部API对未来版本的兼容性强
public class UniformHandler extends ResponseEntityExceptionHandler {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
topicMap = AopContextHolder.getHandlerMetaData(IceHandler.class, IceListener.class, (annotation, metaData) -> {
IceListener iceListener = (IceListener) annotation;
return iceListener.value();
}, false, true);
}, true);
topicTtrOverloadMap = AopContextHolder.getHandlerMetaData(IceHandler.class, IceTtrOverloadListener.class, (annotation, metaData) -> {
IceTtrOverloadListener iceTtrOverloadListener = (IceTtrOverloadListener) annotation;
return iceTtrOverloadListener.value();
}, false, true);
}, true);
}

static Map<String, List<HandlerMetaData>> getTopicMap() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* @author yizzuide
* @since 1.13.4
* @version 2.5.0
* @version 3.0.1
* Create at 2019/10/24 21:17
*/
public class AopContextHolder {
Expand Down Expand Up @@ -64,23 +64,21 @@ public static XCometData getXCometData() {
* @param handlerAnnotationClazz 处理器注解类
* @param executeAnnotationClazz 执行方法注解类
* @param nameProvider 标识名称提供函数
* @param useAOP 执行方法是否使用了切面
* @param onlyOneExecutorPerHandler 一个组件只有一个处理方法是传true
* @return Map
*/
public static Map<String, List<HandlerMetaData>> getHandlerMetaData(
Class<? extends Annotation> handlerAnnotationClazz,
Class<? extends Annotation> executeAnnotationClazz,
BiFunction<Annotation, HandlerMetaData, String> nameProvider,
boolean useAOP,
boolean onlyOneExecutorPerHandler) {
Map<String, List<HandlerMetaData>> handlerMap = new HashMap<>();
Map<String, Object> beanMap = ApplicationContextHolder.get().getBeansWithAnnotation(handlerAnnotationClazz);
for (String key : beanMap.keySet()) {
Object target = beanMap.get(key);
// 如果方法有aop切面,可以通过AopUtils.getTargetClass()获取
Method[] methods = ReflectionUtils.getAllDeclaredMethods(useAOP ?
AopUtils.getTargetClass(target.getClass()) : target.getClass());
// 查找AOP切面(通过Proxy.isProxyClass()判断类是否是代理的接口类,AopUtils.isAopProxy()判断对象是否被代理),可以通过AopUtils.getTargetClass()获取原Class
Method[] methods = ReflectionUtils.getAllDeclaredMethods(AopUtils.isAopProxy(target) ?
AopUtils.getTargetClass(target) : target.getClass());
for (Method method : methods) {
// 获取指定方法上的注解的属性
final Annotation executeAnnotation = AnnotationUtils.findAnnotation(method, executeAnnotationClazz);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package com.github.yizzuide.milkomeda.universe.polyfill;

import com.github.yizzuide.milkomeda.hydrogen.interceptor.HydrogenMappedInterceptor;
import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder;
import com.github.yizzuide.milkomeda.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Field;
Expand All @@ -23,7 +29,7 @@

/**
* SpringMvcPolyfill
* Spring MVC功能填充类
* Spring MVC功能扩展类
*
* @author yizzuide
* @since 3.0.0
Expand All @@ -36,6 +42,37 @@ public class SpringMvcPolyfill {
// 缓存反射字段
private static Field adaptedInterceptorsField;

/**
* 动态添加异常切面
* @param handlerExceptionResolver HandlerExceptionResolver
* @param beanName 异常拦截处理器bean名
*/
public static void addDynamicExceptionAdvice(HandlerExceptionResolver handlerExceptionResolver, String beanName) {
if (handlerExceptionResolver instanceof HandlerExceptionResolverComposite) {
List<HandlerExceptionResolver> exceptionResolvers = ((HandlerExceptionResolverComposite) handlerExceptionResolver).getExceptionResolvers();
for (HandlerExceptionResolver exceptionResolver : exceptionResolvers) {
if (exceptionResolver instanceof ExceptionHandlerExceptionResolver) {
// TODO <mark> 由于使用底层API, 这个exceptionHandlerAdviceCache属性在未来版本中可能会改
Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> resolverMap = ReflectUtil.invokeFieldPath(exceptionResolver, "exceptionHandlerAdviceCache");
if (resolverMap == null) {
resolverMap = new HashMap<>(2);
}
// 仿Spring MVC源码创建advice
ControllerAdviceBean adviceBean = new ControllerAdviceBean(beanName, ApplicationContextHolder.get(), null);
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("SpringMvcPolyfill find unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
resolverMap.put(adviceBean, resolver);
}
break;
}
}
}
}

/**
* 动态添加消息体响应切面
* @param returnValueHandlers 响应处理器列表
Expand All @@ -50,7 +87,7 @@ public static void addDynamicResponseBodyAdvice(List<HandlerMethodReturnValueHan
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
// 只有AbstractMessageConverterMethodArgumentResolver继承类型(主要是HttpEntityMethodProcessor、RequestResponseBodyMethodProcessor)有Advice Chain
if (returnValueHandler instanceof AbstractMessageConverterMethodArgumentResolver) {
// TODO <mark> 由于使用底层API, 这个advice.responseBodyAdvice属性在未来版本中可能会改
// TODO <mark> 由于使用底层API, 这个advice.responseBodyAdvice属性在未来版本中可能会改
List<Object> advices = ReflectUtil.invokeFieldPath(returnValueHandler, "advice.responseBodyAdvice");
if (CollectionUtils.isEmpty(advices)) {
continue;
Expand Down Expand Up @@ -99,7 +136,7 @@ public static void addDynamicInterceptor(HandlerInterceptor interceptor, int ord
if (itor instanceof HydrogenMappedInterceptor) {
return (Ordered) ((HydrogenMappedInterceptor) itor)::getOrder;
}
return 0;
return null;
})).collect(Collectors.toList());
adaptedInterceptorsField.set(handlerMapping, handlerInterceptors);
} catch (Exception e) {
Expand Down

0 comments on commit 3f95c87

Please sign in to comment.