spring注解开发

前言

此笔记为尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)学习笔记

1、课程安排

f9cbe4d23ca660544f7cc71b15a8d081

根据上面这张脑图,我把整个专栏分成了三个大的部分,分别是:容器、扩展原理以及Web。

1.1、容器

容器作为整个专栏的第一大部分,内容包括:

  • AnnotationConfigApplicationContext
  • 组件添加
  • 组件赋值
  • 组件注入
  • AOP
  • 声明式事务

1.2、扩展原理

扩展原理作为整个专栏的第二大部分,内容包括:

  • BeanFactoryPostProcessor
  • BeanDefinitionRegistryPostProcessor
  • ApplicationListener
  • Spring容器创建过程

1.3、Web

Web作为整个专栏的第三大部分,内容包括:

  • servlet3.0

  • 异步请求

这部分,其实就是SpringMVC,在这个部分中,我们会重点来说异步请求。

2、配置文件开发

2.1、导入Spring-context依赖包

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>

2.2、编写Spring配置文件

在 resources 目录下创建 application.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">
</beans>

2.3、编写Person类

public class Person {
private String name;
private Integer age;
// 省略构造方法,get/set方法,toString方法
}

2.4、在Spring配置文件中创建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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="person" class="com.atguigu.bean.Person" scope="prototype" >
<property name="name" value="zhangsan"/>
<property name="age" value="18"/>
</bean>
</beans>

2.5、编写测试类

public class SpringTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
}

测试结果:

Person{name='zhangsan', age=18}

3、注解开发简单案例

3.1、创建配置类

在方法上面加上@Bean注解后,Spring会以方法返回类型作为组件的类型,方法名作为组件的 id

当向@Bean中添加参数时,默认添加的第一个参数是value,强制给组件赋值id

// 配置类等同于xml配置文件
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {
// 给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person") //给当前Bean取一个别名
public Person person(){
return new Person("李四", 20);
}
}

3.2、测试代码

@Test
public void test01() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
System.out.println(person);
}

测试结果:

Person [name=李四, age=11]

4、包扫描

只要标注了@Controller、@Service、@Repository、@Component的,都会被扫描加入到容器里

注意:配置类自身也会被扫描到容器中,如果存在多个配置类,则多个配置类里的所有bean对象都会被扫描进容器中

4.1、配置文件包扫描

新建配置文件:application.xml,可以不用在XML文件中配置包扫描(如下),直接在配置类加上相关注解也可以实现(如下面的4.2)

<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component -->
<context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan>
</beans>

4.2、注解扫描

数据准备

package com.atguigu.controller;
import org.springframework.stereotype.Controller;
@Controller
public class PersonController {
}
/////////////////////////////////////
package com.atguigu.dao;
import org.springframework.stereotype.Repository;
@Repository
public class personDao {
}
//////////////////////
package com.atguigu.service;
import org.springframework.stereotype.Service;
@Service
public class personService {
}

image-20220619175424482

在配置类上添加注解@ComponentScan

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu") //相当于4.1中application.xml配置的包扫描
public class MainConfig {
// ...
}

测试

@Test
public void test02() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = applicationContext.getBeanDefinitionNames(); //获取容器中所有组件的名字
for (String name : names) {
System.out.println(name);
}
}

4.3、排除扫描

Filter[] excludeFilters() default {};

注解排除excludeFilters的返回类型为Filter[]

Filter 的排除类型 FilterType 有一下几种:

  • ANNOTATION:按注解排除,常用
  • ASSIGNABLE_TYPE:按类型排除,常用
  • ASPECTJ:按AspectJ 类型模式表达式排除,不常用
  • REGEX:按正则表达式排除
  • CUSTOM:自定义排除

下面以ANNOTATION按注解排除来演示:

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
public class MainConfig {
// ...
}

4.4、指定扫描

扫描的时候只需要包含哪些组件,编写方式和excludeFilters一样

注意要关闭默认的扫描过滤器

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
}, useDefaultFilters = false)
public class MainConfig {
// ...
}

在 jdk8 之后可以写多个@ComponentScan,写法如下:

@Configuration // 告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value = "com.atguigu", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = { Controller.class,
Service.class }) }, useDefaultFilters = false),
@ComponentScan(...)
})
public class MainConfig {
// ...
}

4.5、按类型扫描

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu", includeFilters = {
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookController.class})
}, useDefaultFilters = false)
public class MainConfig {
// ...
}

4.6、自定义扫描

4.6.1、编写TypeFilter的实现类

public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader 读取到当前正在扫描类的信息
* @param 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);
return className.contains("er");
}
}

4.6.2、修改配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu", includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
}, useDefaultFilters = false)
public class MainConfig {
// ...
}

5、@scope注解

作用:调整实例的作用域

5.1、默认单实例

5.1.1、编写配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
public class MainConfig2 {
@Bean("person")
public Person person() {
return new Person("张三", 25);
}
}

5.1.2、编写测试

@Test
public void test2() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Person bean = applicationContext.getBean(Person.class);
Person bean2 = applicationContext.getBean(Person.class);
System.out.println(bean.hashCode());
System.out.println(bean2.hashCode());
}

输出结果:

com.atguigu.config.MainConfig2
com.atguigu.config.MyTypeFilter
com.atguigu.controller.BookController
com.atguigu.bean.Person
404214852
404214852

可以看到 bean 和 bean2 是同一个实例

5.2、@scope参数

  • ConfigurableBeanFactory.SCOPE_PROTOTYPE:多实例
  • ConfigurableBeanFactory.SCOPE_SINGLETON:单实例
  • org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST
  • org.springframework.web.context.WebApplicationContext.SCOPE_SESSION

后两个需要导入 Spring web 的jar包,且基本不用,只讨论前两个

ConfigurableBeanFactory中有两个常量:

// 单实例,默认值,在IOC容器启动时就会调用方法创建对象,以后每次获取直接从容器中拿(map.get())
String SCOPE_SINGLETON = "singleton";
// 多实例,在使用getBean()获取bean对象时才会创建
String SCOPE_PROTOTYPE = "prototype";

WebApplicationContext中的两个常量

// 同一次请求创建一个实例
String SCOPE_REQUEST = "request";
// 同一个session创建一个实例
String SCOPE_SESSION = "session";

5.3、测试多实例

5.3.1、修改配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
public class MainConfig2 {
@Bean("person")
@Scope("prototype")
public Person person() {
return new Person("张三", 25);
}
}

5.3.2、测试代码

@Test
public void test2() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Person bean = applicationContext.getBean(Person.class);
Person bean2 = applicationContext.getBean(Person.class);
System.out.println(bean.hashCode());
System.out.println(bean2.hashCode());
}

测试结果:

com.atguigu.config.MainConfig2
com.atguigu.config.MyTypeFilter
com.atguigu.controller.BookController
com.atguigu.bean.Person
961712517
1928931046

可以看到两个 bean 的哈希值不同

5.4、懒加载

针对单实例设置,因为单实例是在容器启动时创建对象

懒加载就是不让单实例在容器启动时马上创建,在第一次获取Bean时创建对象才初始化。

只需要在方法上添加注解@Lazy

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
public class MainConfig2 {
@Bean("person")
@Lazy
public Person person() {
return new Person("张三", 25);
}
}

6、@Conditional注解(重点)

按照一定的条件进行判断,满足条件给容器中注册Bean

@Conditional源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
// 传入一个Condition数组
Class<? extends Condition>[] value();
}

Condition接口源码:

