Spring注解驱动开发01

1、@Configuration+@Bean

Spring IOC和DI

在Spring容器的底层,最重要的功能就是IOC和DI,也就是控制反转和依赖注入。

在这里插入图片描述

DI和IOC它俩之间的关系是DI不能单独存在,DI需要在IOC的基础上来完成。

xml配置文件注入JavaBean

1、新建一个maven工程,spring-annotation-01

2、添加以下依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.20</version>
</dependency>

3、新增实体类bean->Person

package com.jian.bean;

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

4、在resources目录下新建beans.xml,添加以下代码

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="person" class="com.jian.bean.Person">
        <property name="name" value="Json"/>
        <property name="age" value="22"/>
    </bean>

</beans>

5、测试

package com.jian;

import com.jian.bean.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Person person = applicationContext.getBean("person", Person.class);
        System.out.println(person);
    }
}

在这里插入图片描述

使用注解注入JavaBean

1、添加包config,添加配置类MainConfig

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//@Configuration代表这是一个配置类,等同于beans.xml
@Configuration
public class MainConfig {
    //给容器中注册bean,等同于<bean id="person" class="com.jian.bean.Person" />
    @Bean
    public Person person() {
        return new Person("liza", 23);
    }
}

2、测试

package com.jian;

import com.jian.bean.Person;
import com.jian.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {
    public static void main(String[] args) {
//        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//        Person person = applicationContext.getBean("person", Person.class);
//        System.out.println(person);
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = applicationContext.getBean(Person.class);
        System.out.println(person);
    }
}

在这里插入图片描述

3、获取bean的名字

package com.jian;

import com.jian.bean.Person;
import com.jian.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] beanNames = applicationContext.getBeanNamesForType(Person.class);
        for (String name : beanNames) {
            System.out.println(name);
        }
    }
}

在这里插入图片描述

4、可以通过修改@Bean注解修改Bean的名称

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//@Configuration代表这是一个配置类,等同于beans.xml
@Configuration
public class MainConfig {
    //给容器中注册bean,等同于<bean id="person" class="com.jian.bean.Person" />
    @Bean("people")
    public Person person() {
        return new Person("liza", 23);
    }
}

在这里插入图片描述

2、ComponentScan

在实际项目中,我们更多的是使用Spring的包扫描功能对项目中的包进行扫描,凡是在指定的包或其子包中的类上标注了@Repository、@Service、@Controller、@Component注解的类都会被扫描到,并将这个类注入到Spring容器中

1、新增BooKDao、BooKService、BookController类,并且使用相应的注解

package com.jian.controller;

import org.springframework.stereotype.Controller;

@Controller
public class BookController {
}
#################################################
package com.jian.dao;

import org.springframework.stereotype.Repository;

@Repository
public class BookDao {
}
#################################################
package com.jian.service;

import org.springframework.stereotype.Service;

@Service
public class BookService {
}

使用xml配置包扫描

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置扫描包:由@Repository,@Service,@Controller,@Component标注的类就会被注册到容器中-->
    <context:component-scan base-package="com.jian"/>
    <bean id="person" class="com.jian.bean.Person">
        <property name="name" value="Json"/>
        <property name="age" value="22"/>
    </bean>

</beans>

配置<context:component-scan base-package="com.jian"/>需要添加以下命名空间

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context        https://www.springframework.org/schema/context/spring-context.xsd

添加测试类test->com.jian.test->ComponentScanTest类

  • 引入junit依赖
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
  • 测试
package com.jian.test;

import com.jian.config.MainConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ComponentScanTest {

    @Test
    public void test(){
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }
}

在这里插入图片描述

使用注解配置扫描包

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//@Configuration代表这是一个配置类,等同于beans.xml
@Configuration
//等价于<context:component-scan base-package="com.jian"/>
@ComponentScan("com.jian")
public class MainConfig {
    //给容器中注册bean,等同于<bean id="person" class="com.jian.bean.Person" />
    @Bean("people")
    public Person person() {
        return new Person("liza", 23);
    }
}

测试

    @Test
    public void test02(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
    }

在这里插入图片描述

指定排除某些组件

@ComponentScan(value = "com.jian",excludeFilters = {
    		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:除了@Controller和@Service标注的组件之外,IOC容器中剩下的组件我都要,即相当于是我要排除@Controller和@Service这俩注解标注的组件。
		 */
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class})
})

