Spring高级知识点

参考视频:https://www.bilibili.com/video/BV1P44y1N7QG/?p=8&spm_id_from=pageDriver&vd_source=85ac5ee1b07df12a44b648a8751d30f6

参考文章:https://mofan212.github.io/posts/Spring-Forty-Nine-Lectures-Container-And-Bean/

Spring容器

以 SpringBoot 的启动类为例:

1
2
3
4
5
6
7
@Slf4j
@SpringBootApplication
public class A01Application {
public static void main(String[] args) {
SpringApplication.run(A01Application.class, args);
}
}

其中,run()方法存在返回值,返回 ConfigurableApplicationContext 容器

1
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

image-20240824102311015

ConfigurableApplicationContext 接口继承了 ApplicationContext 接口,而 ApplicationContext 接口又间接地继承了 BeanFactory 接口,除此之外还继承了其他很多接口,相当于对 BeanFactory 进行了拓展。

BeanFactory

  • 是 ApplicationContext 的父接口
  • 是 Spring 的核心容器,主要的 ApplicationContext 实现 组合 了它的功能,也就是说,BeanFactory 是 ApplicationContext 中的一个成员变量。

常用的 context.getBean(“xxx”) 方法,其实是调用了 BeanFactory 的 getBean() 方法。

其他方法:

1
2
3
4
preInstantiateSingletons():预先初始化单例对象
addEmbeddedValueResolver():给beanFactory加入解析器
常见参数:
- new StandardEnvironment()::resolvePlaceholders : ${} 解析器

基于它的子接口:

  • ListableBeanFactory:提供获取 Bean 集合的能力,比如一个接口可能有多个实现,通过该接口下的方法就能获取某种类型的所有 Bean;
  • HierarchicalBeanFactory:Hierarchical 意为“层次化”,通常表示一种具有层级结构的概念或组织方式,这种层次化结构可以通过父子关系来表示对象之间的关联,比如树、图、文件系统、组织架构等。根据该接口下的方法可知,能够获取到父容器,说明 BeanFactory 有父子容器概念;
  • AutowireCapableBeanFactory:提供了创建 Bean、自动装配 Bean、属性填充、Bean 初始化、依赖注入等能力,比如 @Autowired 注解的底层实现就依赖于该接口的 resolveDependency() 方法;
  • ConfigurableBeanFactory:该接口并未直接继承至 BeanFactory,而是继承了 HierarchicalBeanFactory。

BeanFactory不会

  • 主动调用 BeanFactory 后置处理器;
  • 主动添加 Bean 后置处理器;
  • 主动初始化单例对象;
  • 解析 ${} 和 #{}

DefaultListableBeanFactory

image-20240824102509577

  • DefaultListableBeanFactory 实现了 BeanFactory 接口,它能管理 Spring 中所有的 Bean,当然也包含 Spring 容器中的那些单例对象。
  • DefaultListableBeanFactory 还继承了 DefaultSingletonBeanRegistry 类,这个类就是用来管理 Spring 容器中的单例对象。
  • 通过 DefaultListableBeanFactory#registerBeanDefinition 可以注册bean到容器中

BeanFactoryPostProcessor

BeanFactory后置处理器,典型的有

1
org.springframework.context.annotation.internalConfigurationAnnotationProcessor

比如:internalConfigurationAnnotationProcessor就是用来处理 @Configuration 和 @Bean 注解的,将配置类中定义的 Bean 信息补充到 BeanFactory 中。

BeanPostProcessor

Bean后置处理器,典型的有

1
2
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor

前者用于解析 @Autowired 注解,后者用于解析 @Resource 注解,它们都有一个共同的类型 BeanPostProcessor。

1
ConfigurationClassPostProcessor.class

用于解析 @ComponentScan @Bean @Import @ImportResource

1
MapperScannerConfigurer.class

用于解析 @MapperScan

DefaultSingletonBeanRegistry

用来管理 Spring 容器中的单例对象。

1
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

Map 的 key 就是 Bean 的名字,而 value 是对应的 Bean,即单例对象。

BeanDefinition

BeanDefinition 也是一个接口,它封装了 Bean 的定义,Spring 根据 Bean 的定义,就能创建出符合要求的 Bean。