@FunctionalInterface
public interface Condition {
/**
* ConditionContext:判断条件能使用的上下文环境
* AnnotatedTypeMetadata:注释信息
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

6.1、业务场景

如果是linux系统则注册linus,如果是windows系统则注册bill Gates

6.2、编写WindowsCondition

编写windows条件,需要实现接口Condition,重写matches方法

public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否是Windows系统
// 1. 获取到IOC使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 3. 获取运行环境
Environment environment = context.getEnvironment();
// 4. 获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// 容器中是否包含person,判断容器中的bean注册情况,也可以给容器中注册bean
boolean person = registry.containsBeanDefinition("person");
// 运行环境是否是Windows
String property = environment.getProperty("os.name");
assert property != null;
return property.contains("Windows");
}
}

6.3、编写LinuxCondition

public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
assert property != null;
return property.contains("linux");
}
}

6.4、编写配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
public class MainConfig3 {
@Bean("bill")
@Conditional({WindowsCondition.class})
public Person person() {
return new Person("bill gates", 62);
}
@Bean("linus")
@Conditional({LinuxCondition.class})
public Person person2() {
return new Person("linus", 48);
}
}

6.5、编写测试

@Test
public void test3() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
System.out.println(beansOfType);
}

测试结果:

{bill=Person{name='bill gates', age=62}}

当@Conditional配置在类上时,表示只有满足条件时,这个配置类配置的所有bean才会生效

7、组件注册方法

  1. 包扫描 + 组件标注注解(@Controller、@Service、@Repository、@Component),这种方式只能注册自己写的类

  2. 在配置类中添加@Bean,可以注册第三方包的类

  3. @Import

    1)、在配置类的类名上面添加@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名

    2)、ImportSelector:返回需要导入的组件的全类名数组;

    3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中

    4)、使用Spring提供的FactoryBean(工厂Bean)

    ​ 工厂bean获取的是 调用getObject创建的对象

    ​ 如果想要拿到colorFactoryBean对象,则需要在传入的id前加一个&标识

7.1、@Import(重点)

7.1.1、编写配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
@Import({Color.class, Red.class}) // 导入组件,id默认是组件的全类名
public class MainConfig3 {}

7.1.2、编写测试

@Test
public void test4() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}

测试结果:

...
com.atguigu.bean.Color
com.atguigu.bean.Red

7.2、ImportSelector

7.2.1、编写MyImportSelector

// 自定义逻辑,返回需要导入的组件
public class MyImportSelector implements ImportSelector {
// 返回值就是要导入到容器中的组件的全类名
// AnnotationMetadata:当前标注@Import注解类的所有信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.atguigu.bean.Blue", "com.atguigu.bean.Yellow"};
}
}

7.2.2、修改配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
@Import({Color.class, Red.class, MyImportSelector.class}) // 导入组件,id默认是组件的全类名
public class MainConfig3 {}

7.2.3、测试

@Test
public void test4() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}

测试结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig3
mainConfig
mainConfig2
personController
personDao
personService
person
myTypeFilter
bill
com.atguigu.bean.Color
com.atguigu.bean.Red
com.atguigu.bean.Blue
com.atguigu.bean.Yellow

7.3、ImportBeanDefinitionRegistrar

7.3.1、编写MyImportBeanDefinitionRegistrar

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:注册类
* 把所有需要添加到容器中的bean,调用BeanDefinitionRegistry.registerBeanDefinition手动注册
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 查询容器中是否存在red组件
boolean red = registry.containsBeanDefinition("com.atguigu.bean.Red");
if (red) {
// 指定bean的定义信息(bean的类型,bean的scope)
BeanDefinition beanDefinition = new RootBeanDefinition(Purple.class);
// 第一个参数指定bean的id
registry.registerBeanDefinition("purple", beanDefinition);
}
}
}

7.3.2、修改配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig3 {}

7.3.3、测试

@Test
public void test4() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}

测试结果:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig3
mainConfig
mainConfig2
personController
personDao
personService
person
myTypeFilter
bill
com.atguigu.bean.Color
com.atguigu.bean.Red
com.atguigu.bean.Blue
com.atguigu.bean.Yellow
purple

7.4、FactoryBean

FactoryBean是一个接口,我们需要自己实现

7.4.1、编写ColorFactoryBean

public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
// 返回是否为单例,如果为false,则每次创建时调用getObject()方法
@Override
public boolean isSingleton() {
return true;
}
}

7.4.2、编写配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
public class MainConfig4 {
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}

7.4.3、测试

@Test
public void test5() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
// 工厂bean获取的是 调用getObject创建的对象
Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass());
// 如果想要拿到colorFactoryBean对象,则需要在传入的id前加一个&标识
Object colorFactoryBean2 = applicationContext.getBean("&colorFactoryBean");
System.out.println(colorFactoryBean2.getClass());
}

测试结果:

class com.atguigu.bean.Color
class com.atguigu.bean.ColorFactoryBean

8、Bean生命周期

容器管理bean的生命周期:创建对象(调用构造器) ----> 初始化 -----> 销毁

我们可以自定义初始化和销毁方法

构造(对象创建)

  • 单实例:在容器启动的时候创建对象
  • 多实例:在每次获取的时候创建对象

初始化

  • 对象完成创建,并赋值好后,调用初始化方法

销毁

  • 单实例:在容器关闭的时候销毁
  • 多实例:容器不会管理这个bean,所以不会调用销毁方法

初始化和销毁方法:

  1. 通过@Bean注解指定init-method和destroy-method
  2. 实现InitializingBean和DisposableBean接口,重写里面的destroy和afterPropertiesSet方法
  3. 使用@PostConstruct和@PreDestroy注解
  4. BeanPostProcessor:接口,bean后置处理器,在bean初始化前后进行一些处理

8.1、自定义初始化和销毁方法

通过@Bean注解指定init-method和destroy-method

8.1.1、创建Car类

public class Car {
public Car() {
System.out.println("car构造方法");
}
public void init() {
System.out.println("car初始化方法");
}
public void destroy() {
System.out.println("car销毁方法");
}
}

8.1.2、创建配置文件

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu")
public class MainConfig5 {
@Bean(initMethod = "init", destroyMethod = "destroy")
public Car car() {
return new Car();
}
}

8.1.3、测试

@Test
public void test6() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig5.class);
applicationContext.close();
}

测试结果:

car构造方法
car初始化方法
car销毁方法

8.2、InitializingBean和DisposableBean

通过让bean实现InitializingBean接口来定义初始化逻辑

通过让bean实现DisposableBean接口来定义销毁逻辑

InitializingBean中有一个方法afterPropertiesSet,该方法在bean创建并赋值后调用

8.2.1、创建Cat类

public class Cat implements InitializingBean, DisposableBean {
public Cat() {
System.out.println("创建猫对象");
}
@Override
public void destroy() throws Exception {
System.out.println("销毁猫对象");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化猫对象");
}
}

8.2.2、创建配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu.bean")
public class MainConfig6 {
@Bean
public Cat cat() {
return new Cat();
}
}

8.2.3、测试

@Test
public void test7() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig6.class);
applicationContext.close();
}

测试结果:

创建猫对象
初始化猫对象
销毁猫对象

8.3、@PostConstruct和@PreDestroy

@PostConstruct:在bean创建完成并且属性赋值完成后进行初始化

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}

@PreDestroy:在容器销毁bean之前执行

@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}

8.3.1、创建Dog类

public class Dog {
public Dog() {
System.out.println("创建狗对象");
}
@PostConstruct
public void init() {
System.out.println("初始化狗对象");
}
@PreDestroy
public void destroy() {
System.out.println("销毁狗对象");
}
}

8.3.2、配置类

@Configuration // 告诉Spring这是一个配置类
//@ComponentScan(value = "com.atguigu.bean")
public class MainConfig7 {
@Bean
public Dog dog() {
return new Dog();
}
}

8.3.3、测试

@Test
public void test8() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig7.class);
applicationContext.close();
}

测试结果:

创建狗对象
初始化狗对象
销毁狗对象

8.4、BeanPostProcessor

bean的后置处理器,在bean初始化前后做一些处理工作

BeanPostProcessor接口中有两个方法:

  • postProcessBeforeInitialization:在初始化之前执行
  • postProcessAfterInitialization:在初始化之后执行

8.4.1、编写MyBeanPostProcessor

@Component //将后置处理器加入到容器中
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* @param bean 刚创建好的实例
* @param beanName 实例的id
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("id为" + beanName + "的bean对象:" + bean + "执行postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("id为" + beanName + "的bean对象:" + bean + "postProcessAfterInitialization");
return bean;
}
}

8.4.2、配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu.bean")
public class MainConfig7 {
@Bean
public Dog dog() {
return new Dog();
}
}

8.4.3、测试

@Test
public void test8() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig7.class);
applicationContext.close();
}

测试结果:

id为mainConfig7的bean对象:com.atguigu.config.MainConfig7$$EnhancerBySpringCGLIB$$aef15d18@7c469c48执行postProcessBeforeInitialization
id为mainConfig7的bean对象:com.atguigu.config.MainConfig7$$EnhancerBySpringCGLIB$$aef15d18@7c469c48postProcessAfterInitialization
创建狗对象
id为dog的bean对象:com.atguigu.bean.Dog@1534f01b执行postProcessBeforeInitialization
初始化狗对象
id为dog的bean对象:com.atguigu.bean.Dog@1534f01bpostProcessAfterInitialization
销毁狗对象

8.4.4、BeanPostProcessor执行过程

  1. 执行populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值

  2. 开始initializeBean初始化bean

    1. 先执行applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName),遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization
    2. 然后执行invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
    3. 最后执行applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

Spring底层对BeanPostProcessor的使用:bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async ....

9、@Value注解

使用@Value赋值,赋值方法:

  1. 直接在@Value中写基本数值
  2. 使用SpEL表达式:#{}
  3. 使用${}:取出配置文件( xxx.properties )中的值(在运行环境变量里的值)

9.1、修改Person类

public class Person {
@Value("xjhqre")
private String name;
@Value("#{20-2}")
private Integer age;
@Value("${person.email}")
private String email;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}

9.2、配置类

在类路径下新建配置文件person.properties

person.email=126@qq.com
//用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
@PropertySource(value = {"classpath:/person.properties"})
@ComponentScan(value = "com.atguigu.bean")
public class MainConfig9 {
@Bean
public Person person() {
return new Person();
}
}

9.3、测试类

@Test
public void test9() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig9.class);
Person person = applicationContext.getBean(Person.class);
System.out.println(person);
// 也可以使用环境变量取出
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("person.email");
System.out.println(property);
}

测试结果:

Person2 [name=张三, age=18, email=126@qq.com]
126@qq.com

10、自动装配

Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值

10.1、@AutoWired

@Autowired:自动注入:

​ 1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值

​ 2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找 applicationContext.getBean("bookDao")

​ 3)、@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名

​ 4)、自动装配默认一定要将属性赋值好,没有就会报错; 可以使用@Autowired(required=false);

​ 5)、@Primary:让Spring进行自动装配的时候,默认使用首选的bean; 也可以继续使用@Qualifier指定需要装配的bean的名字

给属性自动注入值

默认优先按照类型去容器中找对应的组件,找到就赋值。

如果该类型的组件有多个,再将属性名作为组件的id去容器中查找

@AutoWired可以在构造器、参数、方法、属性上标注

自动装配默认要求装配的属性一定要存在容器中,没有就会报错;可以使用@Autowired(required=false);

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}

10.1.1、required属性

@AutoWired可以给required属性赋值,类型为boolean,默认为true。表示该组件是否必须。

当required=false时,表示这个组件不是必须的,在调用getBean方法时如果找不到对应的组件时不会直接抛出异常,而是返回一个null

@Service
public class PersonService{
@Autowired(required=false)
PersonDao personDao
}

10.1.2、标注在方法上

标注在方法上,Spring容器创建当前对象,就会调用方法,完成赋值

方法使用的参数,自定义类型的值从IOC容器中获取

@Autowired
public void setCar(Car car) {
this.car = car;
}

@Bean标注的方法创建对象的时候,方法参数的值从容器中获取

// 参数car会从IOC容器中获取,可以省略Car前的@Autowired
@Bean
public Color color(@Autowired Car car) {
return new Color();
}
等同于
@Bean
public Color color(Car car) {
return new Color();
}

10.1.3、标注在构造器上

默认加载IOC容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作

我们可以在有参构造函数上标注@Autowired,让IOC容器创建组件时调用该类的有参构造方法

如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略

@Autowired
public Boss(Car car) {
this.car = car;
}

10.1.4、标注在参数上

public void setCar(@Autowired Car car) {
this.car = car;
}
等同于
@Autowired
public void setCar(Car car) {
this.car = car;
}
-----------------------------------------
@Autowired
public Boss(Car car) {
this.car = car;
}
等同于
public Boss(@Autowired Car car) {
this.car = car;
}

10.2、@Qualifier

使用@Qualifier指定需要装配的组件的id,而不是用属性名

@Qualifier需要和@AutoWired一起使用

10.3、@Primary

该注解可以让Spring进行自动装配的时候,默认使用首选的bean

也可以继续使用@Qualifier指定需要装配的bean的名字

无论是否有依赖注入,@Primary标注的bean都会被容器创建

/**
* 指示当多个候选者有资格自动装配单值依赖项时,应优先考虑 bean。如果候选中恰好存在一个“主”bean,则它将是自动装配的值。
* 这个注解在语义上等同于 Spring XML 中<bean>元素的primary属性。
* 可用于任何直接或间接使用@Component注释的类或使用Bean注释的方法。
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
@Configuration
@ComponentScan({"com.atguigu.service","com.atguigu.dao",
"com.atguigu.controller","com.atguigu.bean"})
public class MainConifgOfAutowired {
@Primary //如果容器中有多个BookDao类型的组件,如果需要往其他类注入该对象时,首选这个带有@Primary注解的组件
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}
}

10.4、@Resource

Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

作用:可以和@Autowired一样实现自动装配功能,但默认是按组件名称进行装配;可以给属性name赋值,自定义组件名称

@Resource(name="bookDao2")
private BookDao bookDao;

但该注解没有支持@Primary、@Autowired(require=false)的功能

10.5、@Inject

需要导入javax.inject依赖

和@Autowired功能一样,但没有required=false的功能

<!-- https://mvnrepository.com/artifact/javax.inject/javax.inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Inject
private BookDao bookDao;

11、使用Spring容器底层组件

自定义组件想要使用Spring容器底层的一些组件(ApplicationContext、BeanFactory),需要自定义组件实现xxxAware接口

在创建对象的时候,会调用接口规定的方法,注入相关的组件

Aware的子接口

7e2adad964dca3330bff5a7583021c5f

下面我们来自定义一个类使用Spring底层的组件

11.1、编写Red类

@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
// 获取IOC容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("IOC容器:" + applicationContext);
this .applicationContext = applicationContext;
}
// 获取当前bean对象的名称
@Override
public void setBeanName(String name) {
System.out.println("当前bean的名字:" + name);
}
// 解析String语句中的占位符 $ 或 #
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String s = resolver.resolveStringValue("你好${os.name}, 我是#{20*18}");
System.out.println("解析的字符串:" + s);
}
}

11.2、配置类

@Configuration // 告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu.bean")
public class MainConfig10 {
}

11.3、测试

@Test
public void test10() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig9.class);
}

测试结果:

当前bean的名字:red
解析的字符串:你好Windows 10, 我是360
IOC容器:org.springframework.context.annotation.AnnotationConfigApplicationContext@7c53a9eb: startup date [Mon Jun 20 16:11:17 CST 2022]; root of context hierarchy

每一个xxxAware都有一个对应的xxxAwareProcessor,用来处理相关逻辑

把Spring底层一些组件注入到自定义的Bean中;

xxxAware:对应xxxProcessor;

​ ApplicationContextAware==》ApplicationContextAwareProcessor;

12、@Profile注解

@Profile注解是Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能

例子:在不同环境下使用不同的数据源,在开发环境下使用A数据源,在测试环境下使用B数据源,在生产环境下使用C数据源

需要导入c3p0依赖C3p0:JDBC DataSources/Resource Pools和数据库驱动依赖MySQL Connector

12.1、编写db.properties

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=abc123

12.2、编写配置类

@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定的话在任何环境下都能注册这个组件

  • 加了环境标识的bean,只有在这个环境被激活的时候才能注册到容器中,默认环境为default,即@Profile("default")
  • @Profile注解写在类上时,只有当指定的环境被激活时,整个类才会被注册
  • 没有标注环境标识的bean在,任何环境下都是加载的;
@Configuration // 告诉Spring这是一个配置类
@PropertySource("classpath:/db.properties")
public class MainConfig11 implements EmbeddedValueResolverAware {
@Value("${jdbc.username}")
private String user;
private StringValueResolver valueResolver;
// 测试数据库
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${jdbc.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
String driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}");
dataSource.setDriverClass(driverClass);
return dataSource;
}
// 开发数据库
@Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${jdbc.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/atguigu?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
String driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}");
dataSource.setDriverClass(driverClass);
return dataSource;
}
// 生产数据库
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${jdbc.password}")String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
String driverClass = valueResolver.resolveStringValue("${jdbc.driverClassName}");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
}
}

12.3、环境激活方式

激活运行环境方式:

  1. 命令行方式,在虚拟机选项中输入:-Dspring.profiles.active=test

    208d1e00c1600928ccc5a568b5ea068d

  2. 使用代码方式:

    1. 使用无参构造器创建application,AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    2. 设置需要激活的环境:applicationContext.getEnvironment().setActiveProfiles("test", "dev");
    3. 注册主配置类:applicationContext.register(MainConfig11.class);
    4. 刷新容器:applicationContext.refresh();

12.4、测试

测试中使用代码方式激活环境

@Test
public void test11() {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
//1、创建一个applicationContext
//2、设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("dev");
//3、注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//4、启动刷新容器
applicationContext.refresh();
String[] dataSources = applicationContext.getBeanNamesForType(DataSource.class);
for (String dataSource : dataSources) {
System.out.println(dataSource);
}
}

测试结果:

testDataSource
devDataSource

13、AOP

AOP:【动态代理】,指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;

AOP使用步骤:

  1. 导入aop模块;Spring AOP:(spring-aspects)
  2. 定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
  3. 定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
  4. 给切面类的方法标注何时何地运行(通知注解)
  5. 将切面类和业务逻辑类(目标方法所在类)都加入到容器中
  6. 必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
  7. 给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】(相当于在xml中配置<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

通知方法说明:

  • 前置通知(@Before):logStart:在目标方法(div)运行之前运行
  • 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
  • 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
  • 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
  • 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())

*表示所有方法,…表示任意类型的形参

13.1、AOP测试

13.1.1、编写被增强类

public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator...div...");
return i / j;
}
}

13.1.2、编写切面类

切面表达式 "execution(public int com.atguigu.aop.MathCalculator.*(..))"中 *表示所有方法,..表示任意类型的形参。

JoinPoint joinPoint必须写在形参的第一位。

joinPoint可以获取被增强方法的签名,如方法名、参数列表、方法的返回值、方法抛出的异常等等

@Aspect //告诉spring这是一个切面类
public class LogAspects {
//抽取公共的切入点表达式
//1、本类引用,只需要引入方法名即可
//2、如果其他的切面引用当前切入点表达式,需要引用方法的全类名:"com.atguigu.aop.LogAspects.pointCut()"
@Pointcut("execution(public int com.atguigu.aop.MathCalculator.*(..))")
public void pointCut() {
}
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("" + joinPoint.getSignature().getName() + "运行。。。@Before:参数列表是:{" + Arrays.asList(args) + "}");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println("" + joinPoint.getSignature().getName() + "结束。。。@After");
}
//JoinPoint一定要出现在参数表的第一位
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println("" + joinPoint.getSignature().getName() + "正常返回。。。@AfterReturning:运行结果:{" + result + "}");
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println("" + joinPoint.getSignature().getName() + "异常。。。异常信息:{" + exception + "}");
}
}

13.1.4、编写配置类

@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAop {
//业务逻辑类加入容器中
@Bean
public MathCalculator calculator() {
return new MathCalculator();
}
//切面类加入到容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}

13.1.5、测试

@Test
public void test12() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAop.class);
MathCalculator bean = applicationContext.getBean(MathCalculator.class);
bean.div(1, 1);
}

测试结果:

div运行。。。@Before:参数列表是:{[1, 1]}
MathCalculator...div...
div正常返回。。。@AfterReturning:运行结果:{1}
div结束。。。@After

13.1.6、AOP使用总结

AOP使用的三个步骤:

  1. 将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
  2. 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
  3. 开启基于注解的aop模式;@EnableAspectJAutoProxy

13.2、AOP原理

13.2.1、@EnableAspectJAutoProxy

查看@EnableAspectJAutoProxy注解信息:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 引入AspectJAutoProxyRegister.class对象
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// true——使用CGLIB基于类创建代理;false——使用java接口创建代理
boolean proxyTargetClass() default false;
// 是否通过aop框架暴露该代理对象,aopContext能够访问.
boolean exposeProxy() default false;
}

该注解引入了AspectJAutoProxyRegistrarAspectJAutoProxyRegistrar手动地给容器注册AnnotationAwareAspectJAutoProxyCreator组件

过程:

AspectJAutoProxyRegistrar中有一个registerBeanDefinitions方法,在该方法中使用

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);语句注册了一个名为internalAutoProxyCreator的组件,该组件的类型为AnnotationAwareAspectJAutoProxyCreator

13.2.2、AnnotationAwareAspectJAutoProxyCreator分析

以下是AnnotationAwareAspectJAutoProxyCreator的层次结构:

1afa0cd9770adaf8bf16f135722b3b66

这里主要关注后置处理器和自动装备BeanFactory相关的方法:SmartInstantiationAwareBeanPostProcessor(后置处理器,在bean完成初始化前后所做的事情), BeanFactoryAware(自动装配BeanFactory).

接下来去AnnotationAwareAspectJAutoProxyCreator及其父类中寻找有关后置处理器和BeanFactory相关的方法

AbstractAutoProxyCreator:

  • setBeanFactory
  • postProcessAfterInitialization
  • postProcessBeforeInstantiation
  • postProcessProperties

AbstractAdvisorAutoProxyCreator:

  • setBeanFactory,在该方法中调用了initBeanFactory方法

AnnotationAwareAspectJAutoProxyCreator:

  • initBeanFactory

13.2.3、注册AnnotationAwareAspectJAutoProxyCreator流程

断点测试程序执行流程:

注册AnnotationAwareAspectJAutoProxyCreator的后置处理器的方法registerBeanPostProcessors(beanFactory)在IOC创建的refresh()方法中

registerBeanPostProcessors(beanFactory):注册bean的后置处理器来方便拦截bean的创建;

  1. 先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
  2. 给容器中加别的BeanPostProcessor
  3. 优先注册实现了PriorityOrdered接口的BeanPostProcessor
  4. 再给容器中注册实现了Ordered接口的BeanPostProcessor
  5. 注册没实现优先级接口的BeanPostProcessor;
  6. 注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;下面以创建internalAutoProxyCreator(类型为AnnotationAwareAspectJAutoProxyCreator)的后置处理器BeanPostProcessor步骤为例:
    1. 创建Bean的实例
    2. populateBean;给bean的各种属性赋值
    3. initializeBean:初始化bean步骤:
      1. invokeAwareMethods():处理Aware接口的方法回调
      2. applyBeanPostProcessorsBeforeInitialization():应用所有后置处理器的postProcessBeforeInitialization(),初始化前的逻辑
      3. invokeInitMethods();执行自定义的初始化方法
      4. applyBeanPostProcessorsAfterInitialization();执行所有后置处理器的postProcessAfterInitialization();初始化后的逻辑
    4. AnnotationAwareAspectJAutoProxyCreator的后置处理器创建成功,生成aspectJAdvisorsBuilder
  7. 把BeanPostProcessor注册到BeanFactory中:beanFactory.addBeanPostProcessor(postProcessor)

注册AnnotationAwareAspectJAutoProxyCreator的后置处理器流程图:

64af20e9ba169f5d19e97679aeab4fe6

13.2.4、AnnotationAwareAspectJAutoProxyCreator执行时机

我们来分析IOC创建方法refresh()中的finishBeanFactoryInitialization(beanFactory);:完成BeanFactory初始化工作;创建剩下的单实例bean

  1. 遍历获取容器中所有的Bean,依次创建对象getBean(beanName);getBean->doGetBean()->getSingleton()->

  2. 创建bean

    【AnnotationAwareAspectJAutoProxyCreator(InstantiationAwareBeanPostProcessor的实现类)在所有bean创建之前会有一个拦截,会调用postProcessBeforeInstantiation()】

    • 总体流程:先从缓存中获取当前bean:Object sharedInstance = getSingleton(beanName);,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建新的bean;只要创建好的Bean都会被缓存起来

    • 创建bean:createBean()

      首先调用resolveBeforeInstantiation(beanName, mbdToUse);解析BeforeInstantiation希望后置处理器在此能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续

      1. 后置处理器先尝试返回对象;bean = applyBeanPostProcessorsBeforeInstantiation(); 拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor,就执行postProcessBeforeInstantiation,如果不是则执行bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
      2. 若后置处理器返回对象失败,则会真正的去创建一个bean实例:doCreateBean(beanName, mbdToUse, args);

AnnotationAwareAspectJAutoProxyCreator执行时机流程图:

ae7e09b19d065cc8e5ba8063fbc04455

总结:

  • AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,它继承了InstantiationAwareBeanPostProcessor,会调用postProcessBeforeInstantiation()方法

  • AnnotationAwareAspectJAutoProxyCreator会在任何bean创建之前先尝试返回bean的实例,

  • BeanPostProcessor是在Bean对象创建完成初始化前后调用的

  • InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的

13.2.5、AnnotationAwareAspectJAutoProxyCreator作用

AnnotationAwareAspectJAutoProxyCreator的类型是InstantiationAwareBeanPostProcessor

  1. 每一个bean创建之前,调用postProcessBeforeInstantiation()
    1. 判断当前bean是否在advisedBeans中(里面保存了所有需要增强bean, 如MathCalculator)
    2. 判断当前bean是否是基础类型的,是否实现了Advice、Pointcut、Advisor、AopInfrastructureBean接口,或者是否是切面(是否标注了@Aspect注解)
    3. 判断是否需要跳过,获取候选的增强器(切面里面的通知方法)封装在List<Advisor> candidateAdvisors集合中,判断每一个增强器是否是 AspectJPointcutAdvisor 类型的,如果是则返回true,但我们的增强器是InstantiationModelAwarePointcutAdvisor类型,所以返回false
  2. 创建对象后,调用postProcessAfterInitialization,该方法返回一个包装bean,return wrapIfNecessary(bean, beanName, cacheKey)
    1. 调用getAdvicesAndAdvisorsForBean方法获取当前bean的所有增强器(通知方法),封装在集合Object[] specificInterceptors
      1. 找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的)
      2. 获取到能在bean使用的增强器。
      3. 给增强器排序
    2. 保存当前bean在advisedBeans
    3. 如果当前bean需要增强,调用createProxy创建当前bean的代理对象;
      1. 获取所有增强器(通知方法)
      2. 保存到proxyFactory
      3. 调用proxyFactory.getProxy创建代理对象:Spring自动决定,创建JdkDynamicAopProxy(config) jdk动态代理或ObjenesisCglibAopProxy(config) cglib的动态代理;
    4. 给容器中返回当前组件使用cglib增强了的代理对象;
    5. 以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;

AnnotationAwareAspectJAutoProxyCreator配置代理流程图:

432f6271c13989fef4137fb90b9dd1a7

13.2.6、拦截器链分析

容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);

  1. CglibAopProxy.intercept();拦截目标方法的执行
  2. 根据ProxyFactory对象获取将要执行的目标方法拦截器链;List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    1. List<Object> interceptorList保存所有拦截器 长度为5,里面有一个默认的ExposeInvocationInterceptor 和 4个增强器;
    2. 遍历所有的增强器,将其转为Interceptor:registry.getInterceptors(advisor);
    3. 将增强器转为List<MethodInterceptor>,如果是MethodInterceptor,直接加入到集合中;如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor;转换完成返回MethodInterceptor数组;
  3. 如果没有拦截器链,直接执行目标方法,拦截器链(其实每一个通知方法是被包装为方法拦截器而组成拦截器链,利用MethodInterceptor机制)
  4. 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入,创建一个 CglibMethodInvocation 对象,并调用 Object retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  5. 拦截器链的触发过程
    1. 如果没有拦截器执行 执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法;
    2. 链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;拦截器链的机制,保证通知方法与目标方法的执行顺序;

执行目标方法到获取拦截器链的流程图:

91964846a238420500fe343955ae3261

拦截器执行流程图:

a28d627c79a4c381daee2e8a9623737a

13.2.7、总结

  1. @EnableAspectJAutoProxy 开启AOP功能
  2. @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
  3. AnnotationAwareAspectJAutoProxyCreator是一个后置处理器
  4. 容器的创建流程:
    1. registerBeanPostProcessors注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象
    2. finishBeanFactoryInitialization初始化剩下的单实例bean
      1. 创建业务逻辑组件和切面组件
      2. AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
      3. 组件创建完之后,判断组件是否需要增强,如果需要则将切面的通知方法,包装成增强器(Advisor),给业务逻辑组件创建一个代理对象(cglib)
  5. 执行目标方法
    1. 代理对象执行目标方法
    2. CglibAopProxy.intercept()
      1. 得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
      2. 利用拦截器的链式机制,依次进入每一个拦截器进行执行;
      3. 正常执行:前置通知-》目标方法-》后置通知-》返回通知
        出现异常:前置通知-》目标方法-》后置通知-》异常通知

14、声明式事务

14.1、环境搭建

  1. 导入相关依赖:数据源、数据库驱动、Spring-jdbc模块(该模块可以简化对数据库的操作以及简化对事务的控制)
  2. 配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据

14.1.1、创建数据库

use ssmbuild;
DROP TABLE IF EXISTS `tbl_user`;
CREATE TABLE `tbl_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`age` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

14.1.2、编写UserDao

@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert() {
String sql = "INSERT INTO `tbl_user`(username,age) VALUES(?,?)";
String username = UUID.randomUUID().toString().substring(0, 5);
jdbcTemplate.update(sql, username, 19);
}
}

14.1.3、编写UserService

@Service
public class UserService {
@Autowired
private UserDao userDao;
public void insertUser() {
userDao.insert();
System.out.println("插入完成...");
}
}

14.1.4、编写配置类

@Configuration
@ComponentScan("com.atguigu.tx")
@PropertySource("classpath:/db.properties")
public class TxConfig implements EmbeddedValueResolverAware {
private StringValueResolver valueResolver;
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(valueResolver.resolveStringValue("${jdbc.username}"));
dataSource.setPassword(valueResolver.resolveStringValue("${jdbc.password}"));
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springtx?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
dataSource.setDriverClass(valueResolver.resolveStringValue("${jdbc.driverClassName}"));
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws Exception{
//Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器中找组件
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
}
}

14.1.5、测试

@Test
public void test13() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.insertUser();
}

14.2、事务测试

14.2.1、给方法标注@Transactional

给方法上标注 @Transactional 表示当前方法是一个事务方法;并在方法中设置异常

@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void insertUser() {
userDao.insert();
int i = 1 / 0;
System.out.println("插入完成...");
}
}

14.2.2、开启注解事务管理功能

在配置类标注@EnableTransactionManagement 开启基于注解的事务管理功能;

@Configuration // 告诉Spring这是一个配置类
@EnableTransactionManagement
@ComponentScan("com.atguigu.tx")
@PropertySource("classpath:/db.properties")
public class TxConfig implements EmbeddedValueResolverAware {
// ...
}

14.2.3、配置事务管理器

在配置类中加入事务管理器bean

//注册事务管理器在容器中
@Bean
public PlatformTransactionManager transactionManager() throws Exception{
return new DataSourceTransactionManager(dataSource());
}

14.2.4、测试

@Test
public void test13() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.insertUser();
}

测试结果:

控制台打印除 0 异常,数据库未增加新记录

14.3、事务源码分析

事务执行原理:

标注@EnableTransactionManagement,spring利用TransactionManagementConfigurationSelector给容器中会导入两个组件:AutoProxyRegistrarProxyTransactionManagementConfiguration

  1. AutoProxyRegistrar功能:

    给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;该组件利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;

  2. ProxyTransactionManagementConfiguration功能:

    给容器中注册事务增强器:

    1. 事务增强器要用事务注解的信息,利用AnnotationTransactionAttributeSource解析事务注解中的属性

    2. 事务拦截器:TransactionInterceptor:继承了MethodInterceptor接口,保存了事务属性信息

      在目标方法执行的时候,需要执行事务拦截器链

      1. 先获取事务相关的属性
      2. 再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger,最终会从容器中按照类型获取一个PlatformTransactionManager
      3. 执行目标方法,如果异常,获取到事务管理器,利用事务管理回滚操作;如果正常,利用事务管理器,提交事务

15、扩展原理

15.1、BeanFactoryPostProcessor

BeanPostProcessor:bean后置处理器,bean创建对象初始化前后进行拦截工作的

BeanFactoryPostProcessor:beanFactory的后置处理器:

  1. 在BeanFactory标准初始化之后调用,来定制和修改BeanFactory的内容
  2. 执行时机:所有的bean定义已经保存加载到beanFactory,但是bean的实例还未创建

BeanFactoryPostProcessor原理:

  1. ioc容器创建对象
  2. 执行refresh()方法中的invokeBeanFactoryPostProcessors(beanFactory);
  3. 直接在BeanFactory中找到所有类型是BeanFactoryPostProcessor的组件,并执行他们的方法
  4. 在初始化创建其他组件前面执行

15.1.1、编写Dog类

package com.atguigu.bean;
public class Blue {
public Blue() {
System.out.println("Blue......");
}
}

15.1.2、编写MyBeanFactoryPostProcessor

编写MyBeanFactoryPostProcessor方法,实现接口BeanFactoryPostProcessor,重写postProcessBeanFactory方法

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("执行postProcessBeanFactory");
int count = beanFactory.getBeanDefinitionCount();
String[] names = beanFactory.getBeanDefinitionNames();
System.out.println("当前BeanFactory中有" + count + " 个Bean");
System.out.println(Arrays.asList(names));
}
}

15.1.3、编写配置类

@Configuration
@ComponentScan("com.atguigu.ext")
public class ExtConfig {
@Bean
public Blue blue() {
return new Blue();
}
}

15.1.4、测试

@Test
public void test14() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
applicationContext.close();
}

测试结果:

执行postProcessBeanFactory
当前BeanFactory中有9 Bean
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
extConfig
myBeanFactoryPostProcessor
blue
Blue......

15.2、BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor

其优先于BeanFactoryPostProcessor执行;

执行时机:在所有bean定义信息将要被加载到BeanFactory,bean实例还未创建的时候

作用:利用BeanDefinitionRegistryPostProcessor给容器中再额外添加一些组件

BeanDefinitionRegistryPostProcessor执行流程:

  1. ioc创建对象
  2. refresh() --> invokeBeanFactoryPostProcessors(beanFactory);
  3. 从容器中获取到所有的BeanDefinitionRegistryPostProcessor组件
    1. 依次触发所有的postProcessBeanDefinitionRegistry()方法
    2. 再来触发postProcessBeanFactory()方法;
  4. 再来从容器中找到BeanFactoryPostProcessor组件;然后依次触发postProcessBeanFactory()方法

15.2.1、编写MyBeanDefinitionRegistryPostProcessor

其他配置类和测试类同上

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("执行MyBeanDefinitionRegistryPostProcessor...bean的数量:" + beanFactory.getBeanDefinitionCount());
}
//BeanDefinitionRegistry Bean定义信息的保存中心,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实例;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("执行postProcessBeanDefinitionRegistry...bean的数量:" + registry.getBeanDefinitionCount());
// 手动注册bean对象
// 方法一:RootBeanDefinition beanDefinition = new RootBeanDefinition(Cat.class);
// 方法二:
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Cat.class).getBeanDefinition();
registry.registerBeanDefinition("car", beanDefinition);
}
}

15.2.2、编写Cat类

public class Cat implements InitializingBean, DisposableBean {
public Cat() {
System.out.println("创建猫对象");
}
@Override
public void destroy() throws Exception {
System.out.println("销毁猫对象");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化猫对象");
}
}

15.2.3、测试

测试结果:

执行postProcessBeanDefinitionRegistry...bean的数量:9
执行MyBeanDefinitionRegistryPostProcessor...bean的数量:10
执行postProcessBeanFactory
当前BeanFactory中有10 Bean
创建狗对象
初始化狗对象
创建猫对象
初始化猫对象
销毁猫对象
销毁狗对象

结果分析:

  1. 先执行MyBeanDefinitionRegistryPostProcessor里的postProcessBeanDefinitionRegistry,在输出bean对象的数量后又创建了一个Cat类的bean
  2. 再执行MyBeanDefinitionRegistryPostProcessor里的postProcessBeanFactory方法。输出的bean数量加1
  3. 然后执行MyBeanFactoryPostProcessor里的postProcessBeanFactory方法
  4. 以上方法都执行完成后开始创建bean对象

16、事件监听

ApplicationListener:监听容器中发布的事件。事件驱动模型开发;

我们需要自己写一个监听器,该监听器必须实现ApplicationListener<ApplicationEvent>接口,用于监听ApplicationEvent 及其下面的子事件;

步骤:

  1. 写一个监听器(ApplicationListener实现类)来监听某个事件(ApplicationEvent及其子类)
  2. 把监听器加入到容器;
  3. 只要容器中有相关事件的发布,我们就能监听到这个事件,例如spring自动帮我们发布如下事件:
    • ContextRefreshedEvent:容器刷新完成(所有bean都完全创建)会发布这个事件;
    • ContextClosedEvent:关闭容器会发布这个事件;
  4. 也可以手动发布一个事件
    • applicationContext.publishEvent()

16.1、编写监听器并加入容器中

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
//当容器中发布此事件以后,方法触发
@Override
public void onApplicationEvent(ApplicationEvent event) {
// TODO Auto-generated method stub
System.out.println("收到事件:" + event);
}
}

16.2、测试

public void test14() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
});
applicationContext.close();
}

测试结果:

收到事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:04:16 CST 2022]
收到事件:SpringTest$1[source=我发布的事件]
收到事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:04:16 CST 2022]

16.3、源码分析

事件发布顺序:

  1. ContextRefreshedEvent事件
    1. 容器创建对象:refresh();
    2. finishRefresh();容器刷新完成会发布ContextRefreshedEvent事件
  2. 自己发布的事件执行
  3. 容器关闭会发布ContextClosedEvent

事件发布流程:

  1. 容器创建对象:refresh()--->finishRefresh()
  2. finishRefresh();
  3. publishEvent(new ContextRefreshedEvent(this));
    1. 获取事件的多播器(派发器):getApplicationEventMulticaster()
    2. multicastEvent派发事件
    3. 获取到所有的ApplicationListener:for (final ApplicationListener<?> listener : getApplicationListeners(event, type))
      1. 如果有Executor,可以支持使用Executor进行异步派发;
      2. 否则,同步的方式直接执行listener方法;invokeListener(listener, event);拿到listener回调onApplicationEvent方法;

事件多播器(派发器)创建流程:

  1. 容器创建对象:refresh()---》initApplicationEventMulticaster()
  2. initApplicationEventMulticaster():初始化ApplicationEventMulticaster
    1. 先去容器中找有没有id=“applicationEventMulticaster”的组件;
    2. 如果没有则直接创建this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);并且加入到容器中,我们就可以在其他监听器组件,自动注入到派发器applicationEventMulticaster中;

监听器创建流程:

具体流程:从容器中拿到所有的监听器,把他们注册到applicationEventMulticaster中;

  1. 容器创建对象:refresh()---》registerListeners()

  2. registerListeners();

    获取所有的监听器:String[] ListenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false)

    将监听器listener注册到派发器ApplicationEventMulticaster中:getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);

16.4、@EventListener

通过@EventListener注解,我们也可以用来监听事件

16.4.1、编写UserService类

@Service
public class UserService {
@EventListener(classes = {ApplicationEvent.class})
public void listen(ApplicationEvent event) {
System.out.println("UserService。。监听到的事件:" + event);
}
}

16.4.2、测试

@Test
public void test14() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
});
applicationContext.close();
}

测试结果:

UserService监听到的事件:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:37:36 CST 2022]
UserService监听到的事件:SpringTest$1[source=我发布的事件]
UserService监听到的事件:org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@6bc168e5, started on Wed Mar 02 15:37:36 CST 2022]

16.4.3、源码分析

spring使用EventListenerMethodProcessor处理器来解析方法上的@EventListener;其实现了SmartInitializingSingleton接口

SmartInitializingSingleton原理:

  1. ioc容器创建对象并refresh();
  2. finishBeanFactoryInitialization(beanFactory);初始化剩下的单实例bean;
    1. 先创建所有的单实例bean:getBean();
    2. 获取所有创建好的单实例bean,判断是否是SmartInitializingSingleton类型的;如果是就调用afterSingletonsInstantiated();

17、Spring容器创建过程

17.1、BeanFactory的创建及预准备工作

Spring容器的refresh()创建刷新:

  1. prepareRefresh():刷新前的预处理;
    1. initPropertySources()初始化一些属性设置;子类自定义个性化的属性设置方法;
    2. getEnvironment().validateRequiredProperties();检验属性的合法等
    3. earlyApplicationEvents= new LinkedHashSet<ApplicationEvent>();保存容器中的一些早期的事件;
  2. obtainFreshBeanFactory();获取BeanFactory;
    1. refreshBeanFactory();刷新创建BeanFactory,设置序列化ID;
    2. getBeanFactory();返回刚才GenericApplicationContext创建的BeanFactory对象;
    3. 将创建的BeanFactory(DefaultListableBeanFactory)返回
  3. prepareBeanFactory(beanFactory);BeanFactory的预准备工作,对BeanFactory进行一些设置;
    1. 设置BeanFactory的类加载器、支持表达式解析器…
    2. 添加部分BeanPostProcessor【ApplicationContextAwareProcessor】
    3. 设置忽略的自动装配的接口:EnvironmentAwareEmbeddedValueResolverAware
    4. 注册可以解析的自动装配;我们能直接在任何组件中自动注入:BeanFactoryResourceLoaderApplicationEventPublisherApplicationContext
    5. 添加BeanPostProcessor【ApplicationListenerDetector】
    6. 添加编译时的AspectJ;
    7. 给BeanFactory中注册一些能用的组件:environment【ConfigurableEnvironment】systemProperties【Map<String, Object>】systemEnvironment【Map<String, Object>】
  4. postProcessBeanFactory(beanFactory);BeanFactory准备工作完成后进行的后置处理工作;
    1. 子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置

BeanFactory的创建及预准备工作流程图:

109b0b187ab1915688af3970603bfbf4

17.2、执行BeanFactory后置处理器流程

invokeBeanFactoryPostProcessors(beanFactory);执行BeanFactoryPostProcessor;
BeanFactoryPostProcessor:BeanFactory的后置处理器。在BeanFactory标准初始化之后执行的;

  1. 获取所有的BeanDefinitionRegistryPostProcessor
  2. 先获取实现了PriorityOrdered优先级接口的BeanDefinitionRegistryPostProcessor,将其存入currentRegistryProcessors数组中
  3. sortPostProcessors(currentRegistryProcessors, beanFactory);对所有BeanDefinitionRegistryPostProcessor进行排序
  4. registryProcessors.addAll(currentRegistryProcessors);注册所有BeanDefinitionRegistryPostProcessor
  5. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());执行所有BeanDefinitionRegistryPostProcessor
  6. 在获取实现了Ordered优先级接口的BeanDefinitionRegistryPostProcessor,之后的步骤同上面的 3 ~ 5
  7. 最后获取没有实现任何优先级接口的BeanDefinitionRegistryPostProcessor,之后的步骤同上面的 3 ~ 5
  8. 处理完BeanDefinitionRegistryPostProcessor之后才会处理BeanFactoryPostProcessor,处理过程和BeanDefinitionRegistryPostProcessor相同

17.3、注册Bean后置处理器

registerBeanPostProcessors(beanFactory);注册BeanPostProcessor,Bean的后置处理器
不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的

  1. 获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级
  2. 先注册PriorityOrdered优先级接口的BeanPostProcessor,把每一个BeanPostProcessor;添加到BeanFactory中,beanFactory.addBeanPostProcessor(postProcessor);
  3. 再注册Ordered接口的
  4. 接着注册没有实现任何优先级接口的
  5. 最终注册 MergedBeanDefinitionPostProcessor;
  6. 注册一个 ApplicationListenerDetector;来在Bean创建完成后检查是否是ApplicationListener,如果是则调用 applicationContext.addApplicationListener((ApplicationListener<?>) bean);方法将其添加进容器中

17.4、国际化功能

initMessageSource();初始化MessageSource组件(做国际化功能;消息绑定,消息解析)

  1. 获取BeanFactory
  2. 看容器中是否有id为messageSource的,类型是MessageSource的组件
    如果有赋值给messageSource,如果没有自己创建一个DelegatingMessageSource;
    MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取;
  3. 把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource;
    beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale);

17.5、初始化事件派发器

initApplicationEventMulticaster();初始化事件派发器;

  1. 获取BeanFactory
  2. 从BeanFactory中获取类型为ApplicationEventMulticasterapplicationEventMulticaster
  3. 如果上一步没有获取成功;则创建一个SimpleApplicationEventMulticaster
  4. 将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入

17.6、初始化子类中的bean

onRefresh();留给子容器(子类)

  1. 子类重写这个方法,在容器刷新的时候可以自定义逻辑;

17.7、注册监听器

registerListeners();给容器中将所有项目里面的ApplicationListener注册进来;

  1. 从容器中拿到所有的ApplicationListener
  2. 将每个监听器添加到事件派发器中;
    getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
  3. 派发之前步骤产生的事件;

17.8、完成bean工厂初始化,实例化bean

finishBeanFactoryInitialization(beanFactory);初始化所有剩下的单实例bean;

  1. beanFactory.preInstantiateSingletons();实例化后剩下的单实例bean

    获取容器中的所有Bean,依次进行实例化和创建对象

    1. 获取容器中的所有Bean,依次进行实例化和创建对象
    2. 获取Bean的定义信息;RootBeanDefinition
    3. 如果Bean不是抽象的,是单实例的,是懒加载:
      1. 判断是否是FactoryBean(是否是实现FactoryBean接口的Bean,FactoryBean是创建对象的模板;)
      2. 如果不是工厂Bean。则利用getBean(beanName);创建对象
        getBean(beanName) —> doGetBean(name, null, null, false);
        1. getSingleton(beanName);先获取缓存中保存的单实例Bean。如果能获取到说明这个Bean之前被创建过(所有创建过的单实例Bean都会被缓存起来),从private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);获取的
        2. 缓存中获取不到,开始Bean的创建对象流程;
        3. 标记当前bean已经被创建
        4. 获取Bean的定义信息;
        5. 获取当前Bean依赖的其他Bean,如果有按照getBean()把依赖的Bean先创建出来;
        6. 启动单实例Bean的创建流程;
          1. createBean(beanName, mbd, args);
          2. Object bean = resolveBeforeInstantiation(beanName, mbdToUse);让BeanPostProcessor先拦截返回代理对象;
          3. 如果前面的InstantiationAwareBeanPostProcessor没有返回代理对象;调用4)
          4. Object beanInstance = doCreateBean(beanName, mbdToUse, args);创建Bean
            1. 【创建Bean实例】;createBeanInstance(beanName, mbd, args);利用工厂方法或者对象的构造器创建出Bean实例
            2. applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);调用MergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition(mbd, beanType, beanName);
            3. 【Bean属性赋值】populateBean(beanName, mbd, instanceWrapper);
              赋值之前
              1. 先拿到InstantiationAwareBeanPostProcessor后置处理器:postProcessAfterInstantiation();
              2. 再拿到InstantiationAwareBeanPostProcessor后置处理器:postProcessPropertyValues()
              3. 最后应用Bean属性的值;为属性利用setter方法等进行赋值;
            4. 【Bean初始化】initializeBean(beanName, exposedObject, mbd);
              1. 【执行Aware接口方法】invokeAwareMethods(beanName, bean);执行xxxAware接口的方法
              2. 【在初始化之前应用 Bean 后处理器】applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
              3. 【调用初始化方法】invokeInitMethods(beanName, wrappedBean, mbd);
                1. 是否是InitializingBean接口的实现;执行接口规定的初始化;
                2. 是否自定义初始化方法;
              4. 【初始化后应用 Bean 后处理器】applyBeanPostProcessorsAfterInitialization
            5. 注册Bean的销毁方法;
          5. 将创建的Bean添加到缓存中singletonObjects;
      3. 所有Bean都利用getBean创建完成以后;检查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就执行afterSingletonsInstantiated();

finishRefresh();完成BeanFactory的初始化创建工作;IOC容器就创建完成;

  1. initLifecycleProcessor();初始化和生命周期有关的后置处理器;默认从容器中找是否有lifecycleProcessor的组件【LifecycleProcessor】;如果没有new DefaultLifecycleProcessor();加入到容器;
  2. getLifecycleProcessor().onRefresh();拿到前面定义的生命周期处理器(BeanFactory),回调onRefresh()方法;
  3. publishEvent(new ContextRefreshedEvent(this));发布最终事件
  4. liveBeansView.registerApplicationContext(this);

17.9、Spring容器创建流程图

af05b51213fec0c7192b9038150a8d09

17.10、总结

  1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息;
    1. xml注册bean
    2. 注解注册Bean;@Service、@Component、@Bean、xxx
  2. Spring容器会在合适的时机创建这些Bean
    1. 用到这个bean的时候;利用getBean创建bean;创建好以后保存在容器中;大多数后置处理器都是在这一步创建好的
    2. 统一创建剩下所有的bean:finishBeanFactoryInitialization();
  3. 后置处理器;BeanPostProcessor
    1. 每一个bean创建完成,都会使用各种后置处理器来增强bean的功能,例如:
      1. AutowiredAnnotationBeanPostProcessor:处理自动注入
      2. AnnotationAwareAspectJAutoProxyCreator:来做AOP功能;
  4. 事件驱动模型
    1. ApplicationListener:事件监听;
    2. ApplicationEventMulticaster:事件派发:

18、Servlet3.0

18.1、测试

创建一个动态web工程spring-servlet3.0

jsp页面:

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>JSP - Hello World</title>
</head>
<body>
<h1><%="Hello World!"%>
</h1>
<br />
<a href="HelloServlet">Hello Servlet</a>
</body>
</html>

Servlet:

@WebServlet(name = "HelloServlet", value = "/HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("hello...");
}
}

运行结果:

image-20220623152009613

18.2、ServletContainerInitializer扫描包

Shared libraries(共享库) / runtimes pluggability(运行时插件能力)

Servlet容器启动时会扫描当前应用里面每一个jar包的ServletContainerInitializer的实现

使用方法:

  1. 编写ServletContainerInitializer的实现类MyServletContainerInitializer
  2. 在src目录下创建META-INF/services/javax.servlet.ServletContainerInitializer文件,新版本idea需要在resources目录下创建:
  3. 在文件中写入MyServletContainerInitializer的全类名

代码演示:

MyServletContainerInitializer:

//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来;
@HandlesTypes(value={HashMap.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* 应用启动的时候,会运行onStartup方法;
*
* Set<Class<?>> arg0:感兴趣的类型的所有子类型;
* ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用对应一个ServletContext;
*/
@Override
public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub
System.out.println("感兴趣的类型:");
for (Class<?> claz : arg0) {
System.out.println(claz);
}
}
}

