『重学Spring 』(一)—— IoC 容器

1. 什么是 IoC

控制反转 IoC(Inversion of Control),是一种设计思想,DI (依赖注入)是实现 IoC 的一种方法,也有人认为 DI 只是 IoC 的另一种说法。没有 IoC 的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。所谓控制反转就是:获得依赖对象的方式反转了。

控制反转是一种通过描述(XML 或注解)并通过第三方去生产或获取特定对象的方式。在 Spring 中实现控制反转的是 IoC 容器,其实现方法是依赖注入(Dependency Injection, DI)。

2. Bean

2.1 基本配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!-- 配置 bean 到 Spring 容器中 -->
    <bean id="cat" class="com.ice.spring.entity.Cat" name="cat">
        <property name="age" value="13"/>
        <property name="name" value="Tom"/>
    </bean>
    <alias name="cat" alias="cat2"/>
</beans>
  • id 属性是 bean 的唯一表示,遵从小驼峰命名法
  • name 属性表示该 bean 的别名,可以有多个别名,分隔符可以是空格逗号分号允许混合同时使用

2.2 实例化

2.2.1 构造函数实例化

当通过构造方法创建一个 Bean 时,所有普通类都可以被 Spring 使用并兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 Bean 类就足够了。但是,根据用于该特定 Bean 的 IoC 的类型,可能需要一个默认(空)构造函数。

Spring IoC 容器几乎可以管理您要管理的任何类。它不仅限于管理真正的 Java Bean。大多数 Spring 用户更喜欢实际的 Java Bean,它们仅具有默认的(无参数)构造函数,并具有根据容器中的属性建模的适当的 setter 和 getter。

<bean id="cat" class="com.ice.spring.entity.Cat" name="cat" />
<bean id="cat1" class="com.ice.spring.entity.Cat" name="cat">
    <constructor-arg name="age" value="13"/>
    <constructor-arg name="name" value="Tom"/>
</bean>

默认的 bean 就是构造函数来实例化的。

2.2.2 静态工厂实例化

在定义使用静态工厂方法创建的 Bean 时,使用 class 属性指定包含 static 工厂方法的类,并使用命名 factory-method 为属性的属性来指定工厂方法本身的名称。您应该能够调用此方法(带有可选参数,如稍后所述)并返回一个活动对象,该对象随后将被视为已通过构造函数创建。

以下 Bean 定义指定通过调用工厂方法来创建 Bean。该定义不指定返回对象的类型(类),而仅指定包含工厂方法的类。在此示例中,该 createInstance() 方法必须是静态方法。

<bean id="dog" class="com.ice.spring.entity.DogConstructor" factory-method="createInstance"/>
<bean id="dog1" class="com.ice.spring.entity.DogConstructor" factory-method="createInstance">
    <property name="name" value="小黑"/>
    <property name="age" value="2"/>
</bean>

该工厂类如下所示:

public class DogConstructor {
    public static Dog createInstance(String name, int age) {
        return new Dog(name, age);
    }

    public static Dog createInstance(){
        return createInstance("旺财",3);
    }
}

一个工厂类也可以包含一个以上的工厂方法。

2.2.3 实例工厂实例化

类似于通过静态工厂方法进行实例化,使用实例工厂方法进行实例化会从容器中调用现有 Bean 的非静态方法来创建新 Bean。要使用此机制,请将 class 属性留空,并在 factory-bean 属性中指定当前(或父容器或祖先容器)中包含要创建该对象的实例方法的 Bean 的名称。使用 factory-method 属性设置工厂方法本身的名称。

<bean id="catConstructor" class="com.ice.spring.entity.catConstructor" name="catConstructor"/>
<bean id="cat1" factory-bean="catConstructor" factory-method="createInstance">
    <property name="age" value="7"/>
    <property name="name" value="喵喵"/>
</bean>

该工厂类如下所示:

public class CatConstructor {

    public Cat createInstance(String name, int age) {
        return new Cat(name, age);
    }

    public Cat createInstance() {
        return createInstance("HelloKitty", 3);
    }
}

一个工厂类也可以包含一个以上的工厂方法。

2.2.4 延迟加载

有时我们不希望容器启动时就将 Bean 注册到容器中,或是避免延迟加载,我们可以在配置 Bean 时,设置 lazy-init="true"

<bean id="fish" class="com.ice.spring.entity.Fish" name="fish1" lazy-init="true">
    <property name="name" value="金鱼"/>
</bean>

2.2.5 Bean 的作用域

类别说明
singleton在 Spring IoC 容器中仅存在一个 Bean 实例,Bean 以单例的方式存在,默认值
prototype每次从容器中获取 Bean 时,都会返回一个新的实例
request每次 HTTP 请求都会创建一个新的 Bean,该作用域仅适用于 WebApplicationContext 环境
session同一个 HTTP session 共享一个 Bean,不同 session 使用不同 Bean,适用于 WebApplicationContext 环境

可以通过 scope 属性设置作用域,例如:

<bean id="fish" class="com.ice.spring.entity.Fish" name="fish" scope="prototype">
    <property name="name" value="金鱼"/>
</bean>
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");

