SpringBoot(6)—Bean懒加载@Lazy和循环依赖处理
==========================Bean懒加载@Lazy介绍==================================
⼀、问题介绍
  Spring在启动时,默认会⽴即将单实例bean进⾏实例化,并加载到Spring容器中。也就是说,单实例bean默认在Spring容器启动的时候创建对象,并将对象加载到Spring容器中。如果我们需要对某个bean进⾏延迟加载(延迟到在第⼀次调⽤的时候实例化),我们该如何处理呢?此时,就需要使⽤到@Lazy注解了。
⼆、如何配置懒加载
1、在xml配置中
<beans ... default-lazy-init="true"> //全局配置
<bean ...  lazy-init="true" /> //指定bean配置
2、在JavaConfig配置中
/
/全局配置
@Configuration
@Lazy
public class AppConfig {}
//指定bean配置
@Configuration
public class AppConfig{
@Bean
@Lazy
public LazyBean lazyBean(){
return new LazyBean();
}
}
3、SpringBoot中指定bean的懒加载,可以在对应的类上直接使⽤@Lazy
//指定bean配置
@Component
@Lazy
public class LazyBean {
public LazyBean() {
System.out.println("LazyBean should be lazzzzyyyyyy");
}
public void doSomething() {}
}
  那么SpringBoot中如何全局配置懒加载呢?
  通过在stackoverflow上查, 发现的答案是, 在启动类SpringbootApplication上加上@Lazy注解即可. 原来注解@SpringBootApplication是@Configuration,
@EnableAutoConfiguration和@ComponentScan注解的合体.
  ⽽这个SpringbootApplication本⾝就是个配置类, 所以在上⾯加@Lazy注解理论上是可以的.果然是直观的东西不⽅便, ⽅便的东西不直观.
(1)错误⽅式⼀:
//spring boot中声明bean
@Component
public class LazyBean {
public LazyBean() {
System.out.println("LazyBean should be lazzzzyyyyyy");
}
public void doSomething() {}
}
//配置类上加注解
@SpringBootApplication
@Lazy
public class SpringbootApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(SpringbootApplication.class, args);
}
}
  启动应⽤, 发现输出了
LazyBean should be lazzzzyyyyyy
  也就是说配置并没有⽣效. 但是so上的回答⼀般不会是错的. 那会是哪⾥出了问题呢?
(2)⽅式⼀修正
  不使⽤@Component, ⽽是在配置⽂件中声明bean:
//@Component
public class LazyBean {
public LazyBean() {
System.out.println("LazyBean should be lazzzzyyyyyy");
}
public void doSomething() {}
}
//配置类
@SpringBootApplication
@Lazy
public class SpringbootApplication {
//在配置类中声明bean
@Bean
public LazyBean lazyBean() {
return new LazyBean();
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(SpringbootApplication.class, args);
}
}
  这种⽅式实现了懒加载,但是这跟2(在JavaConfig配置中)中的⽅式是⼀样的.
(3)⽅式⼆
  spring2.2中引⼊了⼀个application.properties中的新属性.
spring.main.lazy-initialization=true//指定整个应⽤的懒加载.
  这种⽅式不论是@Component声明的bean,还是@Bean声明的bean, 都可以实现懒加载.
三、@Lazy的属性
  @Lazy只有⼀个属性value,value取值有 true 和 false 两个,默认值是true
  true 表⽰使⽤延迟加载, false 表⽰不使⽤,false 纯属多余,如果不使⽤,不标注该注解就可以了。
  通过以下⽰例看看使⽤注解和不使⽤注解的区别
  Person 类
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
System.out.println(" 对象被创建了.............");
this.name = name;
this.age = age;
}
// 省略 getter setter 和 toString ⽅法
}
1、配置类不标注@Lazy注解(不使⽤延迟加载)
public class LazyConfig {
@Bean
public Person person() {
return new Person("李四", 55);
}
}
  测试:
@Test
public void test5() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(LazyConfig.class);
}
  结果:
  结论:我们发现,没有获取bean,但是打印了语句,说明对象调⽤了构造器,那么⽅法也就被创建了
2、在配置类打上 @Lazy 注解
public class LazyConfig {
@Lazy
@Bean
public Person person() {
return new Person("李四", 55);
}
}
  结果:
  结论:我们发现,没有获取bean,没有打印了语句,说明对象没有调⽤构造器,那么⽅法就没有被创建了
注意:
  1、@Lazy(value = false) 或者 @Lazy(false) 那么对象会在初始化的时候被创建,相当于没有使⽤@Lazy注解,@Lazy注解默认值为true
  2、@Lazy注解的作⽤主要是减少springIOC容器启动的加载时间
  3、当出现循环依赖的时候,也可以添加@Lazy
  4、虽然懒加载可以提升应⽤的启动速度, 但是不利于尽早的发现错误, 对于HTTP请求, ⾸次访问的响应时间也会增长.
===========================Spring中循环的循环依赖============================
⼀、什么是循环依赖
  ⼀般场景是⼀个Bean A依赖Bean B,⽽Bean B也依赖Bean A.
  Bean A → Bean B → Bean A
  当然我们也可以添加更多的依赖层次,⽐如:
  Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
⼆、Spring中的循环依赖
  当Spring上下⽂在加载所有的bean时,他会尝试按照他们他们关联关系的顺序进⾏创建。⽐如,如果不存在循环依赖时,例如:
