Spring中Lazy、Scope注解对IOC容器Bean初始化的影响分析


面试的时候总是会遇到各种 Spring 主要功能点的问题,因为 Spring 对于 java 来说太重要。如 Spring 的 IOC 容器、动态代理、事务、切面编程等等。后期再更新文章的时候我们会慢慢讨论这些东西,这里现在我们先看其中一个功能点 IOC 容器,其实也不算是说 IOC 容器,主要的重点是放在 Spring 注解对 IOC 容器初始化的影响。也是在面试中最常说漏的。

1. 面试模拟

面试官:小明,看你的简历中说对 Spring 有熟练的理解,那问你一个关于 Spring IOC 容器的问题。
小明:嗯!
面试官:我们在 Spring 中都会使用到 IOC 容器,那就说一下 IOC 初始化的基本实现原理吧。
小明:在项目启动的时候,Spring 会去创建并加载对象到 IOC 容器中,在加载的过程可以通过以下几种方式,xml、Configuration……
面试官:嗯,说的挺好的,但是如果我在创建 bean 的时候,使用 Scope 和 Lazy 注解,你能解释以下这两个注解对初始化 bean 有什么影响。
小明:……不太清楚。
小明很明显对 IOC 容器的初始化理解还是很不错的,但是理解的都是普通模式下情景,却没有理解到外界会对这个过程的影响,也就是太片面。关于 Scope 和 Lazy 注解到底对 Spring 的 IOC 容器的初始化有什么影响呢。

2. Scope 注解的使用

@Scope注解在项目中很少用到,但是实际上底层是用到的,不过是给了默认的值。

2.1 Scope 注解属性

先瞅瞅源码:

/**
 * Alias for {@link #scopeName}.
 * @see #scopeName
 */
@AliasFor("scopeName")
String value() default "";

/**
 * Specifies the name of the scope to use for the annotated component/bean.
 * <p>Defaults to an empty string ({@code ""}) which implies
 * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
 * @since 4.2
 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
 * @see ConfigurableBeanFactory#SCOPE_SINGLETON
 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
 * @see #value
 */
@AliasFor("value")
String scopeName() default "";

scopeName 对应的值有四个分别是:

  • singleton,单例模式,这个也是默认的模式
  • prototype,多例模式,表示每次使用这个对象都是新创建的
  • request,每次请求对应一个实例,针对 web 应用
  • session,每个会话对应一个实例,针对 web 应用

这里对 sessionrequest 模式不做太多说明,博友们有兴趣可以自己研究一下。

2.2 案例说明@Scope

直接上代码,一切都不是问题。

/**
 * @Author: 程序猿洞晓
 * @Project: lazy-scope
 * @Desc: 实体类
 */
public class Person {

    public String name;
    public Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
/**
 * @Author: 程序猿洞晓
 * @Project: lazy-scope
 * @Desc: 配置类
 */
@Configuration
public class ScopeConfiguration {
    @Bean("person") //创建bean,bean的名称是person
    //@Scope("prototype")
    public Person person() {
        System.out.println("===>初始化bean");
        return new Person("zdydoit", 22);
    }
}
/**
 * @Author: 程序猿洞晓
 * @Project: lazy-scope
 * @Desc: 测试类
 */
public class ScopeConfigurationTest {