Fish fish = applicationContext.getBean("fish", Fish.class);
Fish fish1 = applicationContext.getBean("fish", Fish.class);

System.out.println(fish == fish1); // false

2.3 依赖注入

这里就只关心属性也是 Bean 的情况了

2.3.1 构造函数注入

  • 构造函数参数名称注入

    <bean id="person" class="com.ice.spring.entity.Person" name="person">
        <constructor-arg name="age" value=" 18"/>
        <constructor-arg name="name" value="ice"/>
        <constructor-arg name="dog" ref="dog"/>
    </bean>
    

    如果引用已有的 Bean,用 ref 传递

  • 构造函数参数类型匹配注入

    <bean id="person2" class="com.ice.spring.entity.Person" name="person2">
        <constructor-arg type="int" value=" 18"/>
        <constructor-arg type="java.lang.String" value="ice"/>
        <constructor-arg type="com.ice.spring.entity.Dog" ref="dog"/>
    </bean>
    
  • 构造函数参数索引注入

    <bean id="person3" class="com.ice.spring.entity.Person" name="person3">
        <constructor-arg index="0" value="Job"/>
        <constructor-arg index="1" value="3"/>
        <constructor-arg index="2" ref="dog"/>
    </bean>	
    
    • 除了解决多个简单值的歧义性外,指定索引还可以解决构造函数具有两个相同类型参数的歧义性

    • 索引从 0 开始

2.3.2 setter 注入

通过调用无参数构造函数或无参数 static 工厂方法实例化 Bean 后,容器在 Bean 上调用 setter 方法来实现基于 setter 的依赖注入 。

2.3.2.1 简单类型

<property/> 元素的 value 属性将属性或构造函数参数指定为人类可读的字符串表示形式。Spring 的转换服务用于将这些值从字符串转换为属性或参数的实际类型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

这是一个常见的数据源的配置

2.3.2.2 对其他 Bean 的引用
<bean id="person4" class="com.ice.spring.entity.Person" name="person4">
    <property name="name" value="Jerry"/>
    <property name="age" value="16"/>
    <property name="dog" ref="dog"/>
</bean>
2.3.2.3 内部 Bean

<property/><constructor-arg/> 元素中的 <bean/> 元素定义了一个内部 Bean,如下面的示例所示:

<bean id="person5" class="com.ice.spring.entity.Person" name="person5">
    <property name="name" value="Jerry"/>
    <property name="age" value="16"/>
    <property name="dog">
        <bean class="com.ice.spring.entity.Dog">
            <property name="age" value="13"/>
            <property name="name" value="热狗"/>
        </bean>
    </property>
</bean>
  • 内部 Bean 定义不需要已定义的 id 或名称。如果指定了该值,则容器不会使用该值作为标识符

  • 容器在创建时也会忽略范围标记,因为内部 Bean 总是匿名的,并且总是与外部 Bean 一起创建

  • 不能独立访问内部 Bean,也不能将它们注入到协作 Bean中

  • 内部 Bean 通常只是共享它们所包含的 Bean 的范围