读取 BeanDefinition 可以通过下列两种类完成:

  • BeanDefinitionReader
  • ClassPathBeanDefinitionScanner

BeanDefinitionReader

该接口中对 loadBeanDefinitions() 方法进行了多种重载,支持传入一个或多个 Resource 对象、资源位置来加载 BeanDefinition。

它有一系列相关实现,比如:

  • XmlBeanDefinitionReader:通过读取 XML 文件来加载;
  • PropertiesBeanDefinitionReader:通过读取 properties 文件来加载,此类已经被 @Deprecated 注解标记;

除此之外,还有一个 AnnotatedBeanDefinitionReader,尽管它并不是 BeanDefinition 的子类,但它们俩长得很像,根据其类注释可知:它能够通过编程的方式对 Bean 进行注册,是 ClassPathBeanDefinitionScanner 的替代方案,能读取通过注解定义的 Bean。

ClassPathBeanDefinitionScanner

通过扫描指定包路径下的 @Component 及其派生注解来注册 Bean,是 @ComponentScan 注解的底层实现。

比如 MyBatis 通过继承 ClassPathBeanDefinitionScanner 实现通过 @MapperScan 注解来扫描指定包下的 Mapper 接口。

BeanDefinitionRegistry

AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 中都有一个 BeanDefinitionRegistry 类型的成员变量,它是一个接口,提供了 BeanDefinition 的增加、删除和查找功能。

ApplicationContext

ApplicationContext 除了继承 BeanFactory 外,还继承了:

  • MessageSource:使其具备处理国际化资源的能力
  • ResourcePatternResolver:使其具备使用通配符进行资源匹配的能力
  • EnvironmentCapable:使其具备读取 Spring 环境信息、配置文件信息的能力
  • ApplicationEventPublisher:使其具备发布事件的能力
  • ListableBeanFactory:提供了获取某种类型的 Bean 集合的能力
  • HierarchicalBeanFactory:提供了获取父容器的能力

虽然 ApplicationContext 继承了很多接口,但这些能力的实现是通过一种委派(Delegate)的方式实现的,这种方式也被叫做委派模式。

委派模式:实现获取资源的方式并不是由实现类自身完成,而是交给其内部的一个成员变量完成,这样的方式就是委派(这和对象适配器模式很相似)。在日常编码遇到这样的实现逻辑时,类名可以以 Delegate 结尾。

ApplicationContext的相关实现

  • ClassPathXmlApplicationContext:基于 classpath 下的 xml 格式的配置文件来创建Bean
  • FileSystemXmlApplicationContext:基于磁盘路径下 xml 格式的配置文件来创建Bean

本质都是利用 XmlBeanDefinitionReader#loadBeanDefinitions 加载Bean

  • AnnotationConfigApplicationContext:基于 Java 配置类来创建
  • AnnotationConfigServletWebServerApplicationContext:基于 Java 配置类来创建,用于 web 环境

ConfigurableApplicationContext

ApplicationContext 有一个子接口 ConfigurableApplicationContext,从类名就可以看出,它提供了对 ApplicationContext 进行配置的能力,浏览其内部方法可知,提供了诸如设置父容器、设置 Environment 等能力。

AbstractApplicationContext

ApplicationContext 有一个非常重要的抽象实现 AbstractApplicationContext,其他具体实现都会继承这个抽象实现,在其内部通过委派的方式实现了一些接口的能力,除此之外还有一个与 Spring Bean 的生命周期息息相关的方法:refresh()。

Bean

生命周期

初始化和销毁 Bean 的实现有三种:

  1. 依赖于后置处理器提供的拓展功能
  2. 相关接口的功能
  3. 使用 @Bean 注解中的属性进行指定

当同时存在以上三种方式时,它们的执行顺序也将按照上述顺序进行执行。

通过实现以下 BeanPostProcessor 接口,可以增强 Bean

  • InstantiationAwareBeanPostProcessor
  • DestructionAwareBeanPostProcessor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Slf4j
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

@Override
public void postProcessBeforeDestruction(Object o, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 销毁执行之前,如 @PreDestroy");
}
}

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 实例化之前执行,这里返回的对象会替换掉原本的 bean");
}
return null;
}