在这里插入图片描述

指定扫描某些注解标注的类

我们也可以使用ComponentScan注解类中的includeFilters()方法来指定Spring在进行包扫描时,只包含哪些注解标注的类。

这里需要注意的是,当我们使用includeFilters()方法来指定只包含哪些注解标注的类时,需要禁用掉默认的过滤规则。 还记得我们以前在XML配置文件中配置这个只包含的时候,应该怎么做吗?我们需要在XML配置文件中先配置好use-default-filters="false",也就是禁用掉默认的过滤规则,因为默认的过滤规则就是扫描所有的,只有我们禁用掉默认的过滤规则之后,只包含才能生效

@ComponentScan(value = "com.jian",includeFilters = {
    		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Controller注解标注的类
		 */
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})
},useDefaultFilters = false)

在这里插入图片描述

重复注解

可以在一个类上重复使用这个@ComponentScan注解

@ComponentScan(value = "com.jian",includeFilters = {
    		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Controller注解标注的类
		 */
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})
},useDefaultFilters = false)
@ComponentScan(value = "com.jian",includeFilters = {
    		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Service注解标注的类
		 */
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})
},useDefaultFilters = false)

@ComponentScans注解

@ComponentScans(value={
    @ComponentScan(value = "com.jian",includeFilters = {
    		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Controller注解标注的类
		 */
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})
},useDefaultFilters = false),
    @ComponentScan(value = "com.jian",includeFilters = {
    		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Service注解标注的类
		 */
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Service.class})
},useDefaultFilters = false)
})

3、FilterType扫描规则

在使用@ComponentScan注解实现包扫描时,我们可以使用@ComponentScan.Filter指定过滤规则,在@ComponentScan.Filter中,通过type来指定过滤的类型。而@ComponentScan.Filter注解中的type属性是一个FilterType枚举

在这里插入图片描述

FilterType.ANNOTATION:按照注解进行包含或者排除

例如,使用@ComponentScan注解进行包扫描时,如果要想按照注解只包含标注了@Controller注解的组件,那么就需要像下面这样写了

@ComponentScan(value="com.jian", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Controller注解标注的类
		 */
		@ComponentScan.Filter(type=FilterType.ANNOTATION, value={Controller.class})
}, useDefaultFilters=false) // value指定要扫描的包

FilterType.ASSIGNABLE_TYPE:按照给定的类型进行包含或排除

例如,使用@ComponentScan注解进行包扫描时,如果要想按照给定的类型只包含BookService类(接口)或其子类(实现类或子接口)的组件,那么就需要像下面这样写了。

@ComponentScan(value="com.jian", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 * value:我们需要Spring在扫描时,只包含@Controller注解标注的类
		 */
		@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value={BookService.class})
}, useDefaultFilters=false) // value指定要扫描的包

此时,只要是BookService这种类型的组件,都会被加载到容器中。也就是说,当BookService是一个Java类时,该类及其子类都会被加载到Spring容器中;当BookService是一个接口时,其子接口或实现类都会被加载到Spring容器中

FilterType.ASPECTJ:按照ASPECTJ表达式进行包含或排除

@ComponentScan(value="com.jian", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 */
		@ComponentScan.Filter(type=FilterType.ASPECTJ, value={AspectJTypeFilter.class})
}, useDefaultFilters=false) // value指定要扫描的包

这种过滤规则基本上不怎么用!

FilterType.REGEX:按照正则表达式进行包含或排除

例如,使用@ComponentScan注解进行包扫描时,按照正则表达式进行过滤,就得像下面这样子写

@ComponentScan(value="com.jian", includeFilters={
		/*
		 * type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
		 */
		@ComponentScan.Filter(type=FilterType.REGEX, value={RegexPatternTypeFilter.class})
}, useDefaultFilters=false) // value指定要扫描的包

这种过滤规则基本上也不怎么用!

FilterType.CUSTOM:自定义规则进行包含或排除

如果实现自定义规则进行过滤时,自定义规则的类必须是org.springframework.core.type.filter.TypeFilter接口的实现类。

要想按照自定义规则进行过滤,首先我们得创建org.springframework.core.type.filter.TypeFilter接口的一个实现类,例如MyTypeFilter,该实现类的代码一开始如下所示

