01 spring 常用注解 原理 逻辑 代码演示
这是自己观看视频的笔记
文章目录
- 01 spring 常用注解 原理 逻辑 代码演示
- 一、组件注册
- 1.1-spring注解驱动开发
- 1.2-组件注册 @Configuration
- 1.3-组件注册 Configuration、Bean、ComponentScan(s)、TypeFilter
- 1.4-组件注册 @Scope
- 1.5-组件注册@Lazy-bean懒加载
- 1.6-组件注册 @Conditional 按照条件给容器注入Bean
- 1.7-组件注册 @Import快速导入
- 1.7.1-组件注册 @ImportSelector
- 1.7.2-组件注册 @ImportBeanDefinationRegister
- 1.8-组件注册 @FactoryBean
- 二、生命周期
- 2.1-生命周期 @Bean指定初始化和销毁方法
- 2.2-生命周期 InitializingBean和DisposableBean
- 2.3-生命周期 @PostConstruct和@PreDestroy
- 2.4-生命周期 BeanPostProcessor(后置处理器)
- 2.4.1-生命周期 BeanPostProcessor原理
- 2.4.2-生命周期 spring底层对BeanPostProcessor的使用
- 三、属性赋值
- 3.1-属性赋值 @Value
- 3.2-属性赋值 @PropertySource加载外部配置文件
- 四、自动装配
- 4.1-自动装配 @Autowired & @Qualifier & @Primary
- 4.2-自动装配 JSR250-@Resource、JSR330-@Inject
- 4.3-自动装配 方法、构造器位置的自动装配 & Aware注入Spring底层组件 & 原理
- 4.4-自动装配 @Profile 根据环境注册Bean
一、组件注册
1.1-spring注解驱动开发
1.2-组件注册 @Configuration
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
- 如果创建beans.xml没有如下内容,则为没有添加spring支持
- 则开启
1.3-组件注册 Configuration、Bean、ComponentScan(s)、TypeFilter
- 创建一个Person类
-
配置beans.xml
-
给一个id方便从容器中获取
-
可以通过property作为一个属性的赋值[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgFFaJDX-1590551049657)((https://cdn.jsdelivr.net/gh/1392517138/imgRepository@master/image-20200511220207948.png)]
这是以前的一个配置文件
-
开始使用,写一个测试类
- 通过ClassPathXmlApplicationContext,表示类路径下的一个xml配置文件。会返回IOC容器
- 可通过getBean加上“id”进行获取。或是类型
-
以前配置文件的方式被替换为了配置类
- 建立一个config.MainConfig
2.回到MainTest,通过AnnotationConfigApplicationContext注解式的config,它传入的就是这个配置类。相当于是穿配置类的位置。
3. 通过getBeanDefinitionNames可获得Bean容器中组件的所有名称
- 也可通过getBeanNamesForType
- 也可通过getBeanNamesForType
- 通过上面的这个方法,我也可改变组键名称。要么改方法名,要么采用下面这种方式
-
在实际开发中,包的扫描写得比较多
- 这是xml的写法(以前的方式)
<!--包扫描、只要标注了@Controller、@Service、@Repository、@Component,都会被自动扫描加入容器中--> <context:component-scan base-package="top.p3wj"></context:component-scan>
3. 写在配置类中
4. 效果演示
发现其中mainConfig也是一个组件,是因为@Configuration也是一个@Component
5. excludeFilters,过滤不扫描的内容。
它是一个Filter()数组
//excludeFilters = Filter[] 指定扫描的时候按照规则排除哪些规则
//includeFilters = Filter[] 指定扫描的时候只需要包含哪些组件
//useDefaultFilters 默认为true,加载所有组件
6.@ComponentScan
在8几以上中才可以
如果不是,就使用@ComponentScan,指定扫描策略
FilterType.ASSIGNABLE_TYPE 按照给定的类型
FilterType.ASPECTJ 使用ASPECTJ表达式(不太常用)
FilterType.REGEX 使用正则表达式
实现TypeFilter
top.p3wj中的每一个类都会进入进行匹配
1.4-组件注册 @Scope
默认单实例
* ConfigurableBeanFactory#SCOPE_PROTOTYPE prototype 多实例
* ConfigurableBeanFactory#SCOPE_SINGLETON singleton 单实例(默认值)
* org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request 同一次请求创建一个实例
* org.springframework.web.context.WebApplicationContext#SCOPE_SESSION session 同一个session创建一个实例
改为多实例后
这其实就相当于在xml文件中,Bean里加上scope
ioc容器启动会创建对象,放到ioc容器中,以后每次获取就是直接从容器(map.get())中拿
1.下面演示单实例
把test02中下面的注释掉
2.多实例情况
就不打印“给容器中添加Person…”了
ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象。
1.5-组件注册@Lazy-bean懒加载
- 单实例bean,默认在容器启动的时候创建对象
- 懒加载:容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化
即在第一次获得的时候才加载
若取消懒加载
1.6-组件注册 @Conditional 按照条件给容器注入Bean
@Conditional ,按照一定的条件进行判断,满足条件给容器中注册Bean
先前准备:
要求:
* 如果是MacOs,给容器注册 jobs
* 如果是linux,给容器注册linus
通过applicationContext拿到一个运行的环境
要传入一个Condition数组。@Conditional({})
配置两个实现了Condition的类
设置一下参数
//boolean pserson = registry.containsBeanDefinition("pserson");//也可判断容器中是否包含一个Bean。也可给容器中注册Bean
可以做非常多的判断条件
也可放在类上,含义即满足当前条件,这个类中配置的 所有bean注册才能生效
注意:
若有多个,则为按照顺序判断(猜测)已经设置-Dos.name=Linux
1.7-组件注册 @Import快速导入
1.新建一个color类
2.使用@Import
3.测试
//导入组件,id默认是组件的全类名,@Import(要导入到容器中到组件),容器中就会自动注册这个组件,id默认是全类名
可以导入多个,现写一个Red类
1.7.1-组件注册 @ImportSelector
2) ImportSelector:返回需要导入的组件的全数组
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
前提条件:
打上断点进行调试
结果:
如果返回null:---->return null;会报空指针,因为在拿类名的时候
所以不要返回null,可以返回一个空数组
return new String[]{};
可获取到所以注解信息及类相关的
因为是return的,所以也被导入了
1.7.2-组件注册 @ImportBeanDefinationRegister
ImportBeanDefinitionRegistrar 手动注册Bean
command+alt+b查看实现类
注意,import方式注入的名称为全类名
1.8-组件注册 @FactoryBean
通过此方法把对象放到容器中
结果:
//工厂获取的是调用getObject创建的对象
@Override
public boolean isSingleton() {
return false;
}
isSingleton是false情况下是多实例,每一次获取都调用getObject
Reason:
使用Spring提供的FactoryBean(工厂Bean)
* 1) 默认获取到到是工厂bean调用getObject创建的对象
* 2) 要获取工厂Bean本身,我们需要给id前面加一个&
* &colorFactoryBean
二、生命周期
2.1-生命周期 @Bean指定初始化和销毁方法
在以前,可以指定初始化和销毁方法
创建Car
public class MainConfigOfLifeCycle {
@Bean
public Car car(){
return new Car();
}
}
以上针对单实例对象
通过调用close()关闭
2.当改为多实例Bean当时候
2.1 当获取的时候才会初始化
2.2 容器关闭后不会进行销毁
* bean的生命周期:
* bean创建---初始化---销毁的过程
* 容器管理bean的生命周期:
* 我们可以自定义初始化和销毁方法;容器在 bean进行到当前生命周期的时候调用我们自定义的初始化和销毁方式
*构造(对象创建)
* 单实例:在容器启动的时候创建对象
* 多实例:在每次获取的时候创建对象
*初始化:
* 对象创建完成,并赋值好,调用初始化方法。。。
*销毁:
* 单实例:容器关闭的时候
* 多实例:容器不会管理这个bean,容器不会调用销毁方法;需要手动调用
* 1)、指定初始化和销毁方法:
* 指定init-method和destroy-method方法
2.2-生命周期 InitializingBean和DisposableBean
在这里提出一个问题,@Bean不搭配@Configuration使用跟搭配有什么区别(还未解决)
通过包扫描的方式进行注册,同时通过实现接口进行初始化和销毁
2)、通过让Bean实现InitializingBean(定义初始化逻辑)
2.3-生命周期 @PostConstruct和@PreDestroy
这是java规范的注解,目前java8能用
* 3)、可以使用JSR250:
* @PostConstruct:在bean创建完成并且属性赋值完成:来执行初始化方法
* @PreDestroy:在容器销毁bean之前通知我们进行清理工作
2.4-生命周期 BeanPostProcessor(后置处理器)
先创建对象-》〉》〉初始化
* 4)、BeanPostProcessor【interface】,bean的后置处理器:
* 在bean初始化前后进行一些处理工作:
* postProcessBeforeInitialization:在初始化之前工作
* postProcessAfterInitialization:在初始化之后工作
2.4.1-生命周期 BeanPostProcessor原理
打断点debug一下
1.查看调用方法栈,往上依次看
10.来看怎么创建的
12.创建好后准备初始化
13.原理体现的地方
点进去看一下,里面的内容
applyBeanPostProcessorsAfterInitialization 就不看了,类似的
* 遍历得到容器中所有的BeanPostProcessor:挨个执行beforeInitialization
* 一单返回null,跳出for循环,不会执行后面单BeanPostProcessor
* populateBean(beanName, mbd, instanceWrapper); 给bean进行属性赋值的
*
* initializeBean:
* {
* wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
* this.invokeInitMethods(beanName, wrappedBean, mbd);
* wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
* }
2.4.2-生命周期 spring底层对BeanPostProcessor的使用
我们来看一下ApplicationContextAwareProcessor,实现的BeanPostProcessor
其实看看之前写的MyBeanPostProcessor,也是实现了它的方法
ApplicationContextAwareProcessor是封装好了这些实现
再来看下ApplicationContextAware
如果是就调用下面的 invokeAwareInterfaces(bean);
点进去查看
来debug看一下
准备如下:
Debug:
并且会把ioc容器传进来,怎么传进来呢?接下来根据方法栈来看下之前调用的
1.在这里调用 postProcessBeforeInitialization
再来看看 BeanValidationPostProcessor
该PostProcessor是用来做数据校验的,在web用的比较多
再来看看 InitDestroyAnnotationBeanPostProcessor
为什么Dog中标注了这样的注解它就知道在哪执行呢?
我们打一个断点来看一下
**出了个问题,init不执行(以后解决)**发现的问题为:
我还重写了个BeanPostProcessor的postProcessBeforeInitialization方法,@PostConstruct也是用
这里重写了:
再来看看 AutowiredAnnotationBeanPostProcessor
@Autowired也是通过这个来注值的
* spring底层对BeanPostProcessor的使用
* bean赋值、注入其他组件,@Autowired,生命周期注解功能,@Async,等等
* 都是用BeanPostProcessor来完成的
三、属性赋值
3.1-属性赋值 @Value
并没有赋值,在以前的beans.xml文件中:
是通过这样的方式
我们有一个对应的@Value
如果要在beans.xml中使用${}取properties中的值就要配上这个名称空间
并采用以下方式:
ApplicationContext applicationContext2 = new ClassPathXmlApplicationContext("beans.xml");
但是启动会报错:
这是因为少了context相关的解析文件。
解决如下,在 xsi:schemaLocation 中添加:
3.2-属性赋值 @PropertySource加载外部配置文件
@PropertySource() 属性的来源
因为是运行时候的变量,所以还可以用applicationContext.getEnvironment
也可用PropertySources,是一个可重复标注的注解
四、自动装配
4.1-自动装配 @Autowired & @Qualifier & @Primary
此外注意,在此版本中:
准备:
两个Dao,通过labe的设置看注入的是哪一个。相同类型,一个叫bookDao,一个叫bookDao2
你可以看见第二个报错了,因为按照BookDao.class去找的
@Autowired 如果找到相同类型组件,就需要按照属性名去寻找
如:BookDao bookDao;就是按照bookDao去寻找
我现在按照名字去找,可以发现这两个BookDao是不一样的,即@Repository与@Bean返回 注入的两者不一样
虽然在BookService通过@Autowired默认方法吗作为id注入,但是我们可以通过@Qualifier去改变
另外:
将BookDao的@Repository注释掉
此时相当于容器中没有任何一个BookDao
运行时会报错
看一下@Autowired,我们要达到没有该Bean就不注入的效果
此时service就正常了
我们发现如果容器同一个类型要用多个就要写多次@Qualifier,那么可以选用@Primary,即让spring进行自动装配的时候,默认使用首选的bean
总结
4.2-自动装配 JSR250-@Resource、JSR330-@Inject
@Resource是默认按照属性的名称
@Inject需要导入maven依赖
4.3-自动装配 方法、构造器位置的自动装配 & Aware注入Spring底层组件 & 原理
@Autowired:
1、标注在方法位置
给Car也加上@Component,通过配置类@ComponentScan扫描进去
默认加载ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
加到参数上,效果也一样
如果只有一个有参构造器,@Autowired可以不用写
以@Bean的方式注入
准备
自定义
传进来这个applicationContext我们就能用,类似这样的有很多
总接口是Aware
找几个Aware来看一下
解析字符串的值
这些Aware都是由相应的XXXAwareProcessor来处理的
我们来看一下怎么将applicationContext注入进来的
打一个断点:
跟之前是类似的
总结一下
4.4-自动装配 @Profile 根据环境注册Bean
引入c3p0和mysql-connector
配置dbconfig.properties
并加载@PropertySource(“classpath:/dbconfig.properties”)
来自spring的黑科技
另一种方式,Aware接口
那么来看看@Profile
* @description Profile:
* Spring为我们提供的可以根据当前环境,动态地激活和切换一些列组件的功能:
* 开发环境、测试环境、生产环境:
* 数据源:(/A)(/B)(/C)
* @Profile 指定组件在哪个环境下才能被注册到容器中。不指定任何环境下都能注册这个组件
* 1)、加了环境表示的bean,只有这个环境被激活的时候才能被注册到容器中,默认是default环境
默认是"default",可以看见只有标了"default"才会被加入到容器中
那么怎么切换环境呢?
最简单的方法,使用命令行参数
2.代码的方式
针对于AnnotationConfigApplicationContext
配置类一注册进来,容器就启动刷新了,环境还没有设置好
如果写在类上,就代表整个类里面的内容是否会被加载
* @Profile 指定组件在哪个环境下才能被注册到容器中。不指定任何环境下都能注册这个组件
* 1)、加了环境表示的bean,只有这个环境被激活的时候才能被注册到容器中,默认是default环境
* 2) 、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
* 3)、没有标注环境标识的bean,任何环境下都会加载