@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 实例化之后执行,如果返回 false 会跳过依赖注入节点");
// return false;
}
return true;
}

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 依赖注入阶段执行,如 @Autowired、@Value、@Resource");
}
return pvs;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 初始化执行之前,这里返回的对象会替换掉原本的 bean,如 @PostConstruct、@ConfigurationProperties");
}
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的 bean,如代理增强");
}
return bean;
}
}
---------------------------------------------------------------------------------------------
输出:

indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 实例化之前执行,这里返回的对象会替换掉原本的 bean
indi.mofan.bean.a03.LifeCycleBean : 构造
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 实例化之后执行,如果返回 false 会跳过依赖注入节点
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 依赖注入阶段执行,如 @Autowired@Value@Resource
indi.mofan.bean.a03.LifeCycleBean : 依赖注入: D:\environment\JDK1.8
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 初始化执行之前,这里返回的对象会替换掉原本的 bean,如 @PostConstruct@ConfigurationProperties
indi.mofan.bean.a03.LifeCycleBean : 初始化
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的 bean,如代理增强
indi.mofan.bean.a03.MyBeanPostProcessor : <<<<<<<<<< 销毁执行之前,如 @PreDestroy
indi.mofan.bean.a03.LifeCycleBean : 销毁

设计模式

为什么实现了 BeanPostProcessor 接口后就能够在 Bean 生命周期的各个阶段进行拓展呢?

因为使用了模板方法设计模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static class MyBeanFactory {
public Object getBean() {
Object bean = new Object();
System.out.println("构造 " + bean);
System.out.println("依赖注入 " + bean);
for (BeanPostProcessor processor : processors) {
processor.inject(bean);
}
System.out.println("初始化 " + bean);
return bean;
}

private List<BeanPostProcessor> processors = new ArrayList<>();

public void addProcessor(BeanPostProcessor processor) {
processors.add(processor);
}
}


// 之后如果需要拓展,调用 MyBeanFactory 实例的 addProcessor() 方法添加拓展逻辑即可:
public static void main(String[] args) {
MyBeanFactory beanFactory = new MyBeanFactory();
beanFactory.addProcessor(bean -> System.out.println("解析 @Autowired"));
beanFactory.addProcessor(bean -> System.out.println("解析 @Resource"));
beanFactory.getBean();
}

image-20240824172029016

@ConfigurationProperties 注解

使用 @ConfigurationProperties 可以指定配置信息的前缀,使得配置信息的读取更加简单。

AutowiredAnnotationBeanPostProcessor

用于解析 @Autowired 和 @Value 注解

AutowiredAnnotationBeanPostProcessor#postProcessProperties()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}

其中的 findAutowiringMetadata() 用于查找指定的 bean 对象中哪些地方使用了 @Autowired、@Value 等与注入相关的注解,并将这些信息封装在 InjectionMetadata 对象中,之后调用其 inject() 方法利用反射完成注入。

InjectionMetadata 对象中有一个名为 injectedElements 的集合类型成员变量,根据上图所示,injectedElements 存储了被相关注解标记的成员变量、方法的信息,因为 Bean1 中的 bean3 成员变量、setBean2() 和 setHome() 方法恰好被 @Autowired 注解标记。

Scope

Scope 用于指定 Bean 的作用范围,有如下五个取值:

  1. singleton:单例(默认值)。容器启动时创建(未设置延迟),容器关闭时销毁
  2. prototype:多例。每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory#destroyBean() 进行销毁
  3. request:作用于 Web 应用的请求范围。每次请求用到此 Bean 时创建,请求结束时销毁
  4. session:作用于 Web 应用的会话范围。每个会话用到此 Bean 时创建,会话结束时销毁
  5. application:作用于 Web 应用的 ServletContext。Web 容器用到此 Bean 时创建,容器关闭时销毁

application 的作用范围是 ServletContext,要想 application scope 发生变化可以重启程序。

Aware 接口

Aware 接口用于注入一些与容器相关的信息,比如:

  • BeanNameAware 注入 Bean 的名字
  • BeanFactoryAware 注入 BeanFactory 容器
  • ApplicationContextAware 注入 ApplicationContext 容器
  • EmbeddedValueResolverAware 解析 ${}
  1. Aware 接口提供了一种 内置 的注入手段,可以注入 BeanFactory、ApplicationContext;
  2. InitializingBean 接口提供了一种 内置 的初始化手段;
  3. 内置的注入和初始化不受拓展功能的影响,总会被执行,因此 Spring 框架内部的类总是使用这些接口。

