面试的时候总是会遇到各种 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 应用
这里对 session
、request
模式不做太多说明,博友们有兴趣可以自己研究一下。
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,这个就说明当@Scope
为singleton
的时候,在容器加载的时候就会将 bean 创建好并放到 IOC 容器中,等待被获取使用,bean 对象是单例的。
现在将配置类上注释掉的@Scope("prototype")
放开,此时再运行一下测试类的代码得到的结果如下:
===>配置类加载结束
===>初始化bean
===>初始化bean
Person{name='zdydoit', age=22}
Person{name='zdydoit', age=22}
===>false
和上面结果不同的是,有两次初始化 bean,并且初始化 bean 都是在 IOC 初始化结束后才执行的,判断两次获取到的 bean 对象是否相等,返回的是 false,可以很明确的知道,两个对象不是同一个。结论就是在 IOC 容器初始化的时候对于@Scope
为prototype
时,不会立即创建对象,而是每次使用的时候创建,并且每次都是创建新的对象,之前创建的不复用。
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)的机会了。