创建javax.servlet.ServletContainerInitializer文件,

181949437e10fd39f87be1c21b886f65

运行结果:

image-20220623155252925

18.3、ServletContainerInitializer注册servlet、Listener、filter

18.3.1、编写MyServlet

public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("userServlet响应");
}
}

18.3.2、编写MyListener

public class MyListener implements ServletContextListener {
// 监听ServletContext销毁
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("监听ServletContext销毁");
}
// 监听ServletContext启动初始化
@Override
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("监听ServletContext启动初始化");
}
}

18.3.3、编写MyFilter

public class MyFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
// 过滤请求
System.out.println("过滤请求");
// 放行
arg2.doFilter(req, resp);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}

18.3.4、编写MyServletContainerInitializer

public class MyServletContainerInitializer implements ServletContainerInitializer {
/**
* 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener)
* 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件;
* 必须在项目启动的时候来添加;
* 1)、ServletContainerInitializer得到的ServletContext;
* 2)、ServletContextListener得到的ServletContext;
*/
@Override
public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
// 注册组件 ServletRegistration
ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new MyServlet());
// 配置servlet的映射信息
servlet.addMapping("/user");
// 注册Listener
sc.addListener(MyListener.class);
// 注册Filter FilterRegistration
FilterRegistration.Dynamic filter = sc.addFilter("userFilter", MyFilter.class);
// 配置Filter的映射信息,过滤所有请求
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}