Aware 相关接口

BeanNameAware, ApplicationContextAware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mofan
* @date 2023/1/8 16:12
*/
@Slf4j
public class MyBean implements BeanNameAware, ApplicationContextAware {
@Override
public void setBeanName(String name) {
log.info("当前 Bean: " + this + "名字叫: " + name);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.info("当前 Bean: " + this + "容器是: " + applicationContext);
}
}

InitializingBean

1
2
3
4
5
6
7
8
9
@Slf4j
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
// --snip--

@Override
public void afterPropertiesSet() throws Exception {
log.info("当前 Bean: " + this + " 初始化");
}
}

当同时实现 Aware 接口和 InitializingBean 接口时,会先执行 Aware 接口。

BeanFactoryAware 、ApplicationContextAware 和 EmbeddedValueResolverAware 三个接口的功能可以使用 @Autowired 注解实现,InitializingBean 接口的功能也可以使用 @PostConstruct 注解实现,为什么还要使用接口呢?

为何有这些接口

@Autowired 和 @PostConstruct 注解的解析需要使用 Bean 后置处理器,属于拓展功能,而这些接口属于内置功能,不加任何拓展 Spring 就能识别。在某些情况下,拓展功能会失效,而内容功能不会失效。

对于 context.refresh(); 方法来说,它主要按照以下顺序干了三件事:

  1. 执行 BeanFactory 后置处理器;
  2. 添加 Bean 后置处理器;
  3. 创建和初始化单例对象。

失效场景

当 Java 配置类中定义了BeanFactoryPostProcessor 时,如果要创建配置类中的 BeanFactoryPostProcessor 就必须 提前 创建和初始化 Java 配置类。

在创建和初始化 Java 配置类时,由于 BeanPostProcessor 还未准备好,无法解析配置类中的 @Autowired 等注解,导致 @Autowired 等注解失效:

具体场景参考博客。

如果一个单例对象的成员变量是多例,怎么办才能在getBean的时候,获取的成员变量是多例的

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// class1
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class F1 {
}

// class2
@Getter
@Component
public class E {
@Autowired
private F1 f1;
}

// main
@Slf4j
@ComponentScan("indi.mofan.bean.a09")
public class A09Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);

E e = context.getBean(E.class);
log.info("{}", e.getF1());
log.info("{}", e.getF1());
log.info("{}", e.getF1());

context.close();
}
}
----------------------------------------------------------------------------------------
// 输出的F1为同一个

原因:对于单例对象来说,依赖注入仅发生了一次,后续不会再注入其他的 f1,因此 e 始终使用的是第一次注入的 f1

解决:

  1. 可以使用 @Lazy 注解,因为 @Lazy 生成的是代理对象,虽然代理对象依旧是同一个,但每次使用代理对象中的方法时,会由代理对象创建新的目标对象
  2. 其他推荐方式(Object工程、Context容器)参考博客

AOP

前置基础

  1. aop的实现方式(除jdk、cglib外的实现方式)
  2. aop源码、原理

以上参考博客(9-13):

https://mofan212.github.io/posts/Spring-Forty-Nine-Lectures-AOP/

Pointcut(切点)

在 Spring 中,切点通过接口 org.springframework.aop.Pointcut 来表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Pointcut {

/**
* 根据类型过滤
*/
ClassFilter getClassFilter();

/**
* 根据方法匹配
*/
MethodMatcher getMethodMatcher();


/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;

}

Pointcut 接口有很多实现类,比如:

  • AnnotationMatchingPointcut:通过注解进行匹配
  • AspectJExpressionPointcut:通过 AspectJ 表达式进行匹配(本节的选择)
  • StaticMethodMatcherPointcut:通过注解进行匹配(视频中匹配 @Transactional 时使用)

无论是 AspectJExpressionPointcut 还是 StaticMethodMatcherPointcut,它们都实现了 MethodMatcher 接口,用来执行方法的匹配。

