答案
在上篇文章中,我们知道了TaskServiceImpl
在注入RobotServiceImpl
前后从raw version变成了代理。而且我们希望找到dependentBeanMap
这个map维护的地方。
在属性注入的代码里,我们看到了dependentBeanMap
维护的地方。那么我们想:能不能不进这个方法?
其实最终的解决方案很简单,就是在注入的TaskService
上加@Lazy
注解:
TaskService
就是有@Aysnc
方法的服务。
问题是:为什么?
原理
假设我们加上了@Lazy
注解,再次debug。容器要注入TaskController
,然后属性注入的时候发现要注入TaskService
,然后去创建TaskService
,创建好后又要属性注入,发现有RobotService
,然后又去创建RobotService
,创建好后又要属性注入,我们就从这里开始。
在这里我们会去检查RobotServiceImpl
中的TaskService
属性是不是有@Lazy
标签。
如果有这个注解,他就走buildLazyResolutionProxy(descriptor, beanName)
。那我们就会进到这个方法。
他在这里生成了一个TaskService
的代理返回了出去。到时候真正去获取TaskSerivce
的时候会回调getTarget
方法。
因为result
有值了,所以autowiredBeanNames
就是空的。
因为autowiredBeanNames
为空,所以dependentBeanMap
也没有值,或者说,他没有以TaskServiceImpl
为key的值。
那么在RobotServiceImpl
中注入的TaskService
就是一个临时的代理对象。
我们走完RobotServiceImpl
的生命周期,然后走TaskServiceImpl
的生命周期。
TaskServiceImpl
因为AsyncAnnotationBeanPostProcessor
被做了一层代理,但是由于hasDependentBean(beanName)
为false
,所以报错就不会出现了。
我们让TaskServiceImpl
的生命周期走完。
在最后将TaskServiceImpl
放到IOC容器的时候,RobotServiceImpl
持有的TaskService
也不是最终容器中的TaskService
。
那么,什么时候RobotServiceImpl
才去获取真正在容器中的TaskService
呢?
我们在TaskController
中再增加一个要用RobotService
的接口:
@RestController
public class TaskController {
@Autowired
private TaskService taskService;
@Autowired
private RobotService robotService;
@GetMapping("/taskTime")
public Long taskTime(){
long start = System.currentTimeMillis();
taskService.getTaskExecutionTime();
long end = System.currentTimeMillis();
return end-start;
}
@GetMapping("/sendCmd")
public String sendCmd(){
robotService.sendCmd("aaa");
return "ok";
}
}
我们请求sendCmd
。
一放行就来到了getTarget
方法。
这时候才去容器中拿真正的TaskService
。