18.4、整合SpringMVC分析

  1. web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
  2. 加载这个文件指定的类SpringServletContainerInitializer
  3. spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;
  4. 并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
    1. AbstractContextLoaderInitializer:调用createRootApplicationContext()方法创建根容器;
    2. AbstractDispatcherServletInitializer:
      1. 调用createServletApplicationContext()方法创建一个web的ioc容器;
      2. 创建DispatcherServlet
      3. 将创建的DispatcherServlet添加到ServletContext中;
    3. AbstractAnnotationConfigDispatcherServletInitializer:注解方式配置的DispatcherServlet初始化器
      1. createRootApplicationContext()创建根容器,调用getRootConfigClasses();传入一个配置类
      2. createServletApplicationContext();创建web的ioc容器: 调用getServletConfigClasses();获取配置类;

结论:以注解方式来启动SpringMVC的步骤:

  1. 继承AbstractAnnotationConfigDispatcherServletInitializer
  2. 实现抽象方法指定DispatcherServlet的配置信息

18.5、整合SpringMVC测试

18.5.1、依赖导入

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>

注意<scope>provided</scope>配置,由于Tomcat服务器里面也有servlet api,即目标环境已经该jar包了,所以我们在这儿将以上servlet api的scope设置成provided。这样的话,我们的项目在被打成war包时,就不会带上该jar包了,否则就会引起jar包冲突。