AspectJExpressionPointcut

判断编写的 AspectJ 表达式是否与某一方法匹配可以使用其 matches() 方法。

StaticMethodMatcherPointcut

@Transactional 是 Spring 中使用频率非常高的注解,那它底层是通过 AspectJExpressionPointcut 与 @annotation() 切点表达式相结合对目标方法进行匹配的吗?

答案是否定的。@Transactional 注解除了可以作用在方法上,还可以作用在类(或接口)上。

在底层 @Transactional 注解的匹配使用到了 StaticMethodMatcherPointcut

Advice(通知)

MethodInterceptor:这个接口实现的通知属于环绕通知。

Aspect(切面)

DefaultPointcutAdvisor:创建这种切面时,传递一个节点和通知。

/*

  • 多个切面:

  • aspect =

  • 通知 1 (advice) + 切点 1(pointcut)

  • 通知 2 (advice) + 切点 2(pointcut)

  • 通知 3 (advice) + 切点 3(pointcut)

  • advisor = 更细粒度的切面,包含一个通知和切点

  • */

ProxyFactory

Spring 是根据什么信息来选择不同的动态代理实现呢?

ProxyFactory 的父类 ProxyConfig 中有个名为 proxyTargetClass 的布尔类型成员变量:

  • 当 proxyTargetClass == false,并且目标对象所在类实现了接口时,将选择 JDK 动态代理;
  • 当 proxyTargetClass == false,但目标对象所在类未实现接口时,将选择 CGLib 动态代理;
  • 当 proxyTargetClass == true,总是选择 CGLib 动态代理。
1
2
3
4
// 设置实现的接口
factory.setInterfaces(target.getClass().getInterfaces());
// 设置proxyTargetClass
factory.setProxyTargetClass(true);

ProxyFactory 是用来创建代理的核心实现,使用 AopProxyFactory 选择具体的代理实现:

  • JdkDynamicAopProxy
  • ObjenesisCglibAopProxy

AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现,AopProxy 通过 getProxy() 方法创建代理对象。

上述类图中的类与接口都实现了 Advised 接口,能够获得关联的切面集合与目标(实际上是从 ProxyFactory 中获取的)。

调用代理方法时,会借助 ProxyFactory 统一将通知转换为环绕通知 MethodInterceptor。。

AnnotationAwareAspectJAutoProxyCreator

Bean 后置处理器。尽管它的名称中没有 BeanPostProcessor 的字样,但它确实是实现了 BeanPostProcessor 接口的。

AnnotationAwareAspectJAutoProxyCreator 有两个主要作用:

  1. 找到容器中所有的切面,针对高级切面,将其转换为低级切面;
  2. 根据切面信息,利用 ProxyFactory 创建代理对象。

AnnotationAwareAspectJAutoProxyCreator 实现了 BeanPostProcessor,可以在 Bean 生命周期中的一些阶段对 Bean 进行拓展。AnnotationAwareAspectJAutoProxyCreator 可以在 Bean 进行 依赖注入之前、Bean 初始化之后 对 Bean 进行拓展。

重点介绍 AnnotationAwareAspectJAutoProxyCreator 中的两个方法:

  • findEligibleAdvisors
  • wrapIfNecessary

findEligibleAdvisors

位于父类 AbstractAdvisorAutoProxyCreator 中,用于找到符合条件的切面类。低级切面直接添加,高级切面转换为低级切面再添加。

findEligibleAdvisors() 方法接收两个参数:

  • beanClass:配合切面使用的目标类 Class 信息
  • beanName:当前被代理的 Bean 的名称

wrapIfNecessary

wrapIfNecessary() 方法内部调用了 findEligibleAdvisors() 方法,若 findEligibleAdvisors() 方法返回的集合不为空,则表示需要创建代理对象。

如果需要创建对象,wrapIfNecessary() 方法返回的是代理对象,否则仍然是原对象。

wrapIfNecessary() 方法接收三个参数:

  • bean:原始 Bean 实例
  • beanName:Bean 的名称
  • cacheKey:用于元数据访问的缓存 key

@Order

根据上述打印的信息可知,低级切面相比于高级切面先一步被执行,这个执行顺序是可以被控制的。

针对高级切面来说,可以在类上使用 @Order 注解

