@Async引发的BeanNotOfRequiredTypeException
背景Spring Boot项目的开发中项目启动时报错org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name xxxxServiceImpl: Unsatisfied dependency expressed through field exampleService; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named ExampleServiceImpl is expected to be of type com.xxxx.service.impl.ExampleServiceImpl but was actually of type com.sun.proxy.$Proxy223查找后发现在xxxxServiceImpl里注入了ExampleServiceImplAutowiredprivateExampleServiceImplexampleService;而对于ExampleServiceImpl最新的修改是给一个方法加上了注解Async原因Async依赖 Spring AOP 代理才能生效。加上该注解后Spring 会对 ExampleServiceImpl 创建一个代理对象来拦截方法调用将任务提交到线程池异步执行。JDK 动态代理生成的代理对象实现了 ExampleService 接口但并不是 ExampleServiceImpl 的实例。Spring 在注入时做类型检查发现是无法赋值给 ExampleServiceImpl 类型的变量于是抛出 BeanNotOfRequiredTypeException。解决在注入时注入接口// 修改前 Autowired private ExampleServiceImpl exampleService; // 修改后 Autowired private ExampleService exampleService;原理1. Spring AOP 和 JDK 动态代理Async、Transactional等注解依赖 Spring AOP 实现而 Spring AOP 的本质是方法拦截。Spring 无法直接修改你的类所以它会在目标对象外面套一层代理对象由代理来拦截方法调用插入额外逻辑。没有代理切面逻辑就无法执行调用方 ↓ 代理对象拦截 ├── 执行切面逻辑如 Async 提交到线程池 └── 调用真实方法Spring 有两种代理方式一般选择逻辑为目标类有接口 ├── 是 → JDK 动态代理 └── 否 → CGLIB 代理其中 JDK 动态代理的生成方式是实现目标类的接口。Spring 在创建代理时会反射扫描目标类实现了哪些接口然后告诉 JDK 动态代理也去实现这些接口。所以如果要使用 JDK 动态代理被代理的类必须实现了接口否则无法代理。同时JDK 动态代理无法为没有在接口中定义的方法实现代理假设我们有一个实现了接口的类我们为它的一个不属于接口中的方法配置了切面Spring 仍然会使用 JDK 的动态代理但是由于配置了切面的方法不属于接口为这个方法配置的切面将不会被织入。有Async、Transactional这些注解的函数在类内部被调用的时候注解不会生效也是因为 Spring AOP 依赖代理类内部调用自己的方法切面不会生效。像本次的问题这样直接导入实现类也会因为代理类和实现类不一致而产生错误。2. CGLIB 代理CGLIB 是一个字节码生成库它在运行时直接对需要代理的类的字节码进行操作生成这个类的一个子类并重写了类的所有可以重写的方法在重写的过程中将切面织入到方法中来实现想达到的目标。而通过字节码操作生成的代理类和我们自己编写并编译后的类没有太大区别。因为 CGLIB 生成的代理类是直接继承自需要被代理的类所以使用 CGLIB 代理的类不需要实现接口。但是由于 CGLIB 通过继承并重写方法来实现如果需要被代理的类是一个 final 类则无法使用也无法对 final 方法或者 private 方法进行代理。 CGLIB 生成代理类的方式是通过操作字节码这种方式生成代理类的速度要比 JDK 通过反射生成代理类的速度更慢但因为直接调用子类方法在执行时更快。从整体功能上看 CGLIB 好像更强可以在项目中显示设置强制使用 CGLIB。实际上 SpringBoot2.x 起也默认使用 CGLIB 了。3. 开发实践时的一些建议永远通过接口注入不要在类内部直接调用有切面的方法可以尝试使用 CGLIB 代理不要在 Service 类和方法上加 final