18.5.1、编写HelloService

@Service
public class HelloService {
public String sayHello(String name) {
return "Hello " + name;
}
}

18.5.2、编写HelloController

@Controller
public class HelloController {
@Autowired
HelloService helloService;
@ResponseBody
@RequestMapping("/hello")
public String hello() {
String hello = helloService.sayHello("tomcat..");
return hello;
}
}

18.5.3、编写RootConfig

//Spring的容器不扫描controller;父容器
@ComponentScan(value = "com.example.SpringMVC_Annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class RootConfig {
}

18.5.4、编写AppConfig

//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan(value="com.example.SpringMVC_Annotation",includeFilters={
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
@EnableWebMvc
public class AppConfig {
}

18.5.5、编写MyWebAppInitializer

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//获取根容器的配置类(相当于Spring的配置文件)父容器;
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
//获取web容器的配置类(相当于SpringMVC配置文件)子容器;
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
//获取DispatcherServlet的映射信息
// /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
// /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}

18.6、配置springMVC

通过继承WebMvcConfigurerAdapter类,重写里面的方法来配置springMVC。

例如:配置视图解析器、静态资源访问、拦截器等等

18.6.1、编写success.jsp

在WEB-INF目录下新建Views目录,在Views目录下创建success.jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
success
</body>
</html>