package com.jian.config;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

public class MyTypeFilter implements TypeFilter {
    /*
    metadataReader:读取到当前正在扫描的类的信息
    metadataReaderFactory:可以获取到其他任何类的信息的工厂
    */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类的信息,比如它的类型是什么,实现了什么接口...
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类的资源信息,比如类的路径等信息
        Resource resource = metadataReader.getResource();
        //获取当前正在扫描类的类名
        String className = classMetadata.getClassName();
        System.out.println("--->" + className);
        //扫描类的类名中包含er的放行,注入容器
        if (className.contains("er")) {
            return true;
        }
        return false;
    }
}

修改配置类MainConfig

将@ComponentScan.Filter中的type属性设置为FilterType.CUSTOM,value属性设置为自定义规则的类所对应的Class对象。

//@Configuration代表这是一个配置类,等同于beans.xml
@Configuration
//等价于<context:component-scan base-package="com.jian"/>
@ComponentScan(value = "com.jian",includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM,value = {MyTypeFilter.class})
},useDefaultFilters = false)
public class MainConfig {
    //给容器中注册bean,等同于<bean id="person" class="com.jian.bean.Person" />
    @Bean("people")
    public Person person() {
        return new Person("liza", 23);
    }
}

在这里插入图片描述

从以上输出的结果信息中,你还可以看到输出了一个myTypeFilter,你不禁要问了,为什么会有myTypeFilter呢?这就是因为我们现在扫描的是com.jian包,该包下的每一个类都会进到这个自定义规则里面进行匹配,若匹配成功,则就会被包含在容器中

当我们实现TypeFilter接口时,需要实现该接口中的match()方法,match()方法的返回值为boolean类型。当返回true时,表示符合规则,会包含在Spring容器中;当返回false时,表示不符合规则,那就是一个都不匹配,自然就都不会被包含在Spring容器中。另外,在match()方法中存在两个参数,分别为MetadataReader类型的参数和MetadataReaderFactory类型的参数,含义分别如下

  • metadataReader:读取到的当前正在扫描的类的信息
  • metadataReaderFactory:可以获取到其他任何类的信息的工厂

4、@Scope设置组件作用域

Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,并将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象。如果每次从Spring容器中获取对象时,都要创建一个新的实例对象,那么该如何处理呢?此时就需要使用@Scope注解来设置组件的作用域了
从@Scope注解类的源码中可以看出,在@Scope注解中可以设置如下值:

  • ConfigurableBeanFactory#SCOPE_PROTOTYPE
  • ConfigurableBeanFactory#SCOPE_SINGLETON
  • org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
  • org.springframework.web.context.WebApplicationContext#SCOPE_SESSION

SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype

SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session

在这里插入图片描述

默认单实例

新建一个config–>MainConfig2

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {
    @Scope
    @Bean
    public Person person() {
        return new Person("张三", 28);
    }
}

测试

    @Test
    public void test02(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            System.out.println(name);
        }
        Object person1 = applicationContext.getBean("person");
        Object person2 = applicationContext.getBean("person");
        System.out.println(person1==person2);
    }

在这里插入图片描述

Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到了Spring容器中;以后每次从容器中获取组件实例对象时,都是直接返回相应的对象,而不必再创建新的对象了

多实例Bean作用域

修改MainConfig2

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {
    @Scope("prototype")  //设置Bean的作用域
    @Bean
    public Person person() {
        return new Person("张三", 28);
    }
}

测试代码同上

在这里插入图片描述

在创建Spring容器时,并不会去实例化和加载多实例对象,当向Spring容器中获取Person实例对象时,Spring容器才会实例化Person对象,再将其加载到Spring容器中去

5、@Lazy

Spring在启动时,默认会将单实例bean进行实例化,并加载到Spring容器中去。也就是说,单实例bean默认是在Spring容器启动的时候创建对象,并且还会将对象加载到Spring容器中。如果我们需要对某个bean进行延迟加载,那么该如何处理呢?此时,就需要使用到@Lazy注解了
什么是懒加载

懒加载就是Spring容器启动的时候,先不创建对象,在第一次使用(获取)bean的时候再来创建对象,并进行一些初始化。

非懒加载模式

MainConfig2,一般用于单实例对象

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig2 {
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }
}