2.3.2.4 复杂类型注入
  • Collections

    <list/><set/><map/><props/>元素分别设置了 Java 集合类型 ListSetMapProperties 的属性和参数。下面的官方示例演示如何使用它们:

    <bean id="moreComplexObject" class="example.ComplexObject">
        <!-- results in a setAdminEmails(java.util.Properties) call -->
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.org</prop>
                <prop key="support">support@example.org</prop>
                <prop key="development">development@example.org</prop>
            </props>
        </property>
        <!-- results in a setSomeList(java.util.List) call -->
        <property name="someList">
            <list>
                <value>a list element followed by a reference</value>
                <ref bean="myDataSource" />
            </list>
        </property>
        <!-- results in a setSomeMap(java.util.Map) call -->
        <property name="someMap">
            <map>
                <entry key="an entry" value="just some string"/>
                <entry key ="a ref" value-ref="myDataSource"/>
            </map>
        </property>
        <!-- results in a setSomeSet(java.util.Set) call -->
        <property name="someSet">
            <set>
                <value>just some string</value>
                <ref bean="myDataSource" />
            </set>
        </property>
        <property name="someNull">
            <null />
        </property>
    </bean>
    

    map 的键和值、set 的值可以是以下任何元素:bean | ref | idref | list | set | map | props | value | null

  • Collections 合并

    Spring 容器还支持合并集合。一个应用开发者可以定义一个父元素 <list/><map/><set/><props/>,并让子元素 <list/><map/><set/><props/> 继承和覆盖父元素集合中的值。也就是说,子集合的值是合并父集合和子集合的元素的结果,子集合元素覆盖父集合中指定的值。

    下面的例子演示了集合的合并:

    <beans>
        <bean id="parent" abstract="true" class="example.ComplexObject">
            <property name="adminEmails">
                <props>
                    <prop key="administrator">administrator@example.com</prop>
                    <prop key="support">support@example.com</prop>
                </props>
            </property>
        </bean>
        <bean id="child" parent="parent">
            <property name="adminEmails">
                <!-- the merge is specified on the child collection definition -->
                <props merge="true">
                    <prop key="sales">sales@example.com</prop>
                    <prop key="support">support@example.co.uk</prop>
                </props>
            </property>
        </bean>
    <beans>
    
  • null 和字符串控制

    Spring 将属性之类的空参数视为空字符串。以下基于 XML 的配置元数据片段将 email 属性设置为空字符串值(")。

    <bean class="ExampleBean">
      <property name="email" value=""/>
    </bean>
    

    <null/> 元素的作用是:处理 null 值。

    <bean class="ExampleBean">
      <property name="email">
          <null/>
      </property>
    </bean>
    
2.3.2.5 简写方式
  • p 命名空间注入

    下面的示例显示了两个 XML 片段(第一个使用标准 XML 格式,第二个使用 p-namespace),它们解析为相同的结果:

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean name="classic" class="com.example.ExampleBean">
            <property name="email" value="someone@somewhere.com"/>
        </bean>
    
        <bean name="p-namespace" class="com.example.ExampleBean"
            p:email="someone@somewhere.com"/>
    </beans>	
    

    该示例显示了 bean 定义中的 p-namespace 中的一个名为 email 的属性。这告诉 Spring 包含一个属性声明。如前所述,p-namespace 没有模式定义,因此可以将属性的名称设置为属性名。

    下一个示例包含了另外两个 bean 定义,它们都引用了另一个 bean:

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean name="john-classic" class="com.example.Person">
            <property name="name" value="John Doe"/>
            <property name="spouse" ref="jane"/>
        </bean>
    
        <bean name="john-modern"
            class="com.example.Person"
            p:name="John Doe"
            p:spouse-ref="jane"/>
    
        <bean name="jane" class="com.example.Person">
            <property name="name" value="Jane Doe"/>
        </bean>
    </beans>
    
  • c 命名空间注入

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:c="http://www.springframework.org/schema/c"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="beanTwo" class="x.y.ThingTwo"/>
        <bean id="beanThree" class="x.y.ThingThree"/>
    
        <!-- traditional declaration with optional argument names -->
        <bean id="beanOne" class="x.y.ThingOne">
            <constructor-arg name="thingTwo" ref="beanTwo"/>
            <constructor-arg name="thingThree" ref="beanThree"/>
            <constructor-arg name="email" value="something@somewhere.com"/>
        </bean>
    
        <!-- c-namespace declaration with argument names -->
        <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
            c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
    
    </beans>
    

2.3.3 自动装配

  • 自动装配是 Spring 满足 Bean 依赖的一种方式
  • Spring 会在上下文中自动寻找,并自动给 Bean 装配属性
2.3.3.1 byType 自动装配

该模式表示根据 property 的数据类型(Type)自动装配,Spring 会自动寻找与属性类型相同的 Bean,若一个 Bean 的数据类型,兼容另一个 Bean 中 property 的数据类型,则自动装配。

注意:使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常。

<bean id="fish" class="com.ice.spring.entity.Fish" name="fish">
    <property name="name" value="金鱼"/>
</bean>
<bean id="fishWatcher" class="com.ice.spring.entity.FishWatcher" name="fishWatcher" autowire="byType"/>
2.3.3.2 byName 自动装配

该模式表示根据 property 的 name 自动装配,如果一个 Bean 的 name,和另一个 Bean 中的 property 的 name 相同,则自动装配这个 Bean 到 property 中。

当一个 Bean 节点带有 autowire="byName" 的属性时,将查找其类中所有的 setter 方法名,获得将 set 去掉并且首字母小写的字符串,然后去 Spring 容器中寻找是否有此字符串名称 id 的对象。如果有,就取出注入;如果没有,就报空指针异常。

<bean id="person6" class="com.ice.spring.entity.Person" name="person6" autowire="byName"/>

3. 使用注解开发

3.1 开启注解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解 -->
    <context:annotation-config/>

    <!-- 指定要扫描的包,这个包下的注解就会生效 -->
    <context:component-scan base-package="com.ice.spring.entity"/>

</beans>

3.2 组件扫描

@Component 用于声明一个类是一个组件,启动 Spring 容器的时候,会扫描指定包下的类,如果包含这个注解,则会在容器中为这个类注册一个 Bean。

import org.springframework.stereotype.Component;

@Component(value = "Tom")
public class Cat {
    private String name;
    private int age;
    // ...
}

@Component 注解有一个可选的参数,无参情况下,组件的在容器中的 name 为 cat,即被注解类的类名的小驼峰形式,如果传递了这个参数,则给组件的 name 就是手动指定的名称。

@Component 有三个衍生注解,为了更好的进行分层,Spring 可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web 层
  • @Service:service 层
  • @Repository:dao 层

写上这些注解,就相当于将这个类交给 Spring 管理装配了!

3.3 属性注入

对于基本类型值的注入,我们可以使用 @Value 注解进行赋值,@Value 注解可以传下面几种值:

  • 基本数值
  • #{}:SpEL
  • ${}:取出配置文件中的值(在运行环境变量里面的值)
@Component(value = "Tom")
public class Cat {
    @Value("${cat.name}")
    private String name;
    @Value("#{20-2}")
    private int age;
    // ...
}

3.4 自动装配

3.4.1 @Autowired

这是最常用的自动装配的注解,可以用在属性、setter 方法及构造函数参数上,其中,作用在属性和 setter 方法上时,必须要有无参构造函数

  • 作用在属性上(不推荐)

    @Component
    public class Person {
        private String name;
        private int age;
        
        @Autowired
        private Dog dog;
        // ...
    }
    
  • 作用在 setter 方法上(解决循环依赖的一种方式)

    @Component
    public class Person {
        private String name;
        private int age;
        
        private Dog dog;
        
        @Autowired
        public void setDog(Dog dog) {
            this.dog = dog;
        }
        // ...
    }
    
  • 作用在构造函数上(推荐)

    这是因为这种方式可以声明属性是 final 的,前面的方式本质上是先声明为 null,再赋值的。

    @Autowired
    public Person(@Value("ice") String name, @Value("14") int age,  Dog dog) {
        this.name = name;
        this.age = age;
        this.dog = dog;
    }
    
    
    public Person(@Value("ice") String name, @Value("14") int age, @Autowired Dog dog) {
        this.name = name;
        this.age = age;
        this.dog = dog;
    }
    

    上面两种方式都可以,只是,如果注解的是参数,那么创建 Bean 默认走无参构造函数,作用在构造函数上,创建 Bean 时自动走有参构造。

@Autowired 注解是按照 byType 方式进行自动装配的,同时,它可以设置是否允许空值装配,即 @Autowired(required = false)

3.4.2 @Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier不能单独使用。
@Component
public class Person {
    private String name;
    private int age;
    
    private Dog dog;
    
    @Autowired
    @Qualifier("dog")
    public void setDog(Dog dog) {
        this.dog = dog;
    }
    // ...
}

有种特殊情况可以直接用,就是注解在构造函数参数上,但是此时不能有无参构造函数,否则创建 Bean 用的默认是无参构造函数:

public Person(@Value("ice") String name, @Value("14") int age, @Qualifier("dog") Dog dog) {
    this.name = name;
    this.age = age;
    this.dog = dog;
}

3.4.3 @Primary

除了每次用 @Qualifier 注解,指定类型相同时匹配的 bean 的名字,我们也可以使用 @Primary 注解指定默认 bean,当有多个相同类型的 bean 时,首先用它

注意,不能和 @Qualifiler 同时使用!

@Component
public class Dog extends Animal{
    
}

@Component
public class Cat extends Animal{
    
}

@Component
public class Person{
    @Autowired
    private Animal pet;
}

此时,优先自动装配 Dog

3.4.4 @Resource

  • @Resource 如有指定的 name 属性,先按该属性进行 byName 方式查找装配;
  • 其次再进行默认的 byName 方式进行装配;
  • 如果以上都不成功,则按 byType 的方式自动装配;
  • 都不成功,则报异常。
@Component
public class Person {
    private String name;
    private int age;

    @Resource(name="dog")
    private Dog dog;
}

3.4.5 @Inject

需要导入:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

@Autowired 功能一样,但是 @Inject 没有参数:

@Component
public class Person {
    private String name;
    private int age;
    
    private Dog dog;
    
    @Inject
    public void setDog(Dog dog) {
        this.dog = dog;
    }
    // ...
}

该注解无法作用于函数参数上!

4. 注解驱动开发

4.1 JavaConfig

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring 4 的版本, JavaConfig 已正式成为 Spring 4 的核心功能 。

配置类最基础的两个注解为:@Configuration@ComponentScan

4.1.1 @Configuration

@Configuration 注解用来声明这个类是一个 Java 配置类,其内部通过定义函数的方式来进行全局配置。

@Configuration
public class Config {
	// 配置内容
}

这个注解有一个可选参数:proxyBeanMethods,默认为 true

proxyBeanMethods=true 时,称作 Full 模式;当 proxyBeanMethods=false 时,称作 Lite 模式。

Full 模式下也就是说该配置类会被代理(CGLib),在同一个配置类中调用其它被 @Bean 注解标注的方法获取对象时会直接从 IoC 容器之中获取。如果设置为 Lite 模式,则该类每次都会调用方法创建新的 Bean。

经过试验,对于普通 Spring 工程不起作用,但是在 SpringBoot 工程中可以验证下面代码输出为 false

@Configuration(proxyBeanMethods = false)
public class Config {
    @Bean
    public Dog dog() {
        return new Dog("小黑", 3);
    }

    @Bean
    public Person person() {
        return new Person("ice", dog());
    }
}
void test(){
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
    Dog dog = applicationContext.getBean("dog", Dog.class);
    Person person = applicationContext.getBean("person", Person.class);
    System.out.println(dog == person.getDog()); // false
}

4.1.2 @ComponentScan

该注解用于指定扫指定包下的所以文件,如果有相应注解就进行处理:

@Configuration
@ComponentScan("com.ice.spring.entity")
public class Config {
	// 配置内容
}

上面这种是最简单的用法,除此之外,还有过滤、排除等功能,下面是 @ComponentScan 的源码(省略了不常用的):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	
    // 基本扫描包,全限定名
	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

	// 基本扫描包,传入类所在的 package 被识别为基本扫描包
	Class<?>[] basePackageClasses() default {};

    // 指示是否应该启用 @Component @Repository、@Service或@Controller注释的类的自动检测
	boolean useDefaultFilters() default true;

	// 过滤规则,在 basePackages 中符合过滤规则的才被扫描
    // 注意,如果 useDefaultFilters 为 true,则此处除了你指定的,DefaultFilters 的类型也会被扫描
    // 如果 DefaultFilters 的有你不想要过滤的类型,必须设置 useDefaultFilters=false
	Filter[] includeFilters() default {};

	// 排除规则,是一个 Filter 数组,Filter 是该注解内部定义的一个注解
	Filter[] excludeFilters() default {};

    /******************************************************************************************/

	// 内部注解,定义过滤规则
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		// 设置按照什么过滤,默认是注解
		FilterType type() default FilterType.ANNOTATION;

		// 设置过滤的类(按照注解过滤就是 注解类.class,按照其他方式过滤,就是其他相关类.class)
		@AliasFor("classes")
		Class<?>[] value() default {};

		@AliasFor("value")
		Class<?>[] classes() default {};

		// 按照 AspectJ type pattern expression 或 正则表达式 过滤的 模式字符串
		String[] pattern() default {};
	}
}