18.6.2、编写拦截器

public class MyInterceptor implements HandlerInterceptor {
// 目标方法运行之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("目标方法运行之前执行");
return true;
}
// 目标方法执行正确以后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
System.out.println("目标方法执行正确以后执行");
}
// 页面响应以后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
System.out.println("页面响应以后执行");
}
}

18.6.3、修改HelloController

@Controller
public class HelloController {
@Autowired
HelloService helloService;
@ResponseBody
@RequestMapping("/hello")
public String hello() {
String hello = helloService.sayHello("atguigu");
return hello;
}
// /WEB-INF/views/success.jsp
@RequestMapping("/suc")
public String success(){
return "success";
}
}

18.6.4、修改AppConfig

//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan(value="com.example.SpringMVC_Annotation",includeFilters={
@ComponentScan.Filter(type= FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter{
//视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//默认所有的页面都从 /WEB-INF/ xxx .jsp
//registry.jsp();
registry.jsp("/WEB-INF/views/", ".jsp");
}
//静态资源访问
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
// "/**":任意多的路径
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}

18.7、spring3.0异步请求

异步请求模型:

20fe3368da1ffdbef018f64905bcc745

编写HelloAsyncServlet

@WebServlet(value="/async",asyncSupported=true)
public class HelloAsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、支持异步处理asyncSupported=true
//2、开启异步模式
System.out.println("主线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
AsyncContext startAsync = req.startAsync();
//3、业务逻辑进行异步处理;开始异步处理
startAsync.start(new Runnable() {
@Override
public void run() {
try {
System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
sayHello();
startAsync.complete();
//4、获取响应
ServletResponse response = startAsync.getResponse();
response.getWriter().write("hello async...");
System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
} catch (Exception e) {
}
}
});
System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+System.currentTimeMillis());
}
public void sayHello() throws Exception{
System.out.println(Thread.currentThread()+" processing...");
Thread.sleep(3000);
}
}