测试

    @Test
    public void test03(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器被创建了...");
    }

在这里插入图片描述

懒加载模式

首先,我们在MainConfig2配置类中的person()方法上加上一个@Lazy注解,以此将Person对象设置为懒加载,如下所示

@Configuration
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }
}

测试方法同上

在这里插入图片描述

加上@Lazy注解后,bean对象是何时被创建的呢?

我们可以试着在IOCTest类中的test05()方法中获取一下Person对象,如下所示。

    @Test
    public void test03(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器被创建了...");
        Person person = applicationContext.getBean("person", Person.class);
    }

在这里插入图片描述

这说明,我们在获取bean对象的时候,创建出了bean对象并加载到Spring容器中去了

懒加载,也称延时加载,仅针对单实例bean生效

6、@Conditional注解

@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean

在这里插入图片描述

从@Conditional注解的源码来看,@Conditional注解不仅可以添加到类上,也可以添加到方法上。在@Conditional注解中,还存在着一个Condition类型或者其子类型的Class对象数组

在这里插入图片描述

可以看到,它是一个接口。所以,我们使用@Conditional注解时,需要写一个类来实现Spring提供的Condition接口,它会按照@Conditional中的条件来注入所符合的组件

不带条件注册bean

我们在MainConfig2配置类中新增person01()方法和person02()方法,并为这两个方法添加@Bean注解,如下所示

package com.jian.config;

import com.jian.bean.Person;
import org.springframework.context.annotation.*;

@Configuration
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    public Person person02() {
        return new Person("Linus", 52);
    }
}

测试

    @Test
    public void test04(){
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
        for (String name : beanNamesForType) {
            System.out.println(name);
        }
    }

在这里插入图片描述

从输出结果中可以看出,同时输出了bill和linus。说明默认情况下,Spring容器会将单实例并且非懒加载的bean注册到IOC容器中。

接下来,我们再输出bean的名称和bean实例对象信息,此时我们只须在test06()方法中添加如下的代码片段即可

    @Test
    public void test04() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
        System.out.println(beansOfType);
    }

在这里插入图片描述

带条件注册Bean

现在,我们就要提出一个新的需求了,比如,如果当前操作系统是Windows操作系统,那么就向Spring容器中注册名称为bill的Person对象;如果当前操作系统是Linux操作系统,那么就向Spring容器中注册名称为linus的Person对象。要想实现这个需求,我们就得要使用@Conditional注解了

使用Spring中的AnnotationConfigApplicationContext类就能够获取到当前操作系统的类型

    @Test
    public void test04() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        System.out.println(environment.getProperty("os.name"));
        Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
        System.out.println(beansOfType);
    }

在这里插入图片描述

要想使用@Conditional注解,我们需要实现Condition接口来为@Conditional注解设置条件,所以,这里我们创建了两个实现Condition接口的类,它们分别是LinuxCondition和WindowsCondition,如下所示

