Spring注解学习之:@Scope,@Lazy,@Conditional

Spring注解详解:@Scope,@Lazy与@Conditional的用法
本文介绍了Spring框架中三个重要的注解:@Scope用于改变bean的作用域,@Lazy实现延迟加载,@Conditional根据条件创建bean。通过测试案例详细解析了每个注解的效果,例如@Scope的singleton和prototype区别,@Lazy如何实现单例延迟加载,以及@Conditional如何根据操作系统创建不同的bean。

1、@Scope

首先,有一点需要说明,在SpringIOC容器中,所有注册的bean(controller,service,dao,....),默认情况下都是单实例的,及singleton;那有哪些作用域,分别什么意思?如下表:

作用域解释说明
singleton单实例,在创建IOC时,bean就会被创建,而且只会创建一次,以后都会直接使用该创建对象,相当于map.get();注意:在IOC容器创建的时候,就已经创建好了bean;
prototype多实例,在创建IOC时,bean不会被创建,只有当bean的创建方法被调用时,才会创建,而且每次调用,每次创建;
request同一个请求时,创建一次;
session同一个session,创建一次;

spring提供可以通过@Scope来对bean的作用域进行修改,相当于配置文件中<bean>标签的scope属性,@Scope的取值有singleton,prototype,request以及session,含义如上表;如果不指定,默认为singleton;

做个测试看一下:

测试一,不写@Scope,或者写了@Scope,不指定内容

1、Myconfig

@Configuration
public class MyConfig {

    @Bean
    @Scope
    public Student student(){
        System.out.println("创建student开始");
        return new Student(14,"idea");
    }
}

-------------------------------------------
2、测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAnnocationApplicationTests {


    @Test
    public void contextLoads() {
        System.out.println("开始创建IOC容器...");
        ApplicationContext alicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println("IOC容器创建完毕...");
        Object student1 = alicationContext.getBean("student");
        Object student2 = alicationContext.getBean("student");
        //如果前后创建的不是同一个对象,那么==一定为false
        System.out.println(student1 == student2);
    }
}

结果:

先不管我说的那个问题,我需要看一下。在打印“开始创建IOC容器...”和“IOC容器创建完毕...”时,student就已经创建了,而且我调用了2次,值打印了一次,而且==为true;验证了单例的结论;

测试二:@Scope指定为prototype

直接看结果:

“创建student开始”在“IOC容器创建完毕...”才打印,而且==为false,验证了prototype是在IOC创建之后,调用时才创建的结论,而且每次调用,每次创建!

2、@Lazy:延迟加载;该注解主要是和@Scope("singleton")结合使用,目的是为了让bean在IOC容器创建之后再创建,当然也只是创建一个;

测试:

1、MyConfig 配置类
@Configuration
public class MyConfig {

    @Bean
    @Lazy
    public Student student(){
        System.out.println("创建student开始");
        return new Student(14,"idea");
    }
}
--------------------------------------------
2、测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAnnocationApplicationTests {


    @Test
    public void contextLoads() {
        System.out.println("开始创建IOC容器...");
        ApplicationContext alicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println("IOC容器创建完毕...");
        Object student1 = alicationContext.getBean("student");
        Object student2 = alicationContext.getBean("student");
        System.out.println(student1 == student2);
    }
}

结果:

可以看到,“创建student开始”在“IOC容器创建完毕...”打印完之后才打印,说明延迟加载了,而且只打印了一遍,且==为true,符合单例特性;

3、@Conditional,这个就NB了,springboot底层大量使用了该注解,意思就是根据不同的条件,进行特定创建;首先看个需求,根据当前JVM的操作系统不同,创建不同的bean;

首先@Conditional的值,是实现了condition接口的一个实现类的数组集合,根据上面的条件,我们需要建两个类,一个WindowsCondition和LinuxCondition,分别实现condition接口;condition接口中有一个match方法,用于实现具体业务,详情见下文:

  

1、MyConfig 类
@Configuration
public class MyConfig {

    @Bean
    @Conditional(WindowsCondition.class)
    public Student windows(){
        return new Student(14,"windows");
    }

    @Bean
    @Conditional(LinuxCondition.class)
    public Student linux(){
        return new Student(14,"linux");
    }
}
---------------------------------------------------------
2、WindowsCondition 类
public class WindowsCondition implements Condition {


    /**
     *  conditionContext : 能使用的上下文,也就是环境
     *  annotatedTypeMetadata:标注了condition的注释信息
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

        /**
         * 如果环境是windows,返回true
         */
        //能获取到IOC使用的bean工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //能获取到类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        //能获取到注册信息
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        //能获取到运行环境
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Windows")){
            return true;
        }
        return false;
    }
}
---------------------------------------------------
3、LinuxCondition 类
public class LinuxCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Linux")){
            return true;
        }
        return false;
    }
}
-------------------------------------------------
4、测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAnnocationApplicationTests {


    @Test
    public void contextLoads() {
        ApplicationContext alicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        Environment environment = alicationContext.getEnvironment();
        System.out.println(environment.getProperty("os.name"));
        Map<String, Student> beansOfType = alicationContext.getBeansOfType(Student.class);
        for (Map.Entry<String, Student> stringStudentEntry : beansOfType.entrySet()) {
            System.out.println(stringStudentEntry.getKey());
            System.out.println(stringStudentEntry.getValue());
        }
    }
}