测试结果:

主线程开始。。。Thread[http-nio-8080-exec-9,5,main]==>1646310054633
主线程结束。。。Thread[http-nio-8080-exec-9,5,main]==>1646310054633
副线程开始。。。Thread[http-nio-8080-exec-5,5,main]==>1646310054633
Thread[http-nio-8080-exec-5,5,main] processing...
副线程结束。。。Thread[http-nio-8080-exec-5,5,main]==>1646310057634

18.8、springMVC异步请求

18.8.1、方法一:callable

步骤:

  1. 控制器返回Callable
  2. Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
  3. DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
  4. Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
  5. 根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。

编写AsyncController:

@Controller
public class AsyncController {
/**
* 1、控制器返回Callable
* 2、Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
* 3、DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
* 4、Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
* 5、根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。
*/
@ResponseBody
@RequestMapping("/async01")
public Callable<String> async01() {
System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
return "Callable<String> async01()";
}
};
System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
return callable;
}
}

测试结果:

目标方法运行之前执行
主线程开始...Thread[http-nio-8080-exec-8,5,main]==>1646311553329
主线程结束...Thread[http-nio-8080-exec-8,5,main]==>1646311553329
副线程开始...Thread[MvcAsync2,5,main]==>1646311553329
副线程开始...Thread[MvcAsync2,5,main]==>1646311555330
目标方法运行之前执行
目标方法执行正确以后执行
页面响应以后执行