package com.jian.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class WindowsCondition implements Condition {
    /**
     * ConditionContext:判断条件能使用的上下文(环境)
     * AnnotatedTypeMetadata:当前标注了@Conditional注解的注释信息
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //1.获取到bean的创建工厂,能获取到IOC容器使用到的BeanFactory,它就是创建对象以及进行装配的工厂)
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2.获取到类的加载器
        ClassLoader classLoader = context.getClassLoader();
        //3.获取当前环境信息,它里面就封装了我们这个当前运行时的一些信息,包括环境变量,以及包括虚拟机的一些变量
        Environment environment = context.getEnvironment();
        //4. 获取到bean定义的注册类
        BeanDefinitionRegistry registry = context.getRegistry();
        // 在这儿还可以做更多的判断,比如说我判断一下Spring容器中是不是包含有某一个bean,就像下面这样,如果Spring容器中果真包含有名称为person的bean,那么就做些什么事情...
        boolean flag = registry.containsBeanDefinition("person");
        String property = environment.getProperty("os.name");
        if (property.contains("Windows")) {
            return true;
        }
        return false;
    }
}
package com.jian.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        //如果是Linux系统
        if (property.contains("Linux")) {
            return true;
        }
        return false;
    }
}

然后,我们就需要在MainConfig2配置类中使用@Conditional注解添加条件了

package com.jian.config;

import com.jian.bean.Person;
import com.jian.condition.LinuxCondition;
import com.jian.condition.WindowsCondition;
import org.springframework.context.annotation.*;

@Configuration
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person02() {
        return new Person("Linus", 52);
    }
}

测试

在这里插入图片描述

可以看到,输出结果中不再含有名称为linus的bean了,这说明程序中检测到当前操作系统为Windows 11之后,没有向Spring容器中注册名称为linus的bean

此外,@Conditional注解也可以标注在类上,标注在类上的含义是:只有满足了当前条件,这个配置类中配置的所有bean注册才能生效,也就是对配置类中的组件进行统一设置

package com.jian.config;

import com.jian.bean.Person;
import com.jian.condition.LinuxCondition;
import com.jian.condition.WindowsCondition;
import org.springframework.context.annotation.*;

@Configuration
@Conditional({WindowsCondition.class})
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person02() {
        return new Person("Linus", 52);
    }
}

@Conditional扩展注解

在这里插入图片描述

7、@Import注解

我们知道,我们可以将一些bean组件交由Spring来管理,并且Spring还支持单实例bean和多实例bean。我们自己写的类,自然是可以通过包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component)的形式将其注册到IOC容器中,但这种方式比较有局限性,局限于我们自己写的类,比方说我们自己写的类,我们当然能把以上这些注解标注上去了

那么如果不是我们自己写的类,比如说我们在项目中会经常引入一些第三方的类库,我们需要将这些第三方类库中的类注册到Spring容器中,该怎么办呢?此时,我们就可以使用@Bean和@Import注解将这些类快速的导入Spring容器中

首先,我们创建一个Color类,这个类是一个空类,没有成员变量和方法,如下所示

package com.jian.bean;

public class Color {
}

然后,我们在IOCTest类中创建一个testImport()方法,在其中输出Spring容器中所有bean定义的名字,来查看是否存在Color类对应的bean实例,以此来判断Spring容器中是否注册有Color类对应的bean实例

    @Test
    public void testImport() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }

在这里插入图片描述

可以看到Spring容器中并没有Color类对应的bean实例。

使用@Import注解

首先,我们在MainConfig2配置类上添加一个@Import注解,并将Color类填写到该注解中,如下所示

package com.jian.config;

import com.jian.bean.Color;
import com.jian.bean.Person;
import com.jian.condition.LinuxCondition;
import com.jian.condition.WindowsCondition;
import org.springframework.context.annotation.*;

@Configuration
@Conditional({WindowsCondition.class})
@Import(Color.class)  //快速导入组件,id就是组件的全类名
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person02() {
        return new Person("Linus", 52);
    }
}

然后,我们运行IOCTest类中的testImport()方法,会发现输出的结果信息如下所示。

在这里插入图片描述

可以看到,输出结果中打印了com.jian.bean.Color,说明使用@Import注解快速地导入组件时,容器中就会自动注册这个组件,并且id默认是组件的全类名。

@Import注解还支持同时导入多个类,例如,我们再次创建一个Red类,如下所示。

package com.jian.bean;

public class Red {
}

然后,我们也将以上Red类添加到@Import注解中,如下所示。

package com.jian.config;

import com.jian.bean.Color;
import com.jian.bean.Person;
import com.jian.bean.Red;
import com.jian.condition.LinuxCondition;
import com.jian.condition.WindowsCondition;
import org.springframework.context.annotation.*;

@Configuration
@Conditional({WindowsCondition.class})
@Import({Color.class, Red.class})  //快速导入组件,id就是组件的全类名
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person02() {
        return new Person("Linus", 52);
    }
}

在这里插入图片描述

可以看到,结果信息中同时输出了com.jian.bean.Color和com.jian.bean.Red,说明Color类对应的bean实例和Red类对应的bean实例都导入到Spring容器中去了

注意:@Import注解只允许放到类上面,不允许放到方法上。

8、ImportSelector接口

我们先看一下@Import注解的源码,如下所示。

在这里插入图片描述

从源码里面可以看出@Import可以配合ConfigurationImportSelector以及ImportBeanDefinitionRegistrar来使用,下面的or表示也可以把Import当成普通的bean来使用。

我们再来看一下ImportSelector接口的源码,如下所示

在这里插入图片描述

该接口文档上说的明明白白,其主要作用是收集需要导入的配置类,selectImports()方法的返回值就是我们向Spring容器中导入的类的全类名。如果该接口的实现类同时实现EnvironmentAware,BeanFactoryAware,BeanClassLoaderAware或者ResourceLoaderAware,那么在调用其selectImports()方法之前先调用上述接口中对应的方法,如果需要在所有的@Configuration处理完再导入时,那么可以实现DeferredImportSelector接口

在ImportSelector接口的selectImports()方法中,存在一个AnnotationMetadata类型的参数,这个参数能够获取到当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息

@ImportSelector接口实例

在com.jian.bean包下新增两个类Yellow和Blue类

然后,我们创建一个MyImportSelector类实现ImportSelector接口,至于使用MyImportSelector类要导入哪些bean,就需要你在MyImportSelector类的selectImports()方法中进行设置了,只须在MyImportSelector类的selectImports()方法中返回要导入的类的全类名(包名+类名)即可,如下所示

package com.jian.condition;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
* 自定义逻辑,返回需要导入的组件
*/
public class MyImportSelector implements ImportSelector {
    @Override
    // 返回值:就是要导入到容器中的组件的全类名
    // AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //默认返回:return new String[0]
        return new String[]{"com.jian.bean.Yellow","com.jian.bean.Blue"};
    }
}