下面再介绍下 FilterType

  • FilterType.ANNOTATION:按照注解类型

  • FilterType.ASSIGNABLE_TYPE:按照指定类类型

  • FilterType.ASPECTJ:按照 AspectJ 表达式

  • FilterType.REGEX:按照正则表达式

  • FilterType.CUSTOM:按照自定义规则

我们用如下测试代码来测试这些过滤、排除的功能:

void testComponentScan() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}
  • 不指定规则

    @Configuration()
    @ComponentScan(basePackages = "com.ice.spring")
    public class Config {
    
    }
    

    输出结果:

    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
    config
    cat
    dog
    person
    
  • useDefaultFilter=false

    @Configuration()
    @ComponentScan(basePackages = "com.ice.spring",useDefaultFilters = false)
    public class Config {
    
    }
    

    输出结果:

    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
    config
    
  • 指定过滤规则(按照类型)

    @Configuration()
    @ComponentScan(basePackages = "com.ice.spring",useDefaultFilters = false,
            includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Dog.class,Person.class})})
    public class Config {
    
    }
    

    输出结果:

    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
    config
    dog
    person
    
  • 指定过滤规则(按照注解类型)

    @Configuration()
    @ComponentScan(basePackages = "com.ice.spring", useDefaultFilters = false,
            includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Mine.class)})
    public class Config {
    	
    }
    

    这里 Mine 是自定义注解,注解在 Fish 类上,输出结果为:

    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
    config
    fish
    
  • 指定排除规则

    @Configuration()
    @ComponentScan(basePackages = "com.ice.spring",
            excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Cat.class)})
    public class Config {
    
    }
    

    输出结果:

    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
    config
    dog
    person
    
  • 按照自定义规则

    自定义的规则就是编写 TypeFilter 的实现类,重写 match() 方法,返回 true 表示匹配成功,false 表示匹配失败:

    public class MyTypeFilter implements TypeFilter {
        
        /**
         * metadataReader:读取到的当前正在扫描的类的信息
         * metadataReaderFactory:可以获取到其他任何类的信息
         */
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
    
            // 获取当前类的注解的信息
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
    
            // 获取当前正在扫描的类的信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
    
            String className = classMetadata.getClassName();
    
            System.out.println("--->" + className);
    
            if (className.contains("cat")) {
                return true;
            }
    
            // 获取当前类的资源信息(类的路径)
            Resource resource = metadataReader.getResource();
            return false;
        }
    }
    

    此时配置类以这么写:

    @Configuration()
    @ComponentScan(basePackages = "com.ice.spring", useDefaultFilters = false,
            excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)})
    public class Config {
    
    }
    

    输出结果:

    --->com.ice.spring.ioc.IoCTest
    --->com.ice.spring.ioc.IoCTest2
    --->com.ice.spring.annotation.Mine
    --->com.ice.spring.config.MyTypeFilter
    --->com.ice.spring.entity.Cat
    --->com.ice.spring.entity.CatConstructor
    --->com.ice.spring.entity.Dog
    --->com.ice.spring.entity.DogConstructor
    --->com.ice.spring.entity.Fish
    --->com.ice.spring.entity.FishWatcher
    --->com.ice.spring.entity.Person
    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
    config
    

