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.8-组件注册 @FactoryBean
- 二、生命周期
- 2.1-生命周期 @Bean指定初始化和销毁方法
- 2.2-生命周期 InitializingBean和DisposableBean
- 2.3-生命周期 @PostConstruct和@PreDestroy
- 2.4-生命周期 BeanPostProcessor(后置处理器)
- 三、属性赋值
- 四、自动装配
一、组件注册
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) 包扫描+组件标注注解 (@Controller/@Service/@Repository/@Component)[自己写的]
* 2) @Bean[导入的第三方包里面的组件],但是它比较麻烦(需要return等)
* 3) @Import[快速给容器导入一个组件]
*/
1.新建一个color类
2.使用@Import
3.测试

//导入组件,id默认是组件的全类名,@Import(要导入到容器中到组件),容器中就会自动注册这个组件,id默认是全类名

可以导入多个,现写一个Red类

1.7.1-组件注册 @ImportSelector
2) ImportSelector:返回需要导入的组件的全数组
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
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,任何环境下都会加载
转载:https://blog.csdn.net/qq_29310729/article/details/106376587