然后,在MainConfig2配置类的@Import注解中,导入MyImportSelector类,如下所示。

package com.jian.config;

import com.jian.bean.Color;
import com.jian.bean.Person;
import com.jian.bean.Red;
import com.jian.condition.LinuxCondition;
import com.jian.condition.MyImportSelector;
import com.jian.condition.WindowsCondition;
import org.springframework.context.annotation.*;

@Configuration
@Conditional({WindowsCondition.class})
@Import({Color.class, Red.class, MyImportSelector.class})  //快速导入组件,id就是组件的全类名
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person02() {
        return new Person("Linus", 52);
    }
}

最后测试

    @Test
    public void testImport() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }

在这里插入图片描述

9、ImportBeanDefinitionRegistrar接口

我们先来看看ImportBeanDefinitionRegistrar是个什么鬼,点击进入ImportBeanDefinitionRegistrar源码,如下所示

在这里插入图片描述

由源码可以看出,ImportBeanDefinitionRegistrar本质上是一个接口。在ImportBeanDefinitionRegistrar接口中,有一个registerBeanDefinitions()方法,通过该方法,我们可以向Spring容器中注册bean实例。

Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口。

ImportBeanDefinitionRegistrar的使用

ImportBeanDefinitionRegistrar需要配合@Configuration和@Import这俩注解,其中,@Configuration注解定义Java格式的Spring配置文件,@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类

既然ImportBeanDefinitionRegistrar是一个接口,那我们就创建一个MyImportBeanDefinitionRegistrar类,去实现ImportBeanDefinitionRegistrar接口,如下所示

package com.jian.condition;

import com.jian.bean.RainBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * AnnotationMetadata:当前类的注解信息
     * BeanDefinitionRegistry:BeanDefinition注册类
     *
     * 我们可以通过调用BeanDefinitionRegistry接口中的registerBeanDefinition方法,手动注册所有需要添加到容器中的bean
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean flag1 = registry.containsBeanDefinition("com.jian.bean.Color");
        boolean flag2 = registry.containsBeanDefinition("com.jian.bean.Red");
        if (flag1&&flag2){
            //指定bean的定义信息,包括bean的类型、作用域等
            //RootBeanDefinition是BeanDefinition的一个实现类
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            //注册一个bean,并指定bean的名称
            registry.registerBeanDefinition("rainBow",beanDefinition);
        }
    }
}

接着,创建一个RainBow类,作为测试ImportBeanDefinitionRegistrar接口的bean来使用,如下所示

package com.jian.bean;

public class RainBow {
}

然后,我们在MainConfig2配置类上的@Import注解中,添加MyImportBeanDefinitionRegistrar类,如下所示。

package com.jian.config;

import com.jian.bean.Color;
import com.jian.bean.Person;
import com.jian.bean.Red;
import com.jian.condition.LinuxCondition;
import com.jian.condition.MyImportBeanDefinitionRegistrar;
import com.jian.condition.MyImportSelector;
import com.jian.condition.WindowsCondition;
import org.springframework.context.annotation.*;