4.2 组件注册

4.2.1 显式注册 Bean

在配置类中,我们用如下方式注册一个 Bean 实例:

@Configuration()
@ComponentScan(basePackages = "com.ice.spring")
public class Config {
    @Bean
    public Fish fish(){
        return new Fish();
    }
}

方法名就是 beanName

也可以显示指定 beanName:

@Bean("baby")
public Fish fish(){
    return new Fish();
}

4.2.2 扫描注册

这里就是前面说的 @Component@Repository@Service@Controller 等默认过滤规则的注解的类,会被 Spring 扫描并添加到 Bean 容器。

4.2.3 @Import 注册

4.2.3.1 直接导入

这种方式比较简单:

@Configuration
@Import(Cat.class)
public class Config {
    
}

也可以同时导入多个类注册 Bean

@Configuration
@Import({Dog.class, Person.class})
public class Config {

}
4.2.3.2 ImportSelector

我们需要实现 ImportSelector 接口并实现接口中的抽象方法 selectImports(),然后用 @Import 接口引入,这样,selectImports() 方法返回的字符串数组中的每个元素所指向的类就会被注册到 Spring 容器中。

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.ice.spring.entity.Apple","com.ice.spring.entity.Banana"};
    }
}
@Configuration
@Import(MyImportSelector.class)
public class Config {

}
4.2.3.3 ImportBeanDefinitionRegistrar

