问题出现的场景
因系统庞大,为了找到问题原因和重现问题,自己用简单的模型复现问题。在此篇幅,就基于此模型进行分析。
先介绍一下类图:
左边是一个简单的类的继承关系:
public class Animal {
}
public class Cat extends Animal {
}
public class Dog extends Animal {
}
右边的结构也是类似,代码如下:
public interface Special<T extends Animal> {
void say();
void eat();
}
@Service
public class CatService implements Special<Cat> {
@Async
@Override
public void say() {
System.out.println("miaomiao");
}
@Override
public void eat() {
System.out.println("I eat fish...");
}
}
@Service
public class DogService implements Special<Dog> {
@Override
public void say() {
System.out.println("wangwang");
}
@Override
public void eat() {
System.out.println("I eat bone...");
}
}
配置类:
@EnableAsync //EnableAsync.mode = AdviceMode.PROXY
@Configuration
public class MyConfig implements AsyncConfigurer {
/**
* 配置线程池
*
* @return
*/
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
ThreadPoolExecutor.CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();
//对拒绝task的处理策略
executor.setRejectedExecutionHandler(callerRunsPolicy);
executor.initialize();
return executor;
}
@Override
public Executor getAsyncExecutor() {
return taskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
此处通过 SpringBoot
启动后进行访问 Controller
路径调用服务进行测试:
@RestController
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
//默认启动端口为:80
SpringApplication.run(TestApplication .class, args);
}
@RequestMapping("/execute")
public void execute() throws Exception {
testService.getBeans();
}
@Autowired
private TestService testService;
}
@Service
public class TestService {
public void getBeans() {
//获取 Special 的实现类,具体是DogService 与 CatService
Map<String, Special> beansOfType = applicationContext.getBeansOfType(Special.class);
for (Map.Entry<String, Special> entry : beansOfType.entrySet()) {
Special value = entry.getValue();
Class<?> lookpt = lookpt(value.getClass());
System.out.println("******************* lookpt:" + lookpt.getName() + " *******************");
}
}
//加入传入的参数为 CatService.class
public static Class<?> lookpt(Class<?> type) {
//获取类( CatService)实现的接口,此处获取的就是 Special<Cat>
Type superClass = type.getGenericInterfaces()[0];
//获取接口中的泛型:Cat
Type t = ((ParameterizedType) superClass).getActualTypeArguments()[0];
if (t instanceof Class) {
return (Class<?>) t;
}
if (t instanceof WildcardType) {
Type rt = ((WildcardType) t).getUpperBounds()[0];
if (rt instanceof Class) {
return (Class<?>) rt;
}
}
return null;
}
@Autowired
private ApplicationContext applicationContext;
}
浏览器访问:http://localhost/execute,后台报错:
threw exception [Request processing failed; nested exception is java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType] with root cause
java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
at com.ibicd.async.service.TestService.lookpt() ~[classes/:na]
其实报错就是说:Type t = ((ParameterizedType) superClass).getActualTypeArguments()[0];
这行中的Class 不能转换为:ParameterizedType
。其写法是没有问题的。通过debug,可以查看到,通过 getBeansOfType() 获取到的两个实现类(DogService
和 CatService
)中,其中 CatService
是 JDK 动态代理:
对比发现,CatService
中使用了@Async
:
好,那把 @Async 注解去掉,应该能解决问题。去掉后再测试,再访问果然没有再报错,后台输出:
******************* lookpt:com.ibicd.async.domain.Cat *******************
******************* lookpt:com.ibicd.async.domain.Dog *******************
但是这样做就相当于违背了初衷,为了避开bug的换一种写法,还是没有找到问题原因。
问题原因
细心的童鞋可能已经发现,我们还有一个配置类:MyConfig
。类中使用了@EnableAsync
查看 @EnableAsync
,发现有一个属性:
/**
*请注意,代理模式只允许通过代理拦截调用。同一个类中的本地调用不能以这种方式被拦截;本地调用中此类方法上
* 的异步注释将被忽略,因为Spring的拦截器甚至不会在这种运行时场景中启动。对于更高级的拦截模式,请考虑
* 将其切换到AdviceMode.ASPECTJ.
*/
AdviceMode mode() default AdviceMode.PROXY;
原来这玩意默认是代理模式,不妨把它改为 AdviceMode.ASPECTJ
。测试结果果然凑效!
再回到 mode = AdviceMode.PROXY 模式。其底层会拦截CatService
和 DogService
。查看是否有使用@Async
注解,如果有的话就生成代理对象。因此获取到的 Bean 就是代理。具体请看总结。
总结
mode = AdviceMode.PROXY:
动态代理,配置为: ProxyAsyncConfiguration
ProxyAsyncConfiguration.asyncAdvisor()
中的返回值类型为:AsyncAnnotationBeanPostProcessor
;
查看AsyncAnnotationBeanPostProcessor.postProcessAfterInitialization()
, 继续查看 isEligible(bean,beanName),继续调试可以发现:在此处会循环遍历是否有使用@Async
注解。有则返回true,则生成代理类。
mode = AdviceMode.ASPECTJ
: 静态代理,配置为:AspectJAsyncConfiguration
使用时需要引入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
解决方式:
方式1:
如上在配置类中注解:@EnableAsync(mode = AdviceMode.ASPECTJ)
方式2:
使用 AopUtils.getTargetClass(value)
解开代理:
@Service
public class TestService {
public void getBeans() {
Map<String, Special> beansOfType = applicationContext.getBeansOfType(Special.class);
for (Map.Entry<String, Special> entry : beansOfType.entrySet()) {
Special value = entry.getValue();
Class<?> clazz = null;
//判断如果是动态代理,则解开
if (AopUtils.isAopProxy(value)) {
clazz = AopUtils.getTargetClass(value);
} else {
clazz = value.getClass();
}
Class<?> lookpt = lookpt(clazz);
System.out.println("******************* lookpt:" + lookpt.getName() + " *******************");
}
}
public static Class<?> lookpt(Class<?> type) {
Type superClass = type.getGenericInterfaces()[0];
Type t = ((ParameterizedType) superClass).getActualTypeArguments()[0];
if (t instanceof Class) {
return (Class<?>) t;
}
if (t instanceof WildcardType) {
Type rt = ((WildcardType) t).getUpperBounds()[0];
if (rt instanceof Class) {
return (Class<?>) rt;
}
}
return null;
}
@Autowired
private ApplicationContext applicationContext;
}