视频地址:https://www.bilibili.com/video/BV1oW41167AV
对应代码Git库地址:https://github.com/whh306318848/spring-annotation.git
- Spring中所有的组件都放在IOC容器中,组件之间的关系通过容器进行自动装配(DI)即依赖注入;
- 以前是通过xml方式配置bean标签的方式注册对象
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="person" class="com.atguigu.bean.Person">
<property name="age" value="18"></property>
<property name="name" value="zhangsan"></property>
</bean>
</beans>
在代码中通过new一个ClassPathXmlApplicationContext对象(返回的是ApplicationContext的IOC容器)使用配置文件的方式构造Bean,构造函数的参数是配置文件的路径(基于resources目录的相对路径)
// 使用配置文件的方式构造一个Bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 用id获取
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);
通过ApplicationContext的getBean方法以Bean的id作为参数,获取在容器中注册的Bean对象
3. 现在可以通过配置类的方式注册对象,使用@Configuration注解告诉Spring某个类是配置类,作用与xml配置文件一样
4. 在配置类中通过给function添加@Bean注解(适用于给容器中导入第三方组件或类),实现向容器中注册Bean,其作用与xml配置文件中的bean标签一样
@Bean(value = "person") // 通过value指定id
public Person person01() {
return new Person("lisi", 20);
}
4.1. 方法的返回值类型即是注册Bean的类型
4.2. 默认情况下方法名是Bean的id,也可以通过@Bean注解的value参数手动设置Bean的id
4.3. 在代码中通过new一个AnnotationConfigApplicationContext对象(返回的是ApplicationContext的IOC容器)使用配置类的方式构建Bean,构造函数的参数是配置类的类型
// 使用配置类的方式构造一个Bean
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
// 用类型获取
Person bean = applicationContext.getBean(Person.class);
通过ApplicationContext的getBean方法以Bean的类型作为参数,获取在容器中注册的Bean对象
5. 在实际开发中,使用包扫描(适用于自己写的类导入到容器中)的情况比较多,包扫描的配置有两种,分别是配置文件配置和注解方式配置
5.1. 若使用配置文件方式,需要在配置文件中添加context:component-scan标签
<context:component-scan base-package="com.atguigu"></context:component-scan>
其中base-package的值指定了包扫描的范围,意思是扫描该包下的所有组件,只要标注了@Controller、@Service、@Repository、@Component这四个注解中任何一个注解,都会被自动扫描加进容器中
5.2. 使用注解方式配置包扫描时,需要用到@ComponentScan注解,该注解的value参数用于指定要扫描的包,其作用与配置文件的base-package值一样
@ComponentScan(value = "com.atguigu")
5.3. 通过getBeanDefinitionNames方法可以获取到容器中所有的bean的名称
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
5.4. 可以通过@ComponentScan注解的excludeFilter属性设置需要被排除的类,其值是一个@Filter数组
5.5. @Filter有两个常用属性分别是type和classes,type用于标注过滤规则,总共有五个,分别是:
– ANNOTATION(按照注解进行过滤)
– ASSIGNABLE_TYPE(按照给定的类型过滤,包含其子类、实现类等)
– ASPECTJ(按照ASPECTJ表达式过滤,不常用)
– REGEX(按照正则表达式过滤)
– CUSTOM(按照自定义规则规律,自定义规则需要实现TypeFilter接口)
classes是一个类型数组
@ComponentScan(value = "com.atguigu", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
})
5.6. 当配置includeFilters规则时,需要先将@ComponentScan注解的useDefaultFilters参数设置为false,否则默认的包扫描规则就是扫描所有的注解
5.7. 如果使用的是JDK 8及以上版本,@ComponentScan是一个重复组件,可以在配置类上定义多个@ComponentScan注解,用以配置多个包扫描规则
5.8. 考虑到兼容性问题,如果有多个包扫描规则,建议使用@ComponentScans注解实现多个包扫描策略,@ComponentScans注解只有一个value参数,其值是@ComponentScan数组
// 配置多个包扫描规则
@ComponentScans(value = {
@ComponentScan(value = "com.atguigu", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
}, useDefaultFilters = false) // 配置包扫描的范围
})
5.9. @Filter注解中,若type为FilterType.CUSTOM,则classes的值必须是实现了TypeFilter接口的类,其中有一个match方法,用于自定义过滤规则,指定报名下的每一个类,都会被扫描到并由match方法进行匹配,match方法有两个参数分别是MetadataReader和MetadataReaderFactory,metadataReader的值是“当前正在扫描的类的信息”,metadataReaderFactory的值是“其他任何类(不止当前类)的信息”,其返回值是boolean型,若当前类需要被过滤(包含或排除),则返回true,反之则返回false
public class MyTypeFilter implements TypeFilter {
/***
* @param metadataReader 读取到的当前正在扫描的类的信息
* @param metadataReaderFactory 获取到其他任何类(不止当前类)的信息
* @return 若该类被过滤,则返回true,反之则返回false
**/
@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);
if (className.contains("er")) {
return true;
}
return false;
}
}
- 在IOC容器中,默认Bean都是单实例的,无论从容器中获取到少次相同类型的Bean,其实返回的都是同一个对象,我们可以通过@Scope注解指定每个Bean的作用范围
6.1. @Scope注解的value取值范围包括4个
– prototype:多实例,IOC容器启动时并不会去调用方法创建对象放在容器中,而是每次获取的时候都会调用方法创建对象
– singleton:单实例(默认值),IOC容器启动会调用方法创建对象到IOC容器中,以后每次获取就是直接从容器(map.get())中拿
– request:同一次请求创建一个实例
– session:同一个session创建一个实例
@Scope("prototype")
@Bean("person")
public Person person() {
System.out.println("给容器中添加Person...");
return new Person("张三", 25);
}
6.2. 当Bean是单实例时,在IOC容器创建好之后,Bean实例马上就会被创建,以后每次获取,都是从容器中取相同的Bean实例
6.3. 当Bean是多实例时,是在每次获取对象的时候,IOC容器才会创建Bean实例并返回
7. 当Bean是单实例时,可以通过@Lazy注解设置懒加载,即首次获取对象时,再创建Bean实例,而不是在IOC容器创建好之后,马上创建Bean实例
@Lazy
@Bean("person")
public Person person() {
System.out.println("给容器中添加Person...");
return new Person("张三", 25);
}
- Spring可以按照条件给容器中注册Bean实例,通过@Conditional注解实现,@Conditional注解的值是实现Condition接口的类的数组,该注解可以标注在类上,也可以标注在方法上
8.1 Condition接口中定义了一个matches方法,该方法有两个值ConditionContext表示“能使用的上下文(环境信息)”,AnnotatedTypeMetadata表示“当前标注了Condition注解的注释信息”
/**
* @author: wuhaohua
* @date: Created in 2020/12/28 17:53
* @description: 判断是否Linux操作系统
*/
public class LinuxCondition implements Condition {
/***
* @param conditionContext 判断能使用的上下文(环境)
* @param annotatedTypeMetadata 当前标注了Condition注解的注释信息
* @throws
* @Author: wuhaohua
* @Date: 2020/12/28
* @Description:
**/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 1、能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
// 2、获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
// 3、获取到环境信息
Environment environment = conditionContext.getEnvironment();
// 4、获取到bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
String property = environment.getProperty("os.name");
if (property.contains("Linux")) {
return true;
}
return false;
}
}
/**
* @author: wuhaohua
* @date: Created in 2020/12/28 17:54
* @description: 判断是否Windows操作系统
*/
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 1、能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
// 2、获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
// 3、获取到环境信息
Environment environment = conditionContext.getEnvironment();
// 4、获取到bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
String property = environment.getProperty("os.name");
if (property.contains("Windows")) {
return true;
}
return false;
}
}
8.2. 在定义好Condition的实现类后,在需要控制的Bean注册方法上增加相应的注解
/***
* @Conditional({Condition}):按照一定的条件进行判断,满足条件给容器中注册bean
* 如果系统是Windows,就给容器中注册("bill")
* 如果系统是Linux,就给容器中注册("linus")
**/
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01() {
return new Person("Bill Gates", 62);
}
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person02() {
return new Person("Linus", 48);
}
8.3. 当@Conditional注解标注在类上时,满足condition条件,这个类中配置的所有bean注册才会生效
9. 使用@Import也可以快速的给容器中导入一个组件,将其标注在配置类上,其值是一个类型数组,被赋值的类会自动注册到IOC容器中,注册的Bean的id默认是组件的全类名(即含包名的类名)
@Configuration
// 导入组件,id默认是组件的全类名(含包名)
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {
//......
}
9.1. 除了直接将类写在@Import注解中,也可以通过实现ImportSelector接口的方式导入其他类,实现ImportSelector接口中的selectImports方法,该方法只有一个参数AnnotationMetadata,其值是当前标注@Import注解的类(即MainConfig2类)的所有信息,该方法的返回值是要导入到容器中的组件全类名数组
/**
* @author: wuhaohua
* @date: Created in 2020/12/28 19:59
* @description: 自定义逻辑返回需要导入的组件
*/
public class MyImportSelector implements ImportSelector {
/***
* @param annotationMetadata 当前标注@Import注解的类的所有信息
* @return 要导入到容器中的组件全类名
* @throws
* @Author: wuhaohua
* @Date: 2020/12/28
* @Description:
**/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//方法不要返回null值,否则会报空指针异常
return new String[]{"com.atguigu.bean.Blue", "com.atguigu.bean.Yellow"};
}
}
9.2. 除了ImportSelector还能通过实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法可以自定义给容器中注册bean(手动注册bean到容器中,在SpringBoot源码中用的比较多),该方法有两个值,分别是AnnotationMetadata(当前类的所有注解及其他信息)和BeanDefinitionRegistry(bean定义的注册类,所有bean的定义都在这注册,可以通过这个变量给容器注册一个bean),主要通过操作BeanDefinitionRegistry进行组件添加,并且可以指定Bean的名字
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/***
* @param importingClassMetadata 当前类的所有注解及其他信息
* @param registry bean定义的注册类,所有bean的定义都在这注册,可以通过这个变量给容器注册一个bean
* 把所有需要添加到容器中的bean:调用BeanDefinitionRegistry.registerBeanDefinition手工注册进来
* @Author: wuhaohua
* @Date: 2020/12/29
* @Description:
**/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean red = registry.containsBeanDefinition("com.atguigu.bean.Red");
boolean blue = registry.containsBeanDefinition("com.atguigu.bean.Blue");
if (red && blue) {
// 指定Bean的定义信息,即bean的类型、bean的scope等等
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
// 指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
- 使用Sprig提供的FactoryBean(工厂Bean)也可以向容器中注册Bean,区别于普通的Bean,当普通的Bean注册到容器时,容器会调用其无参构造函数创建一个对象注册在容器中,而FactoryBean是一个接口,容器会调用其getObject方法,将其返回的对象注册到容器中(也会调用其getObjectType和isSingleton方法)
10.1. 若要使用FactoryBean,需要自定义类并实现FactoryBean接口,接口上的泛型代表着需要注册到容器的类
/**
* @author: wuhaohua
* @date: Created in 2020/12/29 15:31
* @description: 创建一个Spring定义的FactoryBean
*/
public class ColorFactoryBean implements FactoryBean<Color> {
/***
* @return 需要添加到容器中的Color对象
* @Author: wuhaohua
* @Date: 2020/12/29
* @Description: 返回一个Color对应,这个对象会被添加到容器中
**/
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean getObject...");
return new Color();
}
/***
* @return Bean的类型
* @Author: wuhaohua
* @Date: 2020/12/29
* @Description: Bean的类型
**/
@Override
public Class<?> getObjectType() {
return Color.class;
}
/***
* @return 若返回true,这个bean是单例的,在容器中只保存一份,若返回false,每次获取时都会创建一个新的bean
* @Author: wuhaohua
* @Date: 2020/12/29
* @Description: 控制该bean是否是单实例
**/
@Override
public boolean isSingleton() {
return false;
}
}
然后需要在配置类中将自定义的FactoryBean实现类作为Bean对象添加到容器中
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
10.2. 这里需要注意,虽然从代码层面看起来装配的是ColorFactoryBean,但实际上装配的是ColorFactoryBean的getObject方法创建的Color对象,若想获取到ColorFactoryBean本身的话,需要在获取的beanId前面增加一个&前缀
@Test
public void testImport() {
printBeans(applicationContext);
Blue bean = applicationContext.getBean(Blue.class);
System.out.println(bean);
// 工厂Bean获取的是调用getObject创建的对象
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
System.out.println("bean的类型" + colorFactoryBean.getClass());
Object colorFactoryBean3 = applicationContext.getBean("&colorFactoryBean");
System.out.println("bean的类型" + colorFactoryBean3.getClass());
}