这种方式需要实现 ImportBeanDefinitionRegistrar 接口并实现 registerBeanDefinitions() 方法,这种方式是调用 BeanDefinitionRegistryregisterBeanDefinition() 方法来手工注册 Bean。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:BeanDefinition 注册类
     *
     * 把所有需要添加到容器中的 bean,调用 BeanDefinitionRegistry.registerBeanDefinition 手工注册进来
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean dog = registry.containsBeanDefinition("com.ice.spring.entity.Dog");
        boolean person = registry.containsBeanDefinition("com.ice.spring.entity.Person");
        if (dog && person) {
            // 指定 bean 定义信息(bean 的类型、作用域等等)
            RootBeanDefinition apple = new RootBeanDefinition(Apple.class);
            // 注册一个 bean 的定义信息,并指定 bean 名
            registry.registerBeanDefinition("apple", apple);
        }
    }
}
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class,Dog.class,Person.class})
public class Config {

}

4.2.4 FactoryBean 注册

我们创建一个类实现 FactoryBean 接口及其抽象方法就可以利用该类将 Bean 注册到容器中。

public class BananaBean implements FactoryBean<Banana> {
    public Banana getObject() throws Exception {
        System.out.println("BananaBean 的 getObject() 方法");
        return new Banana();
    }

    public Class<?> getObjectType() {
        return Banana.class;
    }

    // 返回 true 说明 scope 是 singleton,否则是 prototype
    public boolean isSingleton() {
        return false;
    }
}

在配置类中,我们应该这么写:

@Configuration
public class Config {
    @Bean
    public BananaBean bananaBean() {
        return new BananaBean();
    }
}

这次我要给出测试方法了,因为稍微和前面傻瓜式的不太一样:

@Test
void test() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
    Banana banana = applicationContext.getBean(Banana.class);
    Banana banana1 = applicationContext.getBean(Banana.class);
    System.out.println(banana == banana1);
    System.out.println("===========================================================");
    Object bananaBean = applicationContext.getBean("bananaBean");
    System.out.println(bananaBean.getClass());  // 默认返回的是工厂方法注册的 bean
    Object bean = applicationContext.getBean("&bananaBean");
    System.out.println(bean.getClass()); // 如果要获取工厂 bean 本身在开头加 &
}

输出结果为:

BananaBean 的 getObject() 方法
BananaBean 的 getObject() 方法
false
===========================================================
BananaBean 的 getObject() 方法
class com.ice.spring.entity.Banana
class com.ice.spring.entity.BananaBean

4.2.5 条件注册

使用 @Conditional 注解,可以按照一定的条件进行判断,满足条件给容器中注册 Bean

例如,我们现在有三个类,DogCatPerson,我们想当容器中存在 Dog 类型的 Bean 时才注册 Person 类型 Bean 到容器中,Cat 则是当容器中没有 Person 类型的 Bean 才注册,我们该怎么写?

首先我们看看 @Conditional 注解的源码。

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

需要在使用时传递条件值,是一个 Condition 数组,进入 Condition 接口中查看,可以发现是个函数式接口:

@FunctionalInterface
public interface Condition {
    /**
     * ConditionContext:判断条件能使用的上下文环境(根据你注解的对象来决定是什么时候的环境)
     * AnnotatedTypeMetadata:当前标注了 @Conditional 注解的类或方法的注释信息
     */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

我们只要实现 matches() 就完成一个条件。

下面我们来实现上面的要求。

public class PersonCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 1. 能获取到 IOC 使用的 BeanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 2. 获取该类的加载器
        ClassLoader classLoader = context.getClassLoader();
        // 3. 获取当前的环境信息
        Environment environment = context.getEnvironment();
        // 4. 获取到 bean 定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        return registry.containsBeanDefinition("dog");
    }
}
public class CatCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 1. 能获取到 IOC 使用的 BeanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 2. 获取该类的加载器
        ClassLoader classLoader = context.getClassLoader();
        // 3. 获取当前的环境信息
        Environment environment = context.getEnvironment();
        // 4. 获取到 bean 定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();

        return !registry.containsBeanDefinition("dog");
    }
}

配置类如下:

@Configuration
public class Config {
    @Bean
    public Dog dog() {
        return new Dog();
    }

    @Bean
    @Conditional(CatCondition.class)
    public Cat cat() {
        return new Cat();
    }

    @Bean
    @Conditional(PersonCondition.class)
    public Person person() {
        return new Person();
    }
}

