spring-组件注册(二)

447 阅读6分钟

前一章节主要介绍了基础的配置注解以及包扫描注解。本章将继续介绍

  • 作用域
  • 懒加载
  • 条件注入

 我是分隔符 

一、作用域

bean的作用域有以下几类:

  1. singleton:单例作用域,从spring容器创建到最后的销毁,每一个bean只有一个对应的bean对象在容器当中。
  2. prototype:原型作用域,每一次使用时,都重新创建一个新的bean对象进行使用。
  3. request:在一次request请求当中,每一个bean只有一个对应的bean对象被创建。专用于web作用域上下文。
  4. session:在同一个session中,每一个bean只有一个对应的bean对象被创建。专用于web作用域上下文。
  5. globalSession:类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。专用于web作用域上下文。

本次主要以前两种作用域举例

基础模型:

public class Book {

    private int id;
    private String name;

    public Book(int id) {
        this.id = id;
    }

    public Book(int id, String name) {
        System.out.println("book bean created。。");
        this.id = id;
        this.name = name;
    }

    //此处忽略n行 setter、getter
}

举例用到的测试类

public class TestBean {

    public static void main(String[] args) {

        //通过注解的方式获取对应的bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Book book1 = applicationContext.getBean(Book.class);
        Book book2 = applicationContext.getBean(Book.class);
        System.out.println(book1 == book2);
    }

}

1. singleton作用域

单例是bean的默认作用域,在没有特别声明的情况下,就是以单例作用域进行的。

//配置类==配置文件
@Configuration  //告诉spring这是一个配置类
public class SpringConfig {
    //单例为默认作用域,Scope注解可加可不加
    //@Scope("singleton")  
    @Bean
    public Book book() {
        return new Book(1, "123");
    }

}

运行测试类,得出结果:

book bean created。。
true

可以发现当容器启动后,不管我们获取几次book对象,只创建一次book对象,并且需要再次获取时,使用之前已创建的对象。

我们再做个实验:

public class TestBean {

    public static void main(String[] args) {

        //通过注解的方式获取对应的bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    }
}

单纯的只是去创建对应的配置类对象,再次运行测试类:

book bean created。。

在配置类中定义的对象也被创建了,也就是说在单例模式下,bean对象的创建和实例化默认是在容器创建完之后就进行的。

2. prototype作用域

在bean上加上注解@Scope并且作用域prototype:

//配置类==配置文件
@Configuration  //告诉spring这是一个配置类
public class SpringConfig {
    @scope("prototype")  
    @Bean
    public Book book() {
        return new Book(1, "123");
    }

}

再次运行TestBean测试类,得出结果:

book bean created。。
book bean created。。
false

可以发现在获取两次book对象时,都重新创建了一个新的book对象。

让我们再去进行一次之前的小实验,单纯的只是实例化一个配置类:

public class TestBean {

    public static void main(String[] args) {

        //通过注解的方式获取对应的bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    }
}

不出意外的发现,它的结果什么都没有打印,命令行中只有如下内容:

Process finished with exit code 0

也就是说在prototype作用域下,容器启动后,该对象并不会被创建,而是在使用时才会进行创建。

二、懒加载

懒加载,顾名思义就是很懒的加载方式-能多迟就多迟,使用的时候才加载

以前面讲的Book bean对象为例,作用域使用默认的单例模式(不过懒加载@Lazy也只适用于单例作用域):

//配置类==配置文件
@Configuration  //告诉spring这是一个配置类
public class SpringConfig {

    /**
     * lazy 懒加载:只适用于单例,在容器创建的时候不创建bean,在第一次使用的时候在创建
     * @return
     */
    @Lazy
    @Bean
    public Book book() {
        return new Book(1, "123");
    }

}

再次运行测试类:

public class TestBean {

    public static void main(String[] args) {

        //通过注解的方式获取对应的bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        Book book1 = applicationContext.getBean(Book.class);
        Book book2 = applicationContext.getBean(Book.class);
        System.out.println(book1 == book2);
    }

}

结果为:

book bean created。。
true

和之前的单例一样,只会创建一个book对象

那么此时这个book对象的实例化是什么时候进行的呢?

我们将上述结果结合下述实验进行研究:

public class TestBean {

    public static void main(String[] args) {

        //通过注解的方式获取对应的bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    }

}

此时我们只是单纯的实例化对应的配置类,再次启动测试类,可以发现输出结果和之前的单例作用域的实验不同,book bean created不见了,只输出了:

Process finished with exit code 0

也就是说,在单例作用域中如果使用了懒加载,那么bean的实例化是在第一次使用对应bean时进行的,之后遵循单例规则,再次使用时,会使用之前已经实例化好后的对象。

三、条件注入

针对spring的bean对象注入,还可以根据自定义的条件进行判断是否进行注入,对应的注解为Conditional,其定义如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

即可以通过实现Condition接口来自定义bean的过滤条件。

举栗正式开始:

针对Person类:

public class Person {

    private int age;

    private String name;

    public Person() {
    }

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    //此处忽略n行 setter、getter

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

我们需要根据代码的运行环境为Windows系统还是Linux系统来实例化不同的person bean对象。则我们可以定义两个条件:

/**
 * spring Windows环境条件过滤器,返回true时才注册对应的bean
 */
public class WindowsCondition implements Condition {
    /**
     *
     * @param conditionContext 条件能使用的上下文
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //能获取到IOC使用的bean工厂
        //ConfigurableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //能获取到类加载器
        //ClassLoader ca = conditionContext.getClassLoader();
        //获取到bean定义的注册类
        //BeanDefinitionRegistry definitionRegistry = conditionContext.getRegistry();
        //能获取到当前环境
        Environment environment = conditionContext.getEnvironment();

        String name = environment.getProperty("os.name");
        if (name.contains("Window")) {
            //当系统名称包含Window时才说明匹配
            return true;
        }
        return false;
    }
}
/**
 * spring linux环境条件过滤器,返回true时才注册对应的bean
 */
public class LinuxCondition implements Condition {
    /**
     *
     * @param conditionContext 条件能使用的上下文
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //能获取到IOC使用的bean工厂
        //ConfigurableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //能获取到类加载器
        //ClassLoader ca = conditionContext.getClassLoader();
        //获取到bean定义的注册类
        //BeanDefinitionRegistry definitionRegistry = conditionContext.getRegistry();
        //能获取到当前环境
        Environment environment = conditionContext.getEnvironment();

        String name = environment.getProperty("os.name");
        if (name.contains("Linux")) {
            //当系统名称包含Linux时才说明匹配
            return true;
        }
        return false;
    }
}

在配置类中配置对应bean时,加入对应注入条件:

//配置类==配置文件
@Configuration  //告诉spring这是一个配置类
public class SpringConfig {
    @Conditional(WindowsCondition.class)
    @Bean("bill")
    public Person person02() {
        return new Person(68, "bill");
    }

    @Conditional(LinuxCondition.class)
    @Bean("linus")
    public Person person03() {
        return new Person(48, "linus");
    }
}

Windows环境运行对应测试类:

public class TestBean {

    public static void main(String[] args) {

        //通过注解的方式获取对应的bean
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanNames) {
            System.out.println(name);
        }
    }

}

运行结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
bill

可以发现在Windows环境下,只有实例化了内容为bill的person对象。

另外@Conditional注解也可以直接使用在配置类上,表明配置类中的所有bean只在某个条件下注入。