    @Test
    public void scopeTest() {
    	//加载配置类
        AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(ScopeConfiguration.class);
        System.out.println("===>配置类加载结束");
        //获取bean对象
        Person person1 = (Person) app.getBean("person");
        Person person2 = (Person) app.getBean("person");
        System.out.println(person1);
        System.out.println(person2);
        System.out.println("===>"+(person1==person2);
    }
}

运行测试类的结果如下:

===>初始化bean
===>配置类加载结束
Person{name='zdydoit', age=22}
Person{name='zdydoit', age=22}
===>true

从运行的结果可以看出来,在 IOC 容器初始化结束之前,初始化 bean 就已经完成,并且在判断两次获取到的 person 实例是否相等时,得到的返回值是 true,这个就说明当@Scopesingleton的时候,在容器加载的时候就会将 bean 创建好并放到 IOC 容器中,等待被获取使用,bean 对象是单例的。
现在将配置类上注释掉的@Scope("prototype")放开,此时再运行一下测试类的代码得到的结果如下:

===>配置类加载结束
===>初始化bean
===>初始化bean
Person{name='zdydoit', age=22}
Person{name='zdydoit', age=22}
===>false

和上面结果不同的是,有两次初始化 bean,并且初始化 bean 都是在 IOC 初始化结束后才执行的,判断两次获取到的 bean 对象是否相等,返回的是 false,可以很明确的知道,两个对象不是同一个。结论就是在 IOC 容器初始化的时候对于@Scopeprototype时,不会立即创建对象,而是每次使用的时候创建,并且每次都是创建新的对象,之前创建的不复用。

3. 案例说明@Lazy

// 实体类还是使用之前的Person
/**
 * @Author: 程序猿洞晓
 * @Project: lazy-scope
 * @Desc: 配置类
 */
@Configuration
public class LazyConfiguration {

    @Bean("person")
    @Lazy
    public Person person() {
        System.out.println("===>bean对象初始化");
        return new Person("zdydoit", 18);
    }
}
/**
 * @Author: 程序猿洞晓
 * @Project: lazy-scope
 * @Desc: 测试类
 */
public class LazyConfigurationTest {

    @Test
    public void testLazy() {
        ApplicationContext app = new AnnotationConfigApplicationContext(LazyConfiguration.class);
        System.out.println("===>配置类初始化结束");
        Person person1 = (Person) app.getBean("person");
        Person person2 = (Person) app.getBean("person");
        System.out.println(person1);
        System.out.println(person2);
        System.out.println("===>" + (person1 == person2));
    }
}

运行测试类得到的结果如下:

===>配置类初始化结束
===>bean对象初始化
Person{name='zdydoit', age=18}
Person{name='zdydoit', age=18}
===>true

bean 的初始化是在 IOC 容器初始化结束以后执行的,也就是在getBean()执行的时候才去做 bean 的初始化,当第二次getBean()的时候就不会再去初始化,而是采用的第一次 bean 初始化的对象,最终两个 bean 的等于判断也是相等的,由此可以看来@Lazy就相当于懒汉模式,第一次请求创建 bean,然后 bean 会被放入到 IOC 容器中,后续请求直接使用。

4. 总结

从上面分析看的出来,在 Spring 容器初始化的时候,并不会把所有的对象都初始化并放到容器中,对于@Scope("prototype")@Lazy,都是使用的时候去创建,两个不同之处在于一个创建多个(多例),一个创建单个(单例)。
从上面的分析,如果在面试的时候问到 Spring 的 IOC 容器初始化,是不是又多了一些可以说(zhuang)的机会了。


文章作者: 程序猿洞晓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 程序猿洞晓 !
评论
 上一篇
Fork-Join内部实现原理分析 Fork-Join内部实现原理分析
在做一个长的任务的时候,需要消耗的时间很长,但是这个时候主流程又要等待这个长任务执行结束后才能执行。如这里这个长任务可能是从数据库中查出报表数据,计算封装出可以写入到报表的数据,主流程在等待往报表中写数据,但是长任务中迟迟计算不出报表数据,因此这里就需要考虑多线程,如果直接创建一个子线程,主线程还是需要等待这个子线程执行结束,这个方案……
2018-06-30
下一篇 
死磕Java并发:深入分析ThreadLocal原理 死磕Java并发:深入分析ThreadLocal原理
ThreadLocal是啥?以前面试别人时就喜欢问这个,有些伙伴喜欢把它和线程同步机制混为一谈,事实上ThreadLocal与线程同步无关。ThreadLocal虽然提供了一种解决多线程环境下成员变量的问题,但是它并不是解决多线程共享变量的问题。那么ThreadLocal到底是什么呢?API是这样介绍它的:This class ……
2018-06-24
  目录