首页 > 编程语言 > Java Spring @Lazy延迟注入源码案例详解
2023
01-13

Java Spring @Lazy延迟注入源码案例详解

前言

有时候我们会在属性注入的时候添加@Lazy注解实现延迟注入,今天咱们通过阅读源码来分析下原因

一、一个简单的小例子

代码如下:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class NormalService1 {
 
    @Autowired
    @Lazy
    private MyService myService;
 
    public void doSomething() {
        myService.getName();
    }
}

作用是为了进行延迟加载,在NormalService1进行属性注入的时候,如果MyService还没有生成bean也不用担心,会注入一个代理,但是在实际运行的时候,会获取Spring容器中实际的MyService,在某些情况下,因为spring生命周期的原因,这个注解有大用。

二、源码解读

1. 注入

代码如下(DefaultListableBeanFactory#resolveDependency):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
 
        descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
        if (Optional.class == descriptor.getDependencyType()) {
            return createOptionalDependency(descriptor, requestingBeanName);
        }
        else if (ObjectFactory.class == descriptor.getDependencyType() ||
                ObjectProvider.class == descriptor.getDependencyType()) {
            return new DependencyObjectProvider(descriptor, requestingBeanName);
        }
        else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
            return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
        }
        else {
            //如果注入属性添加了@Lazy,懒加载,此时spring会根据具体类型搞个cglib代理类
            Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                    descriptor, requestingBeanName);
            if (result == null) {
                result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
            }
            return result;
        }
    }

很明显要执行getLazyResolutionProxyIfNecessary方法,如果加了@Lazy注解,最终会执行buildLazyResolutionProxy方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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() {
            @Override
            public Class<?> getTargetClass() {
                return descriptor.getDependencyType();
            }
            @Override
            public boolean isStatic() {
                return false;
            }
            @Override
            public Object getTarget() {
                Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
                /**
                something valid
                **/
                return target;
            }
            @Override
            public void releaseTarget(Object target) {
            }
        };
        ProxyFactory pf = new ProxyFactory();
        pf.setTargetSource(ts);
        Class<?> dependencyType = descriptor.getDependencyType();
        if (dependencyType.isInterface()) {
            pf.addInterface(dependencyType);
        }
        return pf.getProxy(beanFactory.getBeanClassLoader());
    }

可以看到上面这段代码,其实就是生成了一个TargetSource,然后再生成了一个代理(CGLIB或者JDK),然后作为MyService对象注入给了NormalService1。那么所谓的执行的过程中才进行获取真正的MyService对象是什么意思呢?

2. 使用逻辑

本文示例代码使用的是CGLIB代理,其实是类似的,因为注入的MyService是个CGLIB代理对象,那么在执行方法的时候,就会调用CglibAopProxy#DynamicAdvisedInterceptor#intercept方法

在这里插入图片描述

那么此处其实调用的就是上面的

1
Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);

这个方法就不用认真看了,主要功能就是从Spring容器中找到MyService。
在之前讲@Autowired原理和@Resource注入原理的时候解释过了,不清楚的可以看专栏里其他文章。
拿出来之后会发现,咱们拿到的target对象还是一个CGLIB代理的对象

在这里插入图片描述

那么当执行方法逻辑时

在这里插入图片描述

由于target是CGLIB对象,会再次进入到CglibAopProxy#DynamicAdvisedInterceptor#intercept方法。
此时拿到的target对象类型就不同了

在这里插入图片描述

是我们代理之前的target对象,此时再次进行invoke的时候,就会进行动态代理的一般逻辑,先查找该方法匹配的所有advice,然后依次调用,最终调用target本身对于方法的执行。

总结

所以可以发现其实@Lazy只不过是给spring的代理对象proxy再进行了一次proxy,只不过没有在注入的时候,就获取到对象,而是借用了方法invoke时通过proxy的intercept方法getTarget,然后进行方法调用,延迟了对象的注入。之后每次调用的时候都需要从Spring容器中获取到原生的proxy对象。

在这里插入图片描述

到此这篇关于Java Spring @Lazy延迟注入源码案例详解的文章就介绍到这了,更多相关Java Spring @Lazy延迟注入源码内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