Spring中的上万@Lazy注解真的可以实现Bean的延迟创建吗?
平时工作过程中,不知道大家有没有遇到过这样一种场景:应用程序可能会在启动的注字时候创建大量的对象,加载大量的解也配置文件来进行初始化工作。但是上万在程序运行的过程中,这些对象或者配置文件使用的注字频率并不是很频繁,甚至是解也只有个别很少使用的功能在使用这些配置文件。
此时,上万为了优化应用的注字启动性能,我们就可以对这些对象的解也创建和配置文件的加载进行延迟处理。也就是上万说,在应用启动的时候不去创建这些对象和加载配置文件,而是到触发某些功能操作时,再去创建这些对象和加载配置文件,这就是一种延迟处理的操作。
在设计模式的单例模式中,会分为懒汉模式和饿汉模式,其中,懒汉模式就是一种延迟创建对象的模式。
关于@Lazy注解的一点点说明~~
对于单例Bean来说,如果不想在IOC容器启动的时候就创建Bean对象,而是在第一次使用时创建Bean对象,就可以使用@Lazy注解进行处理。
@Lazy注解可以标注到类、方法、构造方法、参数和属性字段上,能够实现在启动IOC容器时,不创建单例Bean,而是在第一次使用时创建单例Bean对象。源码详见:org.springframework.context.annotation.Lazy。
/**
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
boolean value() default true;
}
从源码可以看出,@Lazy注解是从Spring3.0版本开始提供的注解,其中,只提供了一个boolean类型的value属性,具体含义如下所示。
true:表示延迟创建单例Bean,此时在IOC启动时不会创建Bean对象,而是在第一次使用时创建单例Bean对象。
false:表示不延迟创建单例Bean对象,IOC容器启动时,就会创建单例Bean对象。
注意:使用@Lazy直接延迟创建单例Bean,不是延迟加载思想,因为不是每次使用时都创建,只是改变了第一次创建单例Bean的时机。
在实际开发过程中,如果使用Spring创建的Bean是单例对象时,有时并不是每个单例Bean对象都需要在IOC容器启动时就创建,有些单例Bean可以在使用的时候再创建。此时,就可以使用@Lazy注解实现这样的场景。
注意:@Lazy注解只对单例Bean对象起作用,如果使用@Scope注解指定为多例Bean对象,则@Lazy注解将不起作用。
@Lazy的实现案例,我们一起实现吧~~
本节,就使用@Lazy注解实现延迟创建Bean的案例。本节主要从创建单例Bean、添加@Lazy注解和获取单例Bean三个方面实现案例程序。
本小节,完成创建单例Bean的案例部分,具体步骤如下所示。
(1)新增LazyBean类
LazyBean类的源码详见:spring-annotation-chapter-09工程下的io.binghe.spring.annotation.chapter09.bean.LazyBean。
public class LazyBean {
public LazyBean(){
System.out.println("执行LazyBean类的构造方法...");
}
}
可以看到,LazyBean类就是一个普通的实体类对象,在LazyBean类的构造方法中,打印了执行LazyBean类的构造方法...的日志。
(2)新增LazyConfig类
LazyConfig类的源码详见:spring-annotation-chapter-09工程下的io.binghe.spring.annotation.chapter09.config.LazyConfig。
@Configuration
public class LazyConfig {
@Bean
public LazyBean lazyBean(){
return new LazyBean();
}
}
可以看到,LazyConfig类是Spring中的配置类,在LazyConfig类中使用@Bean注解创建了LazyBean类的单例Bean对象,同时在lazyBean()方法上并没有标注@Lazy注解。
(3)新增LazyTest类
LazyTest类的源码详见:spring-annotation-chapter-09工程下的io.binghe.spring.annotation.chapter09.LazyTest。
public class LazyTest {
public static void main(String[] args) {
System.out.println("创建IOC容器开始...");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LazyConfig.class);
System.out.println("创建IOC容器结束");
}
}
可以看到,LazyTest类主要是测试案例程序,在main()方法中,创建了IOC容器,并在创建IOC容器前后打印了相关的日志信息。
(4)运行LazyTest类
运行LazyTest类中的main()方法,输出的结果信息如下所示。
创建IOC容器开始...
执行LazyBean类的构造方法...
创建IOC容器结束...
从输出的结果信息可以看出,打印了LazyBean类的构造方法中输出的日志信息。
说明:Spring会在IOC容器启动时,创建单例Bean。
本小节在3.1小节的基础上,完成案例添加@Lazy注解的部分,具体实现步骤如下所示。
(1)修改LazyConfig类
在LazyConfig类的lazyBean()方法上添加@Lazy注解,如下所示。
@Bean
@Lazy
public LazyBean lazyBean(){
return new LazyBean();
}
(2)运行LazyTest类
运行LazyTest类中的main()方法,输出的结果信息如下所示。
创建IOC容器开始...
创建IOC容器结束...
可以看到,输出的结果信息中并没有打印LazyBean类的构造方法中输出的日志信息。
说明:在创建单实例Bean的方法上添加@Lazy注解时,当IOC容器启动时,并不会创建单例Bean。
本小节在3.2小节的基础上,完成案例获取单例Bean的部分,具体实现步骤如下所示。
(1)修改LazyTest类
在LazyTest类的main()方法中,创建完IOC容器,从IOC容器中多次获取LazyBean类的Bean对象,如下所示。
public class LazyTest {
public static void main(String[] args) {
System.out.println("创建IOC容器开始...");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LazyConfig.class);
System.out.println("创建IOC容器结束...");
System.out.println("从IOC容器中获取Bean开始...");
LazyBean lazyBean1 = context.getBean(LazyBean.class);
LazyBean lazyBean2 = context.getBean(LazyBean.class);
System.out.println("(lazyBean1 是否等于 lazyBean2) ===>>> " + (lazyBean1 == lazyBean2));
System.out.println("从IOC容器中获取Bean结束...");
}
}
可以看到,在LazyTest类的构造方法中,创建完IOC容器中,从IOC容器中连续获取两次LazyBean类的Bean对象,并打印两次获取的Bean对象是否相等。
(2)运行LazyTest类
运行LazyTest类中的main()方法,输出的结果信息如下所示。
创建IOC容器开始...
创建IOC容器结束...
从IOC容器中获取Bean开始...
执行LazyBean类的构造方法...
(lazyBean1 是否等于 lazyBean2) ===>>> true
从IOC容器中获取Bean结束...
从输出的结果信息可以看出,从第一次从IOC容器中获取Bean对象时,打印了LazyBean类的构造方法中输出的日志信息,并且两次从IOC容器中获取到的Bean对象相同。
说明:当在创建单例Bean的方法上标注@Lazy注解时,启动IOC容器并不会创建对应的单例Bean对象,而是在第一次获取Bean对象时才会创建,同时,由于Spring创建的是单例Bean对象,所以,无论从IOC容器中获取多少次对象,每次获取到的Bean对象都是相同的。
结合时序图理解源码会事半功倍,你觉得呢?
本节,就以源码时序图的方式,直观的感受下@Lazy注解在Spring源码层面的执行流程。本节,主要从注册Bean、调用Bean工厂后置处理器和创建单例Bean三个方面分析源码时序图。
@Lazy注解涉及到的注册Bean的源码时序图如图9-1所示。
由图9-1可以看出,@Lazy注解在注册Bean的流程中涉及到LazyTest类、AnnotationConfigApplicationContext类、AnnotatedBeanDefinitionReader类、AnnotationConfigUtils类、BeanDefinitionReaderUtils类和DefaultListableBeanFactory类。具体的源码执行细节参见源码解析部分。
@Lazy注解涉及到的调用Bean工厂后置处理器的源码时序图如图9-2~9-4所示。
由图9-2~9-4可以看出,@Lazy注解涉及到的调用Bean工厂后置处理器的流程涉及到LazyTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、PostProcessorRegistrationDelegate类、ConfigurationClassPostProcessor类、ConfigurationClassParser类、ComponentScanAnnotationParser类、ClassPathBeanDefinitionScanner类、AnnotationConfigUtils类、BeanDefinitionReaderUtils类和DefaultListableBeanFactory类。具体的源码执行细节参见源码解析部分。
@Lazy注解涉及到的创建Bean的源码时序图如图9-5所示。
由图9-5可以看出,@Lazy注解涉及到的创建Bean的流程涉及到LazyTest类、AnnotationConfigApplicationContext类、AbstractApplicationContext类、DefaultListableBeanFactory类和AbstractBeanFactory类。具体的源码执行细节参见源码解析部分。
源码时序图整清楚了,那就整源码解析呗!
本节,主要分析@Lazy注解在Spring源码层面的执行流程,结合源码执行的时序图,会理解的更加深刻。本节,同样会从注册Bean、调用Bean工厂后置处理器和创建单例Bean三个方面分析源码的执行流程。
@Lazy注解在Spring源码层面注册Bean的执行流程,结合源码执行的时序图,会理解的更加深刻,本节的源码执行流程可以结合图9-1进行理解。
@Lazy注解涉及到的注册Bean的源码流程与第7章5.1小节@DependsOn注解涉及到的注册Bean的源码流程大体相同,只是在解析AnnotatedBeanDefinitionReader类的doRegisterBean()方法时,略有不同。本小节,就从AnnotatedBeanDefinitionReader类的doRegisterBean()方法开始解析。
(1)解析AnnotatedBeanDefinitionReader类的doRegisterBean()方法
源码详见:org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean(ClassbeanClass, String name, Class<? extends Annotation>[] qualifiers, Suppliersupplier, BeanDefinitionCustomizer[] customizers)。重点关注如下代码片段。
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) {
/(责任编辑:焦点)
兴胜创建(00896.HK)因行使购股权配发1090.9万股 每股发行价1.16港元
河北省货币信贷增速连续16个月高于全国平均水平 投放势头良好
宁夏税收优惠助力九个重点产业更快更好发展 去年减免各项税费30.6亿元
国庆长假上海线上线下消费总额达765.88亿元 会商旅文跨界融合“加速”
国家统计局:10月份货物进出口总额33357亿元 出口19408亿元