Example
如果我们的类有如下成员变量:
@Component
public class A {
@Autowired
public B b; // B is a bean
public static C c; // C is also a bean
public static int count;
public float version;
public A() {
System.out.println("This is A constructor.");
}
@Autowired
public A(C c) {
A.c = c;
System.out.println("This is A constructor with c argument.");
}
@PostConstruct
public void init() {
count = 5;
System.out.println("This is A post construct.");
}
}
下面的结论可以通过在构造函数里打断点Debug来观察。
- 首先初始化的是static成员变量, 此处的
count
采用默认值0。 - 然后初始化的是非static成员变量,此处的
version
采用默认值0.0。 -
然后Spring在实例化A时选择的构造函数的原则是:如果有构造函数被
@Autowired
所修饰,则采用该构造函数(注意,@Autowired(required = true)
只能修饰一个构造函数),否则采用默认的无参构造函数。此处采用的构造函数为@Autowired public A(C c) { this.c = c; System.out.println("This is A constructor with c argument."); }
注意执行完该构造函数后,此时的成员变量B并没有被注入,值还是null。
- Spring容器选择合适的Bean注入b (DI阶段)。
- 执行被
@PostConstruct
修饰的init()
函数。
总之,在上面这个例子中,各成员变量的初始化执行顺序为:“static 成员变量 ”--> “非static成员变量” --> “被@Autowired
修饰的构造函数” --> “被@Autowired
修饰的成员变量b” --> “被@PostConstruct
修饰的init()
函数”。
上述过程也是一个Bean完整生成的过程,因此要注意不同于一般类的实例化,Bean在构造函数完成后还有DI(Dependency Injection)阶段,通过Spring容器注入它所依赖的其他Bean,想要在一个Bean生成完后进行其他操作,可以使用@PostConstruct
。
Tips
1. 静态成员依赖注入
有时我们想要对静态成员进行依赖注入(通常是Field dependency injection,即直接在成员上加@Autowired
,此种做法不推荐),直接在静态成员上加@Autowired
是无效的(其值总为null),这是因为静态成员变量是类的属性,不属于任何对象,而Spring实现Field dependency injection 是要依靠基于实例的reflection(反射)进行的。在这个例子中,Spring通过反射生成bean a, 并且发现a使用了bean b,然后去生成bean b,再次利用反射生成setter方法将b注入进a,这样就实现了Field dependency injection。通过上述过程我们可以知道static成员由于不属于任何实例,所以无法实现这样的依赖注入,但是我们可以通过Constructor dependency injection(构造函数依赖注入)来实现。以上面的例子为例,Spring在生成bean a(调用A的构造函数)时,由于A的构造函数带有参数c,Spring将在容器里寻找是否有符合c类型的bean,找到后将bean c赋值给构造函数的参数c,然后当执行到A.c = c
时成员变量c就被“注入”成功了。
2. Bean的延迟生成与注入
如果我们希望某个Bean不要在Spring容器启动时初始化(这样可以加快应用的启动速度),而是在用到时才实例化,可以用@Lazy这个注解。将这个注解加在@Bean、@Component、@Service、@Configuration
等注解上时,这些注解所修饰的Bean将在第一次引用时才实例化。我们举个简单的例子:
存在一个普通Bean
@Component
public class CommonBean {
public CommonBean () {
System.out.println("This is CommonBean constructor.");
}
}
以及加了@Lazy
的“LazyBean”
@Lazy
@Component
public class LazyBean {
public LazyBean() {
System.out.println("This is LazyBean constructor.");
}
}
假设由于某种原因,Spring扫描路径的顺序是LazyBean先于CommonBean,如果LazyBean不加@Lazy
注解,则Bean的生成顺序永远是LazyBean,CommonBean;若是加上@Lazy
,则CommonBean先生成,并且如果没有其他地方引用LazyBean(例如没有其他Bean @Autowired LazyBean),那么LazyBean将一直不进行初始化(直观表现为它的构造函数一直未被调用)。在实际应用中,如果Bean的数目比较多,无法立即理清依赖关系,但确定自己的Bean需要在其他Bean生成之后才生成时,可以通过此方法简单的控制Bean的生成顺序。
注意,若是在CommonBean里引用了LazyBean ,则LazyBean有没有使用@Lazy
,它都会先完成实例化,即Bean之间引用的作用会大于@Lazy
的作用。
@Lazy
同样可以加在@Autowired
注解上,比如
@Component
public class CommonBean {
@Lazy
@Autowired
private LazyBean lazyBean;
}
在CommonBean生成过程中的DI阶段,它不关心LazyBean是否存在于Spring容器中(如果没有添加@Lazy
注解并且没有类型为LazyBean的Bean在容器中的情况下, 会抛找不到Bean的异常),它会生成一个有同样接口的代理(由于是代理,没有真正的功能,但是LazyBean的构造函数及@PostConstruct
所修饰的方法都会被调用),只有真正要调用LazyBean的方法时,该代理才会在自身内部生成LazyBean实例(此时不会再次调用构造函数及@PostConstruct
所修饰的方法,调用代理的各种方法将会转发到生成的真正的LazyBean中),这样也相当于实现了延迟加载Bean的功能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。