目录
- 前言
- 1 @Configuration和@Bean(给容器中注册组件)
- 2 @ComponentScan( 包扫描)
- 3 使用TypeFilter指定@ComponentScan注解的过滤规则
- 4 @Scope(设置作用域)
- 5 @Lazy --- bean懒加载
- 6 @Conditional --- 按照一定条件给容器中注入bean
- 7 @Import --- 给容器中快速导入一个组件
- 8 在@Import注解中使用ImportSelector接口导入bean
- 9 在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean
- 10 使用FactoryBean向Spring容器中注册bean
- 11 @Bean指定初始化和销毁方法
- 12 使用InitializingBean和DisposableBean来管理bean的生命周期
- 13 @PostConstruct注解和@PreDestroy
- 14 BeanPostProcessor后置处理器
- 15 @Value --- 为属性赋值
- 16 @PropertySource --- 加载配置文件
- 17 @Autowired、@Qualifier、@Primary --- 自动装配
- 18 @Resource注解和@Inject注解
- 19 构造器、参数、方法和属性的自动装配
- 20 自定义组件想要使用Spring容器底层的一些组件
- 21 @Profile --- 环境配置与切换
- 22 搭建AOP测试环境
- 23 扩展原理
- 24 ioc容器创建原理
- 25 spring源码总结
- 26 servlet3.0
- 27 定制与接管 Spring MVC
- 28 异步处理
- 29 Spring MVC中的异步请求处理(返回Callable)
- 30 pring MVC中的异步请求处理(返回DeferredResult)
- 总结
前言
首先本篇文章是基于尚硅谷雷神讲解的spring注解驱动开发视频。
注意:视频中使用的是spring4版本,目前使用spring5版本,略有差异。
本篇文章将会带你进入spring注解驱动开发的学习大门中。
文章将会按照下图的脉络进行学习。
特别注意:源码讲解部分因为本人无任何工作经验, 学习时间过短,理解不了。这部分等工作之后再来看,未完待续。
容器
容器作为第一大部分,内容包括:
- AnnotationConfigApplicationContext
- 组件添加
- 组件赋值
- 组件注入
- AOP
- 声明式事务
扩展原理
扩展原理作为第二大部分,内容包括:
- BeanFactoryPostProcessor
- BeanDefinitionRegistryPostProcessor
- ApplicationListener
- Spring容器创建过程
这部分主要是源码讲解。
Web
Web作为第三大部分,内容包括:
- servlet3.0
- 异步请求
这部分,其实就是SpringMVC,在这个部分中,我们会重点来说异步请求。
1 @Configuration和@Bean(给容器中注册组件)
1.1 准备工作 — 创建工程
使用idea21.3创建一个maven工程(spring-annotion)。
1.2 配置依赖 — pom.xml
将打包方式设置为jar包。引入spring-context依赖,这里我们选择最新的5.3.22版本作为配置工具。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>spring-annotion</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
1.3 创建实体类 — Person
package com.atguigu.bean;
/**
* @author hxld
* @create 2022-08-03 22:34
*/
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 +
'}';
}
}
1.4 使用xml配置方式
1.4.1 创建一个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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.atguigu.bean.Person">
<property name="age" value="18"></property>
<property name="name" value="zhangsan"></property>
</bean>
</beans>
1.4.2 创建测试类 — MainTest
public class MainTest {
public static void main(String[] args) {
//beans.xml配置方式的测试
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//本来是object,然后强转为person
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);
}
}
1.5 使用注解方式
使用上面的xml文件配置方式,我们发现太过于繁琐。那不如使用注解方式来实现。
1.5.1 创建配置类 — MainConfig
配置类 MainConfig
其实就相当于配置文件beans.xml
。
@Configuration : 告诉spring这是一个配置类。
@Bean :给容器中注册一个bean。相当于beans.xml中的bean。beans.xml中的id对应配置类中的方法名,beans.xml中的class对应的是配置类中的返回值类型。
//配置类 == 配置文件(beans.xml)
//告诉spring这是一个配置类
@Configuration
public class MainConfig {
//给容器中注册一个bean ,对应beans.xml中的bean。类型:返回值类型,id:方法名
//value可以写person
//@Bean("person01")
@Bean("person")
public Person person() {
return new Person("lisi",20);
}
}
启动时调用方法,将方法的返回值放到IOC容器中,方法名作为组件的id。
1.5.2 测试
public class MainTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);
//获取Person这个类型的组件在IOC容器中的名字
String[] type = applicationContext.getBeanNamesForType(Person.class);
for (String name: type) {
System.out.println(name);
}
}
}
上面我们发现是person,这不就是我们创建的配置类的方法名吗。
经观察,使用注解注入JavaBean时,bean在IOC容器中的名称就是使用@Bean注解标注的方法名称。我们可不可以为bean单独指定名称呢?那必须可以啊!只要在@Bean注解中明确指定名称就可以了。如果我们将person
这个方法名改为person01
,那么测试类中测试组件在IOC容器中的名字时输出就是person01
。都不需要修改方法名,只需要在@Bean(“person01”)既可。
输出结:person01
。
1.6 小结
我们在使用注解方式向Spring的IOC容器中注入JavaBean时,如果没有在@Bean注解中明确指定bean的名称,那么就会使用当前方法的名称来作为bean的名称;如果在@Bean注解中明确指定了bean的名称,那么就会使用@Bean注解中指定的名称来作为bean的名称。
2 @ComponentScan( 包扫描)
在实际项目中,我们更多的是使用Spring的包扫描功能对项目中的包进行扫描,凡是在指定的包或其子包中的类上标注了@Repository、@Service、@Controller、@Component注解的类都会被扫描到,并将这个类注入到Spring容器中。
Spring包扫描功能可以使用XML配置文件进行配置,也可以直接使用@ComponentScan注解进行设置,使用@ComponentScan注解进行设置比使用XML配置文件来配置要简单的多。
2.1 创建三层架构组件
BookController
@Controller
public class BookController {
}
BookDao
@Repository
public class BookDao {
}
BookService
@Service
public class BookService {
}
2.2 使用xml配置方式
2.2.1 在beans.xml中配置
使用xml配置方式进行包扫描需要引入名称空间context
具体配置:
<!--包扫描,只要标注了@Controller @Service @Repository @Component的都会被加入到ioc容器中-->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!--注意包扫描还有一个默认规则,是扫描全部,即use-default-filters = true,我们设置只包含/只扫描的时候要禁用,设置为false-->
<!--<context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan>-->
-
包扫描,只要标注了@Controller @Service @Repository @Component的都会被加入到ioc容器中。
-
注意包扫描还有一个默认规则,是扫描全部,即use-default-filters = true,我们设置只包含/只扫描的时候要禁用,设置为false。
2.2.2 测试
public class IocTest {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
//查看bean容器中所有的组件,输出名字
String[] names = applicationContext.getBeanDefinitionNames();
for (String name: names) {
System.out.println(name);
}
}
}
问题:不知道为什么包扫描使用xml配置文件扫描不出自己创建的三层架构组件?
2.3 @ComponentScan(包扫描)
2.3.1 配置中上进行注解
@ComponentScan(value="com.atguigu")
//设置哪些不扫描 excludeFilters = Filter[] 指定扫描的时候按照什么规则排除哪些组件,不包含哪些组件
//type是指按哪种类型来进行过滤,classes为一个数组,里面为具体的过滤条件实体。
@ComponentScan(value="com.atguigu",excludeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = {
Service.class})
})
//includeFilter =Filter[] 只包含哪些组件,必须设置useDefaultFilters = false,禁用默认全局扫描
@ComponentScan(value="com.atguigu",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class})
},useDefaultFilters = false )
//设置多个扫描策略
@ComponentScans(
value={
@ComponentScan(value="com.atguigu",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class})
},useDefaultFilters = false )
,@ComponentScan(value="com.atguigu",excludeFilters = {
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes = {
Service.class})
})}
)
excludeFilters = Filter[]
设置哪些不扫描 指定扫描的时候按照什么规则排除哪些组件,不包含哪些组件,
type
是指按哪种类型来进行过滤,classes
为一个数组,里面为具体的过滤条件实体。includeFilter =Filter[]
只包含哪些组件,必须设置useDefaultFilters = false
,禁用默认全局扫描。@ComponentScans
:可设置多个扫描策略。
2.3.2 测试
public class IocTest {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
//查看bean容器中所有的组件,输出名字
String[] names = applicationContext.getBeanDefinitionNames();
for (String name: names) {
System.out.println(name);
}
}
}
2.3.3 @ComponentScan本质
@ComponentScan注解就是一个重复注解。
3 使用TypeFilter指定@ComponentScan注解的过滤规则
3.1 FilterType的几种类型
FilterType
- ANNOTATION :按照注解
- ASSIGNABLE_TYPE :按照指定类型 ,比如说classes={BookService.class}
- ASPECTJ:基本不用
- REGEX:按照正则表达式
- CUSTOM:按照自定义规则。需要我们创建一个 TypeFilter的实现类
3.2 使用自定义过滤规则
3.2.1 创建一个TypeFilter的实现类 — MyTypeFilter
继承TypeFilter
// FilterType 的自定义规则
public class MyTypeFilter implements TypeFilter {
//match方法返回值是布尔类型,如果是true--匹配成功,false--匹配失败
//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);
//自定义过滤规则,service,controller中就包含er
if(className.contains("er")){
return true;
}
//false表示一个都不匹配,即一个都不扫描
return false;
}
}
- match方法返回值是布尔类型,如果是true–匹配成功,false–匹配失败
- metadataReader ----读取到的当前正在扫描的类的信息
- metadataReaderFactory -----可以获取到其他任何类信息的
3.2.2 配置类中配置
// FilterType 的自定义规则
@ComponentScan(value="com.atguigu",includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {
MyTypeFilter.class})
},useDefaultFilters = false )
3.2.3 测试
public class IocTest {
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
//查看bean容器中所有的组件,输出名字
String[] names = applicationContext.getBeanDefinitionNames();
for (String name: names) {
System.out.println(name);
}
}
}
4 @Scope(设置作用域)
4.1 注解方式
4.1.1 创建MainConfig2配置类
@Configuration
public class MainConfig2 {
//prototype:多实例 ioc容器启动并不会去调用方法创建对象放到容器中,每次获取的时候才会调用方法创建对象,获取一次创建一次。
//singleton:单实例(默认) ioc容器启动会调用方法创建对象放到Ioc容器中,以后每次获取就是直接从容器(相当于map.get())中拿。
//request:同一次请求创建一个实例 --- web工程中 -----基本不使用
//session:同一个session创建一个实例 --- web工程中 -----基本不使用
@Scope("prototype") //调整作用域
//默认是单实例对象
@Bean("person")
public Person person(){
//测试实例在多久创建,如下语句。
System.out.println("给容器中添加person....");
return new Person("张三",25);
}
}
scopr的四种类型:
prototype
:
- 多实例
- ioc容器启动并不会去调用方法创建对象放到容器中,每次获取的时候才会调用方法创建对象,获取一次创建一次。
singleton
:
- 单实例(默认)
- ioc容器启动会调用方法创建对象放到Ioc容器中,以后每次获取就是直接从容器(相当于map.get())中拿。
request
:
- 同一次请求创建一个实例 — web工程中 -----基本不使用
session
:
- 同一个session创建一个实例 — web工程中 -----基本不使用
4.1.2 测试
当我们设置多实例的时候:
@Test
public void test02() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//查看bean容器中所有的组件,输出名字
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
//ioc容器创建完成(测试多实例对象在何时被创建)
System.out.println("ioc容器创建完成......");
//测试@bean给我们创建的是单实例对象
//第一次获取
Object bean = applicationContext.getBean("person");
//第二次获取
Object bean2 = applicationContext.getBean("person");
//判断这两个对象是否相等
System.out.println(bean == bean2);
//结果为true,即默认是单实例的
//当我们设置@Scope("prototype")时,结果为false,即不是同一个对象,多实例
}
}
输出结果:
结果为false,表示每次创建的实例都不是同一个。
4.1.2 beans.xml中配置
在beans.xml中我们这样设置。如下图:
5 @Lazy — bean懒加载
Spring在启动时,默认会将单实例bean进行实例化,并加载到Spring容器中去。也就是说,单实例bean默认是在Spring容器启动的时候创建对象,并且还会将对象加载到Spring容器中。如果我们需要对某个bean进行延迟加载,那么该如何处理呢?此时,就需要使用到@Lazy注解了。
5.1 什么是懒加载
懒加载 :容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化。
5.2 注解实现方式
@Lazy // 懒加载 :容器启动不创建对象,第一次使用(获取)Bean创建对象,并初始化
//默认是单实例对象
@Bean("person")
public Person person(){
//测试实例在多久创建,如下语句。
System.out.println("给容器中添加person....");
return new Person("张三",25);
}
5.3 测试
在类中方法里面加入一句输出语句,代表实例被创建调用。如上。
[1] 使用懒加载
@Test
public void test02() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//查看bean容器中所有的组件,输出名字
String[] names = applicationContext.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
Object bean = applicationContext.getBean("person");
}
输出结果中没有调用类中的方法,意味着容器创建完成了,但是实例没有被创建。
当我们获取bean的时候,看测试结果
发现调用方法了,即创建实例。
[2] 未使用懒加载
不设置获取对象。
我们看到在容器创建的时候,就已经调用方法了。创建实例了。
6 @Conditional — 按照一定条件给容器中注入bean
6.1 概述
-
Spring支持按照条件向IOC容器中注册bean,满足条件的bean就会被注册到IOC容器中,不满足条件的bean就不会被注册到IOC容器中。
-
@Conditional注解可以按照一定的条件进行判断,满足条件向容器中注册bean,不满足条件就不向容器中注册bean。
-
@Conditional注解是由Spring Framework提供的一个注解,它位于 org.springframework.context.annotation包内。
从@Conditional注解的源码来看,@Conditional注解不仅可以添加到类上,也可以添加到方法上。在@Conditional注解中,还存在着一个Condition类型或者其子类型的Class对象数组,Condition是个啥呢?
可以看到,它是一个接口。所以,我们使用@Conditional注解时,需要写一个类来实现Spring提供的Condition接口,它会匹配@Conditional所符合的方法,然后我们就可以使用我们在@Conditional注解中定义的类来检查了。
6.2 创建接口实现类
@Conditional ({Condition}):按照一定的条件进行判断,满足条件给容器中注册Bean
- 如果当前IOC容器操作环境是windows系统,则给容器中注册bill
- 如果当前IOC容器操作环境是LINXU系统,则给容器中注册linus
//判断是否为Linux系统
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.可以获取到IOC使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2.获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3. 获取当前环境信息
Environment environment = context.getEnvironment();
//4.获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
//5.可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean person = registry.containsBeanDefinition("person");
String property = environment.getProperty("os.name");
if(property.contains("linux")){
return true;
}
return false;
}
}
//判断是否为Windows系统
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}
6.3 创建两个方法
@Bean("bill")
public Person person01(){
return new Person("Bill Gates",62);
}
@Bean("linus")
public Person person02(){
return new Person("linus",48);
}
6.4 在方法上使用注解
6.5 测试
[1] 使用注解
@Test
public void test03() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
//获取当前操作环境
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//动态获取环境变量的值
String property = environment.getProperty("os.name");
System.out.println(property);
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
for (String name : namesForType) {
System.out.println(name);
}
//获取所有对象
Map<String, Person> personMap = applicationContext.getBeansOfType(Person.class);
System.out.println(personMap);
}
测试结果,当前操作环境是window 10,会创建bill这个bean。
[2] 不使用注解
bill 和 linus 都被创建了。
7 @Import — 给容器中快速导入一个组件
目前给容器中注册组件有以下几种方法。
- 包扫描+组件标注注解(@Controller/@Service/@Reposity/@Component ) ----局限于我们自己创建的类
- @Bean(导入的第三方包里面的组件)
- @Import(快速给容器中导入一个组件)
下面我们来使用@import注解给容器中快速导入一个组件。
7.1 创建两个类
public class Red {
}
public class Color {
}
7.2 使用注解快速给容器中导入组件
组件的id默认是组件的全类名。
7.3 测试
//抽取出一个通用的打印配置类中所有组件的方法
public void printBeans(AnnotationConfigApplicationContext applicationContext) {
String[] definitionNames = applicationContext.getBeanDefinitionNames();