此时, Cat 不会被注册到容器中,如果将 Dog 的 Bean 注册方法注释掉,则 Person 不会被注册到容器中。

4.2.6 @Scope

就是 Bean 的作用域,可选的有四种:

  • singleton
  • prototype
  • request
  • session

主要有下面两种使用情况

@Bean
@Scope("singleton")
public Dog dog() {
    return new Dog();
}
/*---------------------------------------------------------------------------------------------------*/
@Component
@Scope("prototype")
public class Apple {
}

4.2.7 懒加载

有些时候,我们并不希望容器启动时候就注册 Bean 到 Spring 容器中,我们可以使用 @Lazy 注解来实现延迟注册。

@Configuration
public class MainConfig {
    @Bean("person")
    @Lazy  // 懒加载
    public Person person() {
        return new Person("亚索", 20);
    }
}

4.2.8 导入原生配置文件

有些时候,我们仍然会用到 XML 显式配置,如老项目的二次开发,很可能以前用的就是 XML配置,现在要用 JavaConfig 方式简化配置。我们只需要使用 @ImportResource 注解,就可以将指定路径的配置文件加载。

@Configuration
@ImportResource("classpath:spring-ioc.xml")
public class Config {

}

4.3 生命周期切面方法

4.3.1 initMethod 与 destroyMethod

我们可以在 Bean 的创建之前和销毁之前,切入自定义的方法来做一些处理。

例如如下类:

@Getter
@Setter
@ToString
public class LifeStyle {
    @Value("18")
    private int age;


    public void init(){
        System.out.println("执行 init 方法");
    }

    public LifeStyle(){
        System.out.println("执行无参构造方法");
    }


    public LifeStyle(int age){
        System.out.println("执行有参构造方法");
    }

    public void destroy_bean(){
        System.out.println("执行 destroy_bean 方法");
    }
}

如何切入 init()destroy() 方法呢?

传统的 XML 配置方式为:

<bean id="lifeStyle" class="com.ice.spring.entity.LifeStyle" 
      name="lifeStyle" init-method="init" destroy-method="destroy_bean"/>

JavaConfig 方式为:

@Configuration
public class Config {
    @Bean(initMethod = "init",destroyMethod = "destroy_bean")
    public LifeStyle lifeStyle(){
        return new LifeStyle();
    }
}

我们执行如下测试方法:

void test03(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
    LifeStyle bean = applicationContext.getBean("lifeStyle", LifeStyle.class);
    System.out.println(bean);
    applicationContext.close();
}

输出结果为:

执行无参构造方法
执行 init 方法
LifeStyle(age=18)
执行 destroy_bean 方法

注意多实例的情况,容器在多实例情况下不会管理这个 Bean,在第一次使用时创建实例,调用 init() 方法,容器不会调用 destroy() 方法。

4.3.2 InitializingBean 与 DisposableBean

这种方式是实现 InitializingBeanDisposableBean 接口,实现 afterPropertiesSet()destroy() 方法来实现方法的切入。

@Getter
@Setter
@ToString
public class LifeStyle implements InitializingBean, DisposableBean {
    @Value("18")
    private int age;


    public void init(){
        System.out.println("执行 init 方法");
    }

    public LifeStyle(){
        System.out.println("执行无参构造方法");
    }


    public LifeStyle(int age){
        System.out.println("执行有参构造方法");
    }

    public void destroy_bean(){
        System.out.println("执行 destroy_bean 方法");
    }

    public void destroy() throws Exception {
        System.out.println("执行 destroy 方法");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("执行 afterProperties 方法");
    }
}

这里我与前面的方法弄一起了,看看合在一起的执行顺序:

执行无参构造方法
执行 afterProperties 方法
执行 init 方法
LifeStyle(age=18)
执行 destroy 方法
执行 destroy_bean 方法

4.3.3 @PostConstruc 与 @PreDestroy

这是 JSR 250 提供的注解,和前面两种方式作用类似,我们直接看看效果:

package com.ice.spring.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Getter
@Setter
@ToString
public class LifeStyle implements InitializingBean, DisposableBean {
    @Value("18")
    private int age;


    public void init(){
        System.out.println("执行 init 方法");
    }

    public LifeStyle(){
        System.out.println("执行无参构造方法");
    }


    @PostConstruct
    public void postConstruct(){
        System.out.println("执行 postConstruct 方法");
    }

    @PreDestroy
    public void preDestroy(){
        System.out.println("执行 preDestroy 方法");
    }

    public void destroy_bean(){
        System.out.println("执行 destroy_bean 方法");
    }

    public void destroy() throws Exception {
        System.out.println("执行 destroy 方法");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("执行 afterProperties 方法");
    }
}

此时的整体输出顺序为:

执行无参构造方法
执行 postConstruct 方法
执行 afterProperties 方法
执行 init 方法
LifeStyle(age=18)
执行 preDestroy 方法
执行 destroy 方法
执行 destroy_bean 方法

4.4 BeanPostProcessor

BeanPostProcessor 接口是 Spring 提供的众多接口之一,他的作用主要是如果我们需要在 Spring 容器完成 Bean 的实例化、配置和其他的初始化前后添加一些自己的逻辑处理,我们就可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。

