@EnableAsync 导致的 ClassCastException

问题出现的场景

因系统庞大,为了找到问题原因和重现问题,自己用简单的模型复现问题。在此篇幅,就基于此模型进行分析。
先介绍一下类图:
在这里插入图片描述
左边是一个简单的类的继承关系:

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() 获取到的两个实现类(DogServiceCatService )中,其中 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 模式。其底层会拦截CatServiceDogService 。查看是否有使用@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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值