结果:

当前运行环境是windows,然后创建了windows,搞定。

<think>好的,我现在需要回答用户关于Spring框架中@Lazy注解的使用场景及示例的问题。首先,我需要回忆一下@Lazy注解的基本作用。根据用户提供的引用内容,@Lazy是用于延迟初始化bean的,也就是说,只有在真正需要这个bean的时候才会创建和初始化,而不是在应用启动时就初始化。这有助于减少启动时的资源消耗,特别是对于那些可能不经常使用的bean来说非常有用。 接下来,我需要确定使用@Lazy的主要场景。第一个想到的是解决循环依赖的问题,比如引用[2]提到的例子,当两个服务类互相注入时,可能会出现循环依赖,这时候使用@Lazy可以延迟其中一个bean的初始化,从而打破循环。另一个场景是优化启动性能,对于那些初始化耗时较长的bean,延迟加载可以加快应用的启动速度。此外,有些资源可能只在特定条件下才需要,比如特定配置开启后才使用,这时候也可以用@Lazy来避免不必要的初始化。 然后,我需要考虑如何给出示例。用户提供的引用中有一个例子,其中taskServiceImpl被注入到groupServiceImpl中,导致循环依赖。这时候在其中一个类上使用@Lazy,比如在GroupServiceImpl的构造函数参数上添加@Lazy注解,可以延迟TaskServiceImpl的初始化,从而解决循环依赖的问题。另外,还可以举一个配置类中使用@Lazy的例子,比如一个耗时较长的DataSource配置,使用@Lazy可以确保只有在第一次访问数据库时才初始化这个数据源。 在编写示例代码时,需要注意正确的注解位置,比如在构造函数的参数前,或者在配置类的方法上。同时,要提醒用户注意,@Lazy的使用需要结合作用域,比如在原型作用域下可能需要不同的处理方式。另外,需要指出可能的误用情况,比如在单例bean之间使用@Lazy可能导致意外行为,或者在单元测试中需要确保bean已经被初始化。 最后,生成的相关问题要围绕用户可能进一步想了解的内容,比如与@Lazy相关的其他注解、如何调试延迟加载的问题、在微服务中的应用等。这样可以帮助用户更全面地理解@Lazy的应用场景和注意事项。</think>### @Lazy 注解Spring 中的使用场景及示例 #### 1. **延迟加载的核心作用** `@Lazy` 注解通过延迟 Bean 的初始化,**仅在首次实际使用时才创建实例**,而非应用启动时立即加载。此特性特别适用于: - **资源密集型 Bean**(如数据库连接池) - **循环依赖场景** - **条件化启用的组件**(如特定环境配置) #### 2. **典型使用场景** **场景 1:解决循环依赖** 当两个 Bean 互相注入时(如 `ServiceA` 依赖 `ServiceB`,`ServiceB` 又依赖 `ServiceA`),可通过 `@Lazy` 切断初始化死锁。 ```java @Service public class GroupServiceImpl { private final TaskServiceImpl taskService; @Autowired public GroupServiceImpl(@Lazy TaskServiceImpl taskService) { // 延迟注入 this.taskService = taskService; } } ``` 此方式使 `TaskServiceImpl` 的初始化推迟到实际调用时,打破循环链[^2]。 **场景 2:优化启动性能** 对于初始化耗时的 Bean(如第三方服务客户端),使用 `@Lazy` 可加速应用启动: ```java @Configuration public class AppConfig { @Bean @Lazy // 首次访问数据库时初始化 public DataSource heavyDataSource() { return new HikariDataSource(...); // 假设配置复杂、耗时 } } ``` **场景 3:条件化资源加载** 结合 `@Profile` 或 `@Conditional` 注解,实现按需加载: ```java @Bean @Lazy @Profile("cloud") public CloudStorageService cloudStorage() { return new AWSStorageService(); // 仅当 cloud 环境激活时加载 } ``` #### 3. **注意事项** - **作用域影响**:在原型作用域(`@Scope("prototype")`)中,`@Lazy` 可能导致每次请求生成新实例。 - **单例误用**:单例 Bean 之间过度使用 `@Lazy` 可能掩盖设计问题(如应重构的紧耦合)。 - **调试陷阱**:延迟加载的 Bean 在启动时不会暴露初始化错误,需通过实际调用触发验证。 #### 4. **扩展对比:与其他注解的关系** | 注解 | 目标 | 与 `@Lazy` 的协作 | |---------------|------------------------------|---------------------------| | `@Autowired` | 依赖注入 | 可组合使用(如示例 1) | | `@Conditional`| 条件化 Bean 创建 | 共同实现按需加载 | | `@PostConstruct` | 初始化后操作 | 延迟到首次使用时才执行 | ```java // 组合使用示例 @Service public class ReportService { @Lazy @Autowired @Qualifier("advancedGenerator") private ReportGenerator generator; // 按需加载指定实现类 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值