4.4.1 Spring 中 Bean 的实例化的简要过程

在这里插入图片描述

由图可以看出,Spring 中的 BeanPostProcessor 在实例化过程处于的位置分为两部分–前置处理和后置处理,而 BeanPostProcessor 接口也提供了两个可实现的方法,下面我们看一下源码:

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

由方法名字也可以看出,前者在实例化及依赖注入完成后、在任何初始化代码(比如配置文件中的 init-method)调用之前调用;后者在初始化代码调用之后调用。此处需要注意的是:接口中的两个方法都要将传入的 Bean 返回,而不能返回 null,如果返回的是 null 那么我们通过 getBean() 方法将得不到目标。

4.4.2 自定义类来实现 BeanPostProcessor 接口

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;


public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization...");
        System.out.println("    " + beanName + "=>" + bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization...");
        System.out.println("    " + beanName + "=>" + bean);
        return bean;
    }
}

看看它的效果:

postProcessBeforeInitialization...
    cat=>Cat{name='汤姆', age=0}
postProcessAfterInitialization...
    cat=>Cat{name='汤姆', age=0}
postProcessBeforeInitialization...
    dog=>Dog{name='black', age=12}
postProcessAfterInitialization...

Spring 底层大量使用 BeanPostProcessor,如 @Autowired@Async 等等注解的解析使用。

4.5 Aware 注入 Spring 底层组件

自定义组件想要使用 Spring 底层的一些组件(ApplicationContextBeanFactory…)可以实现 XXXXAware 接口,其顶级接口为 Aware

在创建对象的时候,会调用接口规定的方法注入相关组件。

XXXXAware 其实都是由 ApplicationContextAwareProcessor 后置处理器处理的,以回调方式传入底层组件。它重写了 BeanPostProcessor 后置处理器的 postProcessBeforeInitialization() 方法:

class ApplicationContextAwareProcessor implements BeanPostProcessor{
    
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        // 这里就判断了,如果没有实现那些个 XXXAware 接口,则不处理,直接返回 bean
        if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
              bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
              bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||
              bean instanceof ApplicationStartupAware)) {
            return bean;
        }

        AccessControlContext acc = null;

        if (System.getSecurityManager() != null) {
            acc = this.applicationContext.getBeanFactory().getAccessControlContext();
        }

        // invokeAwareInterfaces() 方法就是处理那些个 XXXAware 接口重写方法
        if (acc != null) {
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                invokeAwareInterfaces(bean);
                return null;
            }, acc);
        }
        else {
            invokeAwareInterfaces(bean);
        }

        return bean;
    }
	// ...
}

下面是 invokeAwareInterfaces() 的源码:

private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof EnvironmentAware) {
        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
    }
    if (bean instanceof EmbeddedValueResolverAware) {
        ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
    }
    if (bean instanceof ResourceLoaderAware) {
        ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
    }
    if (bean instanceof ApplicationEventPublisherAware) {
        ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
    }
    if (bean instanceof MessageSourceAware) {
        ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
    }
    if (bean instanceof ApplicationStartupAware) {
        ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
    }
    if (bean instanceof ApplicationContextAware) {
        ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
    }
}

下面我们自己写一个栗子:

public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("传入的 IOC:" + applicationContext);
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("当前 bean 的名字:" + name);
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String s = resolver.resolveStringValue("你好${os.name},我是#{4*5}");
        System.out.println("解析的字符串:" + s);
    }
}

测试代码为:

void test(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
    Red red = applicationContext.getBean("red", Red.class);
    System.out.println(red);
}

输出结果为:

当前 bean 的名字:red
解析的字符串:你好Windows 10,我是20
传入的 IOC:org.springframework.context.annotation.AnnotationConfigApplicationContext@548ad73b, started on Sun Aug 22 16:21:06 CST 2021
com.ice.spring.entity.Red@4d154ccd

4.6 Profile

Profile 是 Spring 为我们提供的,可以根据当前环境,动态的激活和切换一系列组件的功能。

@Profile 指定组件在哪个环境情况下才能被注册到容器中,不指定,等价于 @Profile("default"),任何环境下都能注册这个组件。

@Profile 注解可以写在类上和方法上。

一个配置文件中有的标注 @Profile 注解,有的没有,那么没有标注的 Bean 任何时候都会被加载。

例如下面的配置。不同环境注册不同的 Bean:

@Configuration
public class Config {
    @Bean
    @Profile("test")
    public Cat cat() {
        return new Cat();
    }

    @Bean
    @Profile("dev")
    public Person person() {
        return new Person();
    }

    @Bean
    public Dog dog() {
        return new Dog();
    }
}

测试方法:

  1. 使用命令行动态传递参数

    @Test
    public void test() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Arrays.stream(beanDefinitionNames).forEach(System.out::println);
    }
    

    执行前要设置虚拟机参数:spring.profiles.active 指定是哪个环境

  2. 代码方式激活

    void test() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.getEnvironment().setActiveProfiles("dev");
        applicationContext.register(Config.class);
        applicationContext.refresh();
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Arrays.stream(beanDefinitionNames).forEach(System.out::println);
    }
    
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值