@Configuration
@Conditional({WindowsCondition.class})
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})  //快速导入组件,id就是组件的全类名
public class MainConfig2 {
    @Lazy
    @Bean
    public Person person() {
        System.out.println("把id为person的Bean放到容器中...");
        return new Person("张三", 28);
    }

    @Bean("bill")
    @Conditional({WindowsCondition.class})
    public Person person01() {
        return new Person("Bill Gates", 67);
    }

    @Bean("linus")
    @Conditional({LinuxCondition.class})
    public Person person02() {
        return new Person("Linus", 52);
    }
}

以上registerBeanDefinitions()方法的实现逻辑很简单,就是判断Spring容器中是否同时存在以com.jian.bean.Color命名的bean和以com.jian.bean.Red命名的bean,如果真的同时存在,那么向Spring容器中注入一个以rainBow命名的bean
在这里插入图片描述

可以看到,此时输出了rainBow,说明Spring容器中已经成功注册了以rainBow命名的bean

10、FactoryBean接口

一般情况下,Spring是通过反射机制利用bean的class属性指定实现类来实例化bean的。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,那么则需要在标签中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可以得到一个更加简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑

FactoryBean接口的定义如下所示。

在这里插入图片描述

  • T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,那么该实例会放到Spring容器中单实例缓存池中
  • boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype
  • Class getObjectType():返回FactoryBean创建的bean实例的类型

FactoryBean案例

首先,创建一个ColorFactoryBean类,它得实现FactoryBean接口,如下所示。

package com.jian.bean;

import org.springframework.beans.factory.FactoryBean;

/**
 * 创建一个Spring定义的FactoryBean
 * T(泛型):指定我们要创建什么类型的对象
 */
public class ColorFactoryBean implements FactoryBean<Color> {
    //返回一个Color对象,会把这个对象添加到容器中
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    //返回这个对象的类型
    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    //如果返回true,那么代表这个bean是单实例,在容器中只会保存一份;
    //如果返回false,那么代表这个bean是多实例,每次获取都会创建一个新的bean
    @Override
    public boolean isSingleton() {
        return false;
    }
}

然后,我们在MainConfig2配置类中将ColorFactoryBean注入容器中,如下所示。

    @Bean
    public ColorFactoryBean colorFactoryBean() {
        return new ColorFactoryBean();
    }

这里需要小伙伴们注意的是:我在这里使用@Bean注解向Spring容器中注册的是ColorFactoryBean对象。

那现在我们就来看看Spring容器中到底都有哪些bean。我们所要做的事情就是,运行IOCTest类中的testImport()方法,此时,输出的结果信息如下所示。
在这里插入图片描述

可以看到,结果信息中输出了一个colorFactoryBean,我们看下这个colorFactoryBean到底是个什么鬼!此时,我们对测试类中的testImport()方法稍加改动,添加获取colorFactoryBean的代码,并输出colorFactoryBean实例的类型,如下所示

@Test
public void testImport() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    String[] names = applicationContext.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println(name);
    }
    Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
    System.out.println("bean的类型是: "+colorFactoryBean.getClass());
}

在这里插入图片描述

可以看到,虽然我在代码中使用@Bean注解注入的是ColorFactoryBean对象,但是实际上从Spring容器中获取到的bean对象却是调用ColorFactoryBean类中的getObject()方法获取到的Color对象。

在ColorFactoryBean类中,我们将Color对象设置为单实例bean,即让isSingleton()方法返回true。接下来,我们在IOCTest类中的testImport()方法里面多次获取Color对象,并判断一下多次获取的对象是否为同一对象,如下所示。

    @Override
    public boolean isSingleton() {
        return true;
    }
    @Test
    public void testImport() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        Object bean1 = applicationContext.getBean("colorFactoryBean");
        Object bean2 = applicationContext.getBean("colorFactoryBean");
        System.out.println("bean的类型是: "+bean1.getClass());
        System.out.println("bean1==bean2?"+bean1==bean2);
    }

如果要改成多实例bean,是需要更改ColorFactoryBean中的isSingleton()方法返回值为false

获取ColorFactoryBean本身

只需要在获取工厂Bean本身时,在id前面加上&符号即可,例如&colorFactoryBean

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值