在高级切面中,@Order 只有放在类上才生效,放在方法上不会生效。比如高级切面中有多个前置通知,这些前置通知对应的方法上使用 @Order 注解是无法生效的。

针对低级切面,需要设置 advisor 的 order 值,而不是向高级切面那样使用 @Order 注解,使用 @Order 注解设置在 advisor3() 方法上是无用的。

代理对象创建时机

使用 AnnotationAwareAspectJAutoProxyCreator Bean 后置处理器创建代理对象的时机有以下两个选择:

  • Bean 的依赖注入之前
  • Bean 初始化完成之后

代理对象的创建时机:

  • 无循环依赖时,在 Bean 初始化阶段之后创建;
  • 有循环依赖时,在 Bean 实例化后、依赖注入之前创建,并将代理对象暂存于二级缓存。

Bean 的依赖注入阶段和初始化阶段不应该被增强,仍应被施加于原始对象。

高级切面转低级切面

调用 AnnotationAwareAspectJAutoProxyCreator 对象的 findEligibleAdvisors() 方法时,获取能配合目标 Class 使用的切面,最终返回 Advisor 列表。在搜索过程中,如果遇到高级切面,则会将其转换成低级切面。

以解析 @Before 注解为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws Throwable {
// 切面对象实例工厂,用于后续反射调用切面中的方法
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
// 高级切面转低级切面类
List<Advisor> list = new ArrayList<>();
for (Method method : Aspect.class.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
// 解析切点
String expression = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类。前置通知对应的通知类是 AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 切面(advice转换成advisor)
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
for (Advisor advisor : list) {
System.out.println(advisor);
}
}

@Before 标记的前置通知会被转换成原始的 AspectJMethodBeforeAdvice 形式,该对象包含了以下信息:

  • 通知对应的方法信息
  • 切点信息
  • 通知对象如何创建,本例公用一个 Aspect 对象
注解 对应的原始通知类
@Before AspectJMethodBeforeAdvice
@AfterReturning AspectJAfterReturningAdvice
@AfterThrowing AspectJAfterThrowingAdvice
@After AspectJAfterAdvice
@Around AspectJAroundAdvice

静态通知调用

详情见参考博客

动态通知调用

详情见参考博客

MVC

RequestMappingHandlerMapping

HandlerMapping,即处理器映射器,用于建立请求路径与控制器方法的映射关系。

RequestMappingHandlerMapping 是 HandlerMapping 的一种实现,根据类名可知,它是通过 @RequestMapping 注解来实现路径映射。

当 Spring 容器中没有 HandlerMapping 的实现时,尽管 DispatcherServlet 在初始化时会添加一些默认的实现,但这些实现不会交由 Spring 管理,而是作为 DispatcherServlet 的成员变量。

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter 实现了 HandlerAdapter 接口,HandlerAdapter 用于执行控制器方法,而 RequestMapping 表明 RequestMappingHandlerAdapter 用于执行被 @RequestMapping 注解标记的控制器方法。

实现控制器方法的调用很简单,但如何将请求参数与方法参数相绑定的呢?

显然是需要解析 @RequestParam 注解。

Spring 支持许多种类的控制器方法参数,不同种类的参数使用不同的解析器,使用 RequestMappingHandlerAdapter 的 getArgumentResolvers() 方法获取所有参数解析器。

Spring 也支持许多种类的控制器方法返回值类型,使用 RequestMappingHandlerAdapter 的 getReturnValueHandlers() 方法获取所有返回值处理器。

参数解析器

@RequestParam

@RequestParam 注解的解析需要使用到 RequestParamMethodArgumentResolver 参数解析器。构造时需要两个参数:

  • beanFactory:Bean 工厂对象。需要解析 ${} 时,就需要指定 Bean 工厂对象
  • useDefaultResolution:布尔类型参数。为 false 表示只解析添加了 @RequestParam 注解的参数,为 true 针对未添加 @RequestParam 注解的参数也使用该参数解析器进行解析。

RequestParamMethodArgumentResolver 利用 resolveArgument() 方法完成参数的解析,该方法需要传递四个参数:

  • parameter:参数对象
  • mavContainer:ModelAndView 容器,用来存储中间的 Model 结果
  • webRequest:由 ServletWebRequest 封装后的请求对象
  • binderFactory:数据绑定工厂,用于完成对象绑定和类型转换,比如将字符串类型的 18 转换成整型

@PathVariable

@PathVariable 注解的解析需要使用到 PathVariableMethodArgumentResolver 参数解析器。构造时无需传入任何参数。

使用该解析器需要一个 Map 集合,该 Map 集合是 @RequestMapping 注解上指定的路径和实际 URL 路径进行匹配后,得到的路径上的参数与实际路径上的值的关系(获取这个 Map 并将其设置给 request 作用域由 HandlerMapping 完成)。

@RequestHeader

@RequestHeader 注解的解析需要使用到 RequestHeaderMethodArgumentResolver 参数解析器。构造时需要传入一个Bean 工厂对象。

@CookieValue

@CookieValue 注解的解析需要使用到 ServletCookieValueMethodArgumentResolver 参数解析器。构造时需要传入一个Bean 工厂对象。

@Value

@Value 注解的解析需要使用到 ExpressionValueMethodArgumentResolver 参数解析器。构造时需要传入一个Bean 工厂对象。

HttpServletRequest

HttpServletRequest 类型的参数的解析需要使用到 ServletRequestMethodArgumentResolver 参数解析器。构造时无需传入任何参数。

ServletRequestMethodArgumentResolver 参数解析器不仅可以解析 HttpServletRequest 类型的参数,还支持许多其他类型的参数,其支持的参数类型可在 supportsParameter() 方法中看到:

@ModelAttribute

@ModelAttribute 注解的解析需要使用到 ServletModelAttributeMethodProcessor 参数解析器。构造时需要传入一个布尔类型的值。为 false 时,表示 @ModelAttribute 不是不必须的,即是必须的。

针对 @ModelAttribute(“abc”) User user1 和 User user2 两种参数来说,尽管后者没有使用 @ModelAttribute 注解,但它们使用的是同一种解析器。

添加两个 ServletModelAttributeMethodProcessor 参数解析器,先解析带 @ModelAttribute 注解的参数,再解析不带 @ModelAttribute 注解的参数。

通过 ServletModelAttributeMethodProcessor 解析得到的数据还会被存入 ModelAndViewContainer 中。存储的数据结构是一个 Map,其 key 为 @ModelAttribute 注解指定的 value 值,在未显式指定的情况下,默认为对象类型的首字母小写对应的字符串。

@RequestBody User user3 参数也被 ServletModelAttributeMethodProcessor 解析了,如果想使其数据通过 JSON 数据转换而来,则需要使用另一个参数解析器。

@RequestBody

@RequestBody 注解的解析需要使用到 RequestResponseBodyMethodProcessor 参数解析器。构造时需要传入一个消息转换器列表。

1
先添加解析 @ModelAttribute 注解的解析器,再添加解析 @RequestBody 注解的解析器,最后添加解析省略了 @ModelAttribute 注解的解析器。如果更换最后两个解析器的顺序,那么 @RequestBody User user3 将会被 ServletModelAttributeMethodProcessor 解析,而不是 RequestResponseBodyMethodProcessor。

获取参数名

DefaultParameterNameDiscoverer

1
2
3
4
5
6
7
8
9
10
11
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}

}

需要获取参数名称的原因:(java编译的时候,如果不加-par-parameters 即不是【javac -parameters .\Bean2.java】编译的,则不会保留参数名称)

在项目的 src 目录外创建一个 Bean2.java 文件,使其不会被 IDEA 自动编译

1
2
3
4
5
6
7
package indi.mofan.a22;

public class Bean2 {
public void foo(String name, int age) {

}
}

将命令行切换到 Bean2.java 文件所在目录的位置,执行 javac .\Bean2.java 命令手动编译 Bean2.java。查看 Bean2.class 文件的内容

1
2
3
4
5
6
7
8
9
package indi.mofan.a22;

public class Bean2 {
public Bean2() {
}

public void foo(String var1, int var2) {
}
}

编译生成的 class 文件中的 foo() 方法的参数名称不再是 name 和 age,也就是说直接使用 javac 命令进行编译得到的字节码文件不会保存方法的参数名称。