Bean A → Bean B → Bean C
  Spring会先创建Bean C,再创建Bean B(并将Bean C注⼊到Bean B中),最后再创建Bean A(并将Bean B注⼊到Bean A中)。
但是,如果我们存在循环依赖,Spring上下⽂不知道应该先创建哪个Bean,因为它们依赖于彼此。在这种情况下,Spring会在加载上下⽂时,抛出⼀个BeanCurrentlyInCreationException。
  当我们使⽤构造⽅法进⾏注⼊时,也会遇到这种情况,因为JVM虚拟机在对类进⾏实例化的时候,需先实例化构造器的参数,⽽由于循环引⽤这个参数⽆法提前实例化,故只能抛出错误。如果您使⽤其它类型的注⼊,你应该不会遇到这个问题。因为它是在需要时才会被注⼊,⽽不是上下⽂加载被要求注⼊。
三、⽰例
  我们定义两个Bean并且互相依赖(通过构造函数注⼊)。
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(CircularDependencyB circB) {
this.circB = circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(CircularDependencyA circA) {
this.circA = circA;
}
}
  现在,我们写⼀个测试配置类,姑且称之为TestConfig,指定基本包扫描。假设我们的Bean在包“com.baeldung.circulardependency”中定义:
@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}
  最后,我们可以写⼀个JUnit测试,以检查循环依赖。该测试⽅法体可以是空的,因为循环依赖将上下⽂加载期间被检测到。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
@Test
public void givenCircularDependency_whenConstructorInjection_thenItFails() {
// Empty test; we just want the context to load
}
}
  如果您运⾏这个测试,你会得到以下异常:
BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?
四、解决办法
  我们将使⽤⼀些最流⾏的⽅式来处理这个问题。
1、重新设计
  当你有⼀个循环依赖,很可能你有⼀个设计问题并且各责任没有得到很好的分离。你应该尽量正确地重新设计组件,以便它们的层次是精⼼设计的,也没有必要循环依赖。
  如果不能重新设计组件(可能有很多的原因:遗留代码,已经被测试并不能修改代码,没有⾜够的时间或资源来完全重新设计......),但有⼀些变通⽅法来解决这个问题。
2、使⽤@Lazy
  解决Spring 循环依赖的⼀个简单⽅法就是对⼀个Bean使⽤延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注⼊的是⼀个代理,只有当他⾸次被使⽤的时候才会被完全的初始化。
  我们对CircularDependencyA 进⾏修改,结果如下:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
  如果你现在运⾏测试,你会发现之前的错误不存在了。
3、使⽤Setter/Field注⼊
  其中最流⾏的解决⽅法,就是中建议,使⽤setter注⼊。
  简单地说,你对你须要注⼊的bean是使⽤setter注⼊(或字段注⼊),⽽不是构造函数注⼊。通过这种⽅式创建Bean,实际上它此时的依赖并没有被注⼊,只有在你须要的时候他才会被注⼊进来。
  让我们开始动⼿⼲吧。我们将在CircularDependencyB 中添加另⼀个属性,并将我们两个Class Bean从构造⽅法注⼊改为setter⽅法注⼊:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
  现在,我们对修改后的代码进单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
@Autowired
ApplicationContext context;
@Bean
public CircularDependencyA getCircularDependencyA() {
return new CircularDependencyA();
}
@Bean
public CircularDependencyB getCircularDependencyB() {
return new CircularDependencyB();
}
@Test
public void givenCircularDependency_whenSetterInjection_thenItWorks() {
CircularDependencyA circA = Bean(CircularDependencyA.class);
Assert.assertEquals("Hi!", CircB().getMessage());
spring ioc注解}
}
  下⾯对上⾯看到的注解进⾏说明:
  @Bean:在Spring框架中,标志着他被创建⼀个Bean并交给Spring管理
  @Test:测试将得到从Spring上下⽂中获取CircularDependencyA bean并断⾔CircularDependencyB已被正确注⼊,并检查该属性的值。
4、使⽤@PostConstruct
  打破循环的另⼀种⽅式是,在要注⼊的属性(该属性是⼀个bean)上使⽤ @Autowired ,并使⽤@PostConstruct 标注在另⼀个⽅法,且该⽅法⾥设置对其他的依赖。
  我们的Bean将修改成下⾯的代码:
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
@PostConstruct
public void init() {
circB.setCircA(this);
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
  现在我们运⾏我们修改后的代码,发现并没有抛出异常,并且依赖正确注⼊进来。
5、实现ApplicationContextAware and InitializingBean接⼝
  如果⼀个Bean实现了ApplicationContextAware,该Bean可以访问Spring上下⽂,并可以从那⾥获取到其他的bean。实现InitializingBean接⼝,表明这个bean在所有的属性设置完后做⼀些后置处理操作(调⽤的顺序为init-method后调⽤);在这种情况下,我们需要⼿动设置依赖。
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
private CircularDependencyB circB;
private ApplicationContext context;
public CircularDependencyB getCircB() {
return circB;
}
@Override
public void afterPropertiesSet() throws Exception {
circB = Bean(CircularDependencyB.class);
}
@Override
public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
context = ctx;
}
}
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
  同样,我们可以运⾏之前的测试,看看有没有异常抛出,程序结果是否是我们所期望的那样。
五、总结

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。