目录
- @Lazy作用在类上
- @Lazy注解作用在字段上
- @Lazy注解标记的字段或方法中的参数何时触发加载
- AOP代理中的TargetSource对象
- 为什么使用了 @Lazy 之后,就能解决循环依赖问题,正常启动了呢?
- 案例
- @Resource对@Lazy注入的处理
参考: https://blog.csdn.net/wang489687009/article/details/120577472
参考: https://blog.csdn.net/qq_18297675/article/details/103267125
@Lazy作用在类上
比如下面代码
@Service
@Lazy
public class A {}
Spring注册bean的流程如下:
refresh
-> finishBeanFactoryInitialization
-> preInstantiateSingletons
@Override
public void preInstantiateSingletons() throws BeansException {// 省略其他代码// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// 开始实例化所有非懒加载的类的// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);// 省略其他代码}else {getBean(beanName);}}}// 省略其他代码
}
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit())条件表示,不是抽象类并且是单例,且不是懒加载的bean(类上面没有标记@Lazy注解),就走getBean方法的逻辑去创建bean,否则什么也不会做,即@Lazy注解作用在类上,在Spring容器刷新时是不会去创建该bean的。
@Lazy注解作用在字段上
比如
@Service
public class B {@Autowired@Lazyprivate A a;}
此时注册B的bean,由于if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit())这个条件都符合,即默认单例不是抽象类,类上面也没有加@Lazy注解,所以此时会走getBean的流程,如下:
getBean
-> doGetBean
-> createBean
-> doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}// 省略其他代码// Initialize the bean instance.Object exposedObject = bean;try {populateBean(beanName, mbd, instanceWrapper);exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {// 省略其他代码}// 省略其他代码return exposedObject;
}
在调用createBeanInstance方法创建B的bean之后,接着会调用populateBean方法填充属性,即会去注入A这个bean,流程如下:
populateBean
-> AutowiredAnnotationBeanPostProcessor#postProcessProperties
-> AutowiredFieldElement#inject
-> DefaultListableBeanFactory#resolveDependency
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 省略其他代码else {Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);if (result == null) {// 如果该字段没有被@Lazy注解标注,那么就会通过这个方法去创建该beanresult = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}
重点关注ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary
方法的实现如下:
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);}
先判断这个字段是否标记了@Lazy
protected boolean isLazy(DependencyDescriptor descriptor) {for (Annotation ann : descriptor.getAnnotations()) {Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);if (lazy != null && lazy.value()) {return true;}}MethodParameter methodParam = descriptor.getMethodParameter();if (methodParam != null) {Method method = methodParam.getMethod();if (method == null || void.class == method.getReturnType()) {Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);if (lazy != null && lazy.value()) {return true;}}}return false;}
如果标记了,那么执行buildLazyResolutionProxy方法
// 延迟bean的创建,为bean提前创建一个代理对象返回,并不会真正的创建bean对象
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,"BeanFactory needs to be a DefaultListableBeanFactory");final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();//TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return descriptor.getDependencyType();}@Overridepublic boolean isStatic() {return false;}// 当第一次调用该代理对象的方法时,才会走getTarget方法里面的doResolveDependency方法去创建真正的bean对象// 如果是JDK生成的代理对象,具体调用在JdkDynamicAopProxy#invoke方法中@Overridepublic Object getTarget() {Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);if (target == null) {Class<?> type = getTargetClass();if (Map.class == type) {return Collections.emptyMap();}else if (List.class == type) {return Collections.emptyList();}else if (Set.class == type || Collection.class == type) {return Collections.emptySet();}throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),"Optional dependency not present for lazy injection point");}return target;}@Overridepublic void releaseTarget(Object target) {}};ProxyFactory pf = new ProxyFactory();// 将ts保存到代理工厂中pf.setTargetSource(ts);Class<?> dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {pf.addInterface(dependencyType);}// 创建一个代理对象来注入return pf.getProxy(beanFactory.getBeanClassLoader());}
综上,@Lazy注解标注的字段并不会去触发依赖bean的加载,而是提前生成一个代理bean对象返回,并会在第一次调用代理对象的方法时触发bean的加载。
同理,如果@Lazy标记的是方法中的参数也是类似逻辑,只不过链路逻辑为:
populateBean
-> AutowiredAnnotationBeanPostProcessor#postProcessProperties
-> AutowiredMethodElement#inject
-> DefaultListableBeanFactory#resolveDependency
@Lazy注解标记的字段或方法中的参数何时触发加载
还是以刚刚的buildLazyResolutionProxy方法源码说明,先来看看TargetSource对象,是如何起作用的
- 首先new了一个ProxyFactory 对象,然后把TargetSource对象设置进去,其实是保存在其父类的
targetSource
字段中的
- 执行
pf.getProxy(beanFactory.getBeanClassLoader())
方法
public Object getProxy(@Nullable ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);}
- 执行createAopProxy方法
protected final synchronized AopProxy createAopProxy() {if (!this.active) {activate();}return getAopProxyFactory().createAopProxy(this);}
- 执行createAopProxy(this)方法,并把pf这个对象作为参数传进去,用
advised
字段保存
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}// 如果代理的是接口,那么就生成一个JdkDynamicAopProxy对象,并把ProxyFactory 对象传进去,注意它持有TargetSource对象的引用if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}// 否则生成一个ObjenesisCglibAopProxy对象,此种方式是cglib代理实现return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}
- 以JDK动态代理为例,上述返回JdkDynamicAopProxy对象之后,接着回到第二步,调用JdkDynamicAopProxy#getProxy(classLoader)方法
public Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());}// 获取要代理的目标类的所有接口Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);// 生成一个代理对象返回,并且this是当前执行getProxy方法的对象即JdkDynamicAopProxy对象return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}
至此,pf.getProxy(beanFactory.getBeanClassLoader())
方法生成代理对象的逻辑走完了,接下来看一下这个生成的代理对象第一次调用方法时的逻辑
因为JdkDynamicAopProxy实现了InvocationHandler接口,所以当第一次调用方法时,最终会执行JdkDynamicAopProxy的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;// 获取之前保存的ProxyFactory对象,同时获取该对象中的TargetSource对象TargetSource targetSource = this.advised.targetSource;Object target = null;try {// 省略部分代码Object retVal;// 以前好奇为什么通过AopContext#currentProxy()方法能获取到代理对象从而解决自调用导致事务失效的问题// 原来通过@EnableAspectJAutoProxy(exposeProxy = true)注解来实现的,当exposeProxy 为true时,就会走下面的方法if (this.advised.exposeProxy) {// 把代理对象设置进去,底层是一个ThreadLocalMap实现(ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy"))// 后续当前调用线程就能愉快的在执行目标方法时获取到当前执行方法的代理对象了oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// 重点,这里就调用了targetSource的getTarget方法,前面已经介绍过,它会调用doResolveDependency去加载真正的bean,并把bean放入一级缓存中,后续再调用从一级缓存中获取即可target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);// 根据方法对象去获取AOP方法拦截器链,里面会进行优化,这里就不展开讲了,大致流程如下:// 1.创建一个MethodCacheKey对象,该对象持有method对象的引用,然后根据MethodCacheKey对象去缓存中获取增强器链// 2.如果获取不到就去advisorChainFactory工厂中获取// 3.把获取到的增强器适配成方法拦截器,然后添加到方法拦截器链中// 4.把MethodCacheKey对象作为key、方法拦截器链作为value存进Map<MethodCacheKey, List<Object>> methodCache中,下次可以根据MethodCacheKey直接从缓存中获取List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);if (chain.isEmpty()) {// 如果方法拦截器链为空,直接反射执行目标bean对象的方法Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {// 否则创建一个MethodInvocation对象来执行目标方法,里面会根据方法拦截器链的顺序来执行,具体就不展开讲了MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}// 省略部分代码return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.targetSource.releaseTarget(target);}if (setProxyContext) {AopContext.setCurrentProxy(oldProxy);}}}
综上,当一次调用@Lazy注解生成的代理对象方法时,里面会执行targetSource.getTarget()方法,并在第一次执行完成之后会把真实对象放入一级缓存中,后续再次调用直接从一级缓存中获取即可。
AOP代理中的TargetSource对象
经过前面分析,我们知道代理对象执行方法时,都会去调用targetSource.getTarget()方法,那AOP代理生成的代理对象中的targetSource对象又有什么不同呢?
没有发生循环依赖时,AOP动态代理是在bean初始化之后通过AnnotationAwareAspectJAutoProxyCreator
后置处理器生成的,我们来看下它的流程
getBean
->doGetBean
-> createBean
-> doCreateBean
-> initializeBean
-> applyBeanPostProcessorsAfterInitialization
> AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization
- postProcessAfterInitialization方法
// AnnotationAwareAspectJAutoProxyCreator 继承父类AbstractAutoProxyCreator的方法
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 生成代理对象的逻辑return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}
- wrapIfNecessary方法
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 获取bean的环绕通知Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 如果通知不为空,说明要为bean创建代理对象,注意这个new SingletonTargetSource(bean)就是AOP代理生成的targetSource对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}
看下SingletonTargetSource类的代码
可以看到getTarget方法只是返回了目标对象
public class SingletonTargetSource implements TargetSource, Serializable {private static final long serialVersionUID = 9031246629662423738L;private final Object target;public SingletonTargetSource(Object target) {Assert.notNull(target, "Target object must not be null");this.target = target;}@Overridepublic Class<?> getTargetClass() {return this.target.getClass();}@Overridepublic Object getTarget() {return this.target;}@Overridepublic void releaseTarget(Object target) {// nothing to do}@Overridepublic boolean isStatic() {return true;}// 省略部分代码}
- createProxy方法
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (!proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);// 将增强器链保存到代理工厂对象中,后续执行代理对象方法时,会根据目标对象的method去获取对应的增强器链,并把增强器链转换成方法拦截器链,然后保存到缓存中(前面有说过这部分逻辑)proxyFactory.addAdvisors(advisors);// 将targetSource对象保存到代理工厂对象中proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 前面讲过这块逻辑,就是创建一个代理对象,并且该代理对象还会持有proxyFactory对象引用(JdkDynamicAopProxy、ObjenesisCglibAopProxy)return proxyFactory.getProxy(getProxyClassLoader());}
综上,可以看到AOP动态代理生成的targetSource对象是SingletonTargetSource对象,它的getTarget方法在每次调用时都会返回目标对象;而@Lazy注解生成的targetSource对象,第一次调用getTarget方法时会去加载目标bean对象,之后每次调用getTarget方法都会去从一级缓存中获取。
为什么使用了 @Lazy 之后,就能解决循环依赖问题,正常启动了呢?
Spring 默认为我们解决了常规场景下的循环依赖问题。但是有些特殊的场景下的循环依赖,Spring 默认是没有解决的。主要的场景如下:
- prototype 类型的循环依赖
- constructor 注入的循环依赖
- @Async 类型的 Bean 的循环依赖
这些特殊的场景,我们都可以通过 @Lazy 来解决。
@Lazy 的本质就是将注入的依赖变成了一个代理对象。使用 @Lazy 时,不会触发依赖 bean 的加载。
假设代码如下:
@Service
public class A {private B b;public A(@Lazy B b) {this.b = b;}
}
@Service
public class B {private A a;public B(A a) {this.a = a;}
}
假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。那么,就不会去直接加载 B,而是产生了一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。B 加载时,再去注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。所以,循环依赖的问题就解决了。后续A中调用B的方法时,由于注入的B是代理对象,所以会去走获取bean的流程,因为前面已经注册了B的bean,所以可以直接从一级缓存中取到B的bean。
案例
如下代码能正常启动吗
@Service
@Lazy
public class B {@Autowiredprivate A a;}
@Service
public class A {@Autowiredprivate B b;@Asyncpublic void test() {}
}
结果:启动失败,报错
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a'
has been injected into other beans [b] in its raw version as part of a circular reference, but has eventually been wrapped.
This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider
using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
分析:
之前分析过,@Lazy加载类上面,Spring刷新时是不会加载该bean的,所以B这个bean在容器刷新时不会去创建;
- 当创建A这个bean时,发现自己依赖了B,所以去加载B这个bean
- B从三级缓存中获取A的早期引用,因为A有@Async注解标注的方法,是不会提前生成代理对象的,所以B获取的早期引用是A的初始bean对象
- 此时B初始化完成之后,A接着执行初始化时会由AbstractAdvisingBeanPostProcessor后置处理器生成一个代理对象返回
- 此时由于B引用了A的初始对象,和最终生成的A代理对象不一致,所以Spring检查出来并提示报错信息
解决方法:
- 把Lazy注解放到字段上
- 或者把Lazy注解放到@Async标注方法的类上
@Service
public class B {@Autowiredprivate A a;}
@Service
@Lazy
public class A {@Autowiredprivate B b;@Asyncpublic void test() {}
}
分析:
为什么这样就能启动起来
- 因为A类标注@Lazy注解,所以A在Spring刷新容器时不会去加载
- 加载B的bean时,发现自己依赖了A,所以去创建A的bean
- 加载A的bean时,发现自己依赖了B,所以能从三级缓存中获取B的早期引用,接着初始化完成之后,为A生成一个代理对象
- 因为A没有其他类引用着,所以在获取A的早期引用时是null的,不会走之前的分支,把代理A对象放进一级缓存,同时返回代理A对象给到B,B拿到A的代理对象之后,最终执行完初始化操作放入一级缓存中
@Resource对@Lazy注入的处理
@Resource 注入时是通过ResourceElement类来处理的。且注入时会处理@Lazy的逻辑,相应的代码如下:
// CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject()
protected Object getResourceToInject(Object target, String requestingBeanName) {return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :getResource(this, requestingBeanName));
}protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return element.lookupType;}@Overridepublic boolean isStatic() {return false;}@Overridepublic Object getTarget() {return getResource(element, requestingBeanName);}@Overridepublic void releaseTarget(Object target) {}};ProxyFactory pf = new ProxyFactory();pf.setTargetSource(ts);if (element.lookupType.isInterface()) {pf.addInterface(element.lookupType);}ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);return pf.getProxy(classLoader);
}
具体逻辑和上述的@Autowire对@Lazy注解标注的bean注入逻辑类似,这里不在分析