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可以配合Configuration
、ImportSelector
以及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