18.8.2、方法二:DeferredResult

请求模型:

7f7de415001dc5be4416de03fc49f4e5

执行步骤:

  1. controller层收到请求,消息入队
  2. 队列监听到消息入队,处理消息
  3. 如果超时任务触发,将超时状态改为true,并直接返回浏览器任务超时
  4. 队列处理完任务后,判断是否超时,如果超时,中断任务,否则将任务加入完成队列
  5. 完成队列监听器监听到消息入队,取出消息,使用延期返回对象返回

编写DeferredResultQueue:

public class DeferredResultQueue {
private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();
public static void save(DeferredResult<Object> deferredResult) {
System.out.println("加入队列");
queue.add(deferredResult);
}
public static DeferredResult<Object> get() {
System.out.println("从队列中取出");
return queue.poll();
}
}

编写AsyncController:

@Controller
public class AsyncController {
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<Object> createOrder() {
// 设置超时时间
DeferredResult<Object> deferredResult = new DeferredResult<>((long) 3000, "创建订单失败");
DeferredResultQueue.save(deferredResult);
return deferredResult;
}
@ResponseBody
@RequestMapping("/create")
public String create() {
//创建订单
String order = UUID.randomUUID().toString();
DeferredResult<Object> deferredResult = DeferredResultQueue.get();
deferredResult.setResult(order);
return "success ===> " + order;
}
}

回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。

编写AsyncController:

@Controller
public class AsyncController {
/**
* 1、控制器返回Callable
* 2、Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
* 3、DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
* 4、Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
* 5、根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。
*/
@ResponseBody
@RequestMapping("/async01")
public Callable<String> async01() {
System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
return "Callable<String> async01()";
}
};
System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
return callable;
}
}

测试结果:

目标方法运行之前执行
主线程开始...Thread[http-nio-8080-exec-8,5,main]==>1646311553329
主线程结束...Thread[http-nio-8080-exec-8,5,main]==>1646311553329
副线程开始...Thread[MvcAsync2,5,main]==>1646311553329
副线程开始...Thread[MvcAsync2,5,main]==>1646311555330
目标方法运行之前执行
目标方法执行正确以后执行
页面响应以后执行

18.8.2、方法二:DeferredResult

请求模型:

image-20220623145606206

执行步骤:

  1. controller层收到请求,消息入队
  2. 队列监听到消息入队,处理消息
  3. 如果超时任务触发,将超时状态改为true,并直接返回浏览器任务超时
  4. 队列处理完任务后,判断是否超时,如果超时,中断任务,否则将任务加入完成队列
  5. 完成队列监听器监听到消息入队,取出消息,使用延期返回对象返回

编写DeferredResultQueue:

public class DeferredResultQueue {
private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();
public static void save(DeferredResult<Object> deferredResult) {
System.out.println("加入队列");
queue.add(deferredResult);
}
public static DeferredResult<Object> get() {
System.out.println("从队列中取出");
return queue.poll();
}
}

编写AsyncController:

@Controller
public class AsyncController {
@ResponseBody
@RequestMapping("/createOrder")
public DeferredResult<Object> createOrder() {
// 设置超时时间
DeferredResult<Object> deferredResult = new DeferredResult<>((long) 3000, "创建订单失败");
DeferredResultQueue.save(deferredResult);
return deferredResult;
}
@ResponseBody
@RequestMapping("/create")
public String create() {
//创建订单
String order = UUID.randomUUID().toString();
DeferredResult<Object> deferredResult = DeferredResultQueue.get();
deferredResult.setResult(order);
return "success ===> " + order;
}
}
posted @   努力&选择  阅读(140)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2021-07-01 MySQL
点击右上角即可分享
微信分享提示