若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。
写这篇博客旨在制作笔记,方便个人在线阅览,巩固知识。无他用。
参考文章:【Spring 的三级缓存解决循环依赖】
推荐阅读文章:【Spring & SpringBoot 常用注解总结】
博主的相关学习笔记
要求:掌握 refresh 的 12 个步骤
org/springframework/context/support/AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException
refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作。
它的内部主要会调用 12 个方法,我们把它们称为 refresh 的 12 个步骤。
要点:
- 这一步创建和准备了 Environment 对象
- 理解 Environment 对象的作用
@Value
,值注入时提供键值*.properties
文件的键值)要点:
- 这一步 获取 / 创建 了 BeanFactory
- 理解 BeanFactory 的作用
- 理解 BeanDefinition 的作用
- BeanDefinition 从何而来
要点:
- 完善 BeanFactory
- 了解是谁来解析 SpEL(Spring 表达式语言) 的
- 了解是谁执行的类型转换
- 了解特殊 bean 的注入
- 两个内置的 BeanPostProcessors 的作用
${ }
解析要点:这一步是空实现,留给子类扩展。
要点:
- 理解 beanFactory 后处理器的作用
- 掌握常见的 beanFactory 后处理器
@Configuration
、@Bean
、@Import
、@PropertySource
等${ }
要点:
- 理解 bean 后处理器的作用
- 掌握常见的 bean 后处理器
@Autowired
,@Value
注解@Resource
,@PostConstruct
,@PreDestroy
要点:
- 理解 MessageSource 的作用
- MessageSource 从何而来
要点:
- 理解事件广播器的作用
- 事件广播器从何而来
- 如何发布事件
ApplicationContext.publishEvent(事件对象)
来发布事件要点:这一步是空实现,留给子类扩展
要点:
- 理解事件监听器的作用
- 监听器从何而来
- 如何接受事件
@EventListener
的解析onApplicationEvent(E e)
方法即可要点:
- 了解 conversionService
- 了解内嵌值解析器
- 单例池 - singletonObjects
@Value
中的 ${ }
,借用的是 Environment 的功能
要点:
- 了解 lifecycleProcessor
- lifecycleProcessor 从何而来
- 如何控制 lifecycleProcessor
- 发布 ContextRefreshed 事件
要求:掌握 Spring bean 的生命周期
org/springframework/beans/factory/support/AbstractBeanFactory.java
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object beanInstance;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) { ... ... }
else {
... ...
BeanFactory parentBeanFactory = getParentBeanFactory();
... ...
}
return adaptBeanInstance(name, beanInstance, requiredType);
}
Spring 里面的 Bean 是懒惰式初始化的,一开始并不会创建 Bean 的实例,只有在调用的时候才会创建 Bean 的实例,然后再去进行依赖注入、初始化等操作 … … 所以说 bean 的生命周期是从调用 beanFactory 的 getBean 开始的。
bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:
@Autowired
、唯一带参构造、默认构造@Autowired
@Value
、@Resource
、ByName ByType、精确指定@PostConstruct
、InitializingBean、initMethod、创建代理对象注意:划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情
要点:
- 掌握别名处理
- 了解 FactoryBean 的名字规范
- 掌握三级缓存的概念
&
开头,则表示要获取 FactoryBean 本身;否则表示要获取其产品参考文章:【Spring 的三级缓存解决循环依赖】
- 第一级缓存:也叫单例池,存放已经经历了完整生命周期的 Bean 对象。
- 第二级缓存:存放早期暴露出来的 Bean 对象,实例化以后,就把对象放到这个 Map 中。
- 这里的 Bean 可能只是经历过了实例化,其属性还尚未填充。
- 第三级缓存:存放早期暴露的 Bean 的工厂。
注意细节:
- 只有单例的 bean 会通过三级缓存的提前暴露来解决循环依赖的问题;
而非单例的 bean,每次从容器中获取的对象都是一个新的对象,都会重新创建;
所以非单例的 bean 是没有缓存的,不会将其放到三级缓存中。- 为了解决第二级缓存中 AOP 生成新对象的问题,Spring 就提前 AOP
- 例:在加载 b 的流程中,发生了循环依赖,b 依赖了 a,就要对 a 执行 AOP,提前获取增强以后的 a 对象。
这样 b 对象依赖的 a 对象就是增强以后的 a 了。- 二级缓存和三级缓存就是要解决循环依赖的,且之所以设置这两个级别的缓存,主要的原因就是:
- 它们可以实现循环依赖对象需要提前被 AOP 代理的操作;
- 如果没有循环依赖,早期的 bean 也不会真正暴露,不用提前执行代理过程,也不用重复执行代理过程。
要点:了解有父容器时的查找规则
要点 | 总结 |
---|---|
了解有 dependsOn 时的 bean 初始化顺序 | dependsOn 用在非显示依赖的 bean 的创建顺序控制 |
了解 @Conditional 的解析时机 |
@Conditional 由 ConditionEvaluator 解析看是否满足装配条件 |
了解 beanName 的解析时机 | beanName 的解析要分情况 组件扫描:AnnotationBeanNameGenerator @Import :FullyQualifiedAnnotationBeanNameGenerator@Bean :ConfigurationClassBeanDefinitionReader |
掌握 @Bean 的解析 |
@Bean 相当于工厂方法,其所在类相当于工厂 |
了解 @DependsOn ,@Lazy ,@Primary 的解析时机 |
这些注解由 AnnotationConfigUtils 补充为 BeanDefinition |
了解 @Scope 代理的解析 |
Scope 标注的 bean 会为之生成 ScopeProxyFactoryBean 的 BeanDefinition 取代原有的 原有的 BeanDefinition 则会成为内嵌定义 |
要点:理解三种 scope
scope 可以理解为从 XXX 范围内找这个 bean 更加贴切
单例 bean 从首次 refresh 被创建,到 close 被销毁(其中 BeanFactory 会记录哪些 bean 需要调用销毁方法)。
多例 bean 从首次 getBean 被创建,到调用 BeanFactory 的 destoryBean 时会被销毁。
(没有谁会记录该 bean 需要调用销毁方法,需要我们自行调用来做清理工作)
要点 | 总结 |
---|---|
有自定义 TargetSource 的情况 | 由 AnnotationAwareAspectJAutoProxyCreator 创建代理返回 |
Supplier 方式创建 bean 实例 | 为 Spring 5.0 新增功能,方便编程方式创建 bean 实例 |
FactoryMethod 方式 创建 bean 实例 | ① 分成静态工厂与实例工厂; ② 工厂方法若有参数,需要对工厂方法参数进行解析,利用 resolveDependency; ③ 如果有多个工厂方法候选者,还要进一步按权重筛选 |
AutowiredAnnotationBeanPostProcessor 选择构造 | ① 优先选择带 @Autowired 注解的构造;② 若有唯一的带参构造,也会入选 |
mbd.getPreferredConstructors | 选择所有公共构造,这些构造之间按权重筛选 |
采用默认构造 | 如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的 |
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor(注解匹配) | 识别 @Autowired 及 @Value 标注的成员,封装为 InjectionMetadata 进行依赖注入 |
CommonAnnotationBeanPostProcessor(注解匹配) | 识别 @Resource 标注的成员,封装为 InjectionMetadata 进行依赖注入 |
resolveDependency | 用来查找要装配的值,可以识别: ① Optional; ② ObjectFactory 及 ObjectProvider; ③ @Lazy 注解;④ @Value 注解(${ } 、#{ } 、类型转换);⑤ 集合类型(Collection、Map、数组等); ⑥ 泛型和 @Qualifier (用来区分类型歧义);⑦ primary 及名字匹配(用来区分类型歧义) |
AUTOWIRE_BY_NAME(根据名字匹配) | 根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员 |
AUTOWIRE_BY_TYPE(根据类型匹配) | 根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd 的 propertyValues |
applyPropertyValues(精确指定) | 根据 mbd 的 propertyValues 进行依赖注入(即 xml 中的 标签中的精确指定) |
问:如果针对同一个成员,采用了多种方式给它配置了依赖注入,哪种方式的优先级最高呢?
优先级顺序(从高到低排序):[精确指定注入 bean 的名称来匹配] > [AUTOWIRE_BY_TYPE 匹配] > [注解方式匹配]
要点 | 总结 |
---|---|
内置 Aware 接口的装配 | 包括 BeanNameAware,BeanFactoryAware 等 |
扩展 Aware 接口的装配 | 由 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization |
@PostConstruct |
由 CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization |
实现 InitializingBean 接口 | 通过接口回调执行初始化 |
initMethod | 根据 BeanDefinition 得到的初始化方法执行初始化,即 或 @Bean(initMethod) |
创建 aop 代理 | 由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization |
要点:判断并登记可销毁 bean
@Bean
没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(close、shutdown)@PreDestroy
标注的方法,也会被认为是一个可以销毁的 bean要点:
- singleton bean 的销毁时机
- 自定义 scope bean 的销毁时机
- prototype bean 的销毁时机
- 同一 bean 中的不同形式销毁方法的调用次序
ApplicationContext.close
时,此时会找到所有 DisposableBean 的名字,逐一销毁AutowireCapableBeanFactory.destroyBean
方法执行销毁@PreDestroy
要求
- 掌握创建代理的过程
- 掌握单例 set 方式循环依赖的原理
- 掌握其它循环依赖的解决方法
- 要完全理解循环依赖,需要理解代理对象的创建时机
- 掌握 ProxyFactory 创建代理的过程,理解 Advisor,Advice,Pointcut 与 Aspect
- 掌握 AnnotaionAwareAspectJAutoProxyCreator 筛选 Advisor 合格者,创建代理的过程
- AOP 的概念
- AOP 思想的实现方案(动态代理技术)
- AOP 切面编程涉及到的一些专业术语
- AspectJ 的通知有以下五种类型
- aspect 和 advisor
- 动态代理的两种方式
- 切点表达式
AOP 的概念
AOP 思想的实现方案(动态代理技术)
AOP 切面编程涉及到的一些专业术语
概念 | 单词 | 解释 |
---|---|---|
目标对象 | Target | 被增强的方法所在的对象 |
代理对象 | Proxy | 对目标对象进行增强后的对象,客户端实际调用的对象 |
连接点 | Joinpoint | 目标对象中可以被增强的方法 |
切入点 | Pointcut | 目标对象中实际被增强的方法 |
通知 / 增强 | Advice | 增强部分的代码逻辑 |
切面 | Aspect | 增强和切入点的组合 |
织入 | Weaving | 通知和切入点动态组合的过程(生成了代理对象) |
AspectJ 的通知有以下五种类型
通知名称 | 执行时机 |
---|---|
前置通知 | 目标方法执行之前执行 |
后置通知 | 目标方法执行之后执行,目标方法异常时,不再执行 |
环绕通知 | 目标方法执行前后执行,目标方法异常时,环绕后方法不再执行 |
异常通知 | 目标方法抛出异常时执行 |
最终通知 | 不管目标方法是否有异常,最终都会执行 |
aspect 和 advisor
动态代理的两种方式
代理技术 | 使用条件 |
---|---|
JDK 动态代理技术 | 目标类有接口(也就是说目标类是某个接口的实现类),是基于接口动态生成实现类的代理对象 |
Cglib 动态代理技术 | 目标类无接口,且不能使用 final 修饰,是基于被代理对象动态生成的子对象为代理对象 |
切点表达式
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强
execution([访问修饰符] 返回值类型 包名.类名.方法名(参数))
*
表示任意;.
表示该包下的类,使用双点 ..
表示该包及其子包下的类;..
表示任意参数示例代码(circularDependency/App1.java
)
public class App1 {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target1()); // 设置目标对象
// 添加通知(也可以说是功能增强)
proxyFactory.addAdvice(new MethodInterceptor() { // MethodInvocation 是环绕通知
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
System.out.println("目标方法调用前_增强处理");
return invocation.proceed();
} finally { System.out.println("目标方法调用后_增强处理"); }
}
});
Target1 proxy = (Target1) proxyFactory.getProxy(); // 创建代理对象(Cglib 动态代理)
System.out.println("------------[proxy.foo]------------");
proxy.foo();
System.out.println("------------[proxy.bar]------------");
proxy.bar();
System.out.println("-----------------------------------");
}
interface I1 {
void foo();
void bar();
}
static class Target1 implements I1 {
@Override
public void foo() { System.out.println("target1 foo"); }
@Override
public void bar() { System.out.println("target1 bar"); }
}
}
输出结果
------------[proxy.foo]------------
目标方法调用前_增强
target1 foo
目标方法调用后_增强
------------[proxy.bar]------------
目标方法调用前_增强
target1 bar
目标方法调用后_增强
-----------------------------------
示例代码(circularDependency/App2.java
)
public class App2 {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target1());
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); // 创建切点
pointcut.setExpression("execution(* foo())"); // 配置切点表达式(此处只增强 foo() 方法)
// 切点 + 通知 = advisor
proxyFactory.addAdvisor(
new DefaultPointcutAdvisor(pointcut,
// 这里我直接用 Lambda表达式 的形式代替之前的 匿名内部类 形式了
(MethodInterceptor) invocation -> {
try {
System.out.println("before_1_增强处理");
return invocation.proceed();
} finally { System.out.println("after_1_增强处理"); }}));
proxyFactory.addAdvisor(
new DefaultPointcutAdvisor(pointcut,
(MethodInterceptor) invocation -> {
try {
System.out.println("before_2_增强处理");
return invocation.proceed();
} finally { System.out.println("after_2_增强处理"); }}));
Target1 proxy = (Target1) proxyFactory.getProxy();
System.out.println(proxy.getClass() + "\n"); // 查看打印结果,看看这段代码的动态代理方式
proxy.foo();
proxy.bar(); // 这里没有对 bar() 方法做增强处理
}
interface I1 {
void foo();
void bar();
}
static class Target1 implements I1 {
@Override
public void foo() { System.out.println("target1 foo"); }
@Override
public void bar() { System.out.println("target1 bar"); }
}
}
打印结果
class circularDependency.App2$Target1$$EnhancerBySpringCGLIB$$c461669a
before_1_增强处理
before_2_增强处理
target1 foo
after_2_增强处理
after_1_增强处理
target1 bar
其实 3.1.2 小节里使用的 addAdvice() 内部调用的依旧是 addAdvisor()。
在没有传入 pointcut 的情况下,它会默认增强所有的方法。
在传入了 pointcut 的情况下,它才会增强指定的方法。
动态代理的实现的选择:
- 在代理工厂对象(proxyFactory)调用 getProxy() 方法时,可选用的 AopProxy 接口有两个实现类
- CglibAopProxy、JdkDynamicAopProxy
- 这两种都是动态生成代理对象的方式,一种就是基于 JDK 的,一种是基于 Cglib 的
下面对 3.1.2 小节里的 App1.java 进行 Debug
proxyFactory.setTarget(new Target1()); // 设置目标对象
// 添加通知/功能增强 ... ...
Target1 proxy = (Target1) proxyFactory.getProxy(); // 在这个位置进行 Debug 操作(打断点)
// 既然这里可以用 Target1 强转,说明在 Cglib 动态代理下,目标对象和代理对象是父子关系
只是改动了几处代码,其余代码不变(这里我图方便,直接重新创建了一个类,内容与之前相比,并没有差别)
proxyFactory.setTarget(new Target1());
proxyFactory.addInterface(I1.class);
// 添加通知/功能增强 ... ...
I1 proxy = (I1) proxyFactory.getProxy(); // 在这个位置进行 Debug 操作(打断点)
// 这里是不可以用 Target1 强转的,说明在 JDK 动态代理下,目标对象和代理对象是平级的关系
吐槽:个人感觉它这个例子并不算太好。接口实现类混在一起的,其实并不好区分两种动态代理方式的区别。
就 Cglib 动态代理方式示例而言,不用应该用到接口,不让目标对象继承接口。这样不容易混淆。
Cglib 动态代理方式,就是可以直接动态生成目标对象的子类代理对象的。
JDK 动态代理方式,它的目标类必须要有接口(也就是说它是接口的实现类),是基于接口动态生成接口实现类的代理对象。
加上下面一行的代码,直接让程序统一采用 Cglib 动态代理方式来生成代理对象。
proxyFactory.setProxyTargetClass(true);
代理对象调用方法的时候,需要在内部先找到切面,之后通过切面去检查切点是不是跟方法匹配。
如果方法与切点是匹配的,代理对象才去调通知。
这里就抛出一个问题:这个切面的信息都被保存在哪里去了?将来我们需要在哪里找到切面对象呢?
下面对 3.1.3 小节里的 App2.java 进行 Debug
proxy.foo(); // 打断点
显然,代理对象(proxy)的内部(advisors 集合)会记录 advisor 切面的信息.
这里只举了 Cglib 动态代理的例子,其实 JDK 动态代理也会这样做 (懒得贴图了) 。
AOP 编程三要素:切面(advisor),切点(pointcut),通知(advice)
在实际开发中,我们往往会用注解的方式来进行 AOP 编程。
@Aspect
的类是切面类;@Around
、@Before
、@After
等通知类型的注解里的是切点表达式;
@Aspect
static class Aspect1 {
@Around("execution(* foo())") // 一个 advisor 切面(切点 + 通知)
// 环绕通知依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("aspect1 around");
return pjp.proceed();
}
}
这种注解的方式最终还是会被转换成 advisor 与 MethodInterceptor (Spring 内置的环绕通知方式)的方式。
它们之间的转换是以通知方法为单位进行转换的。
@Aspect
static class Aspect2 {
@Before("execution(* foo())") // 一个 advisor 切面
public void before() { System.out.println("aspect2 before"); }
@After("execution(* foo())") // 一个 advisor 切面
public void after() { System.out.println("aspect2 after"); }
}
示例代码(circularDependency/App3.java
)
public class App3 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("aspect1", Aspect1.class);
context.registerBean("aspect2", Aspect2.class);
context.registerBean("aspect3", Aspect3.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class); // 自动代理后处理器
context.registerBean("target1", Target1.class);
context.registerBean("target2", Target2.class);
context.refresh();
Target1 target1 = context.getBean(Target1.class);
Target2 target2 = context.getBean(Target2.class);
target1.foo();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
target2.bar();
}
static class Target1 {
public void foo() {.System.out.println("Target1 foo"); }
}
static class Target2 {
public void bar() { System.out.println("Target2 bar"); }
}
@Aspect
static class Aspect1 {
@Around("execution(* foo())") // 一个 advisor 切面(切点 + 通知)
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("aspect1 around");
return pjp.proceed();
}
}
@Aspect
static class Aspect2 {
@Before("execution(* foo())") // 一个 advisor 切面
public void before() { System.out.println("aspect2 before"); }
@After("execution(* foo())") // 一个 advisor 切面
public void after() { System.out.println("aspect2 after"); }
}
@Aspect
static class Aspect3 {
@Before("execution(* bar())") // 一个 advisor 切面
public void before() { System.out.println("aspect3 before"); }
}
}
控制台打印信息
aspect1 around
aspect2 before
Target1 foo
aspect2 after
>>>>>>>>>>>>>>>>>>>>>>>>>
aspect3 before
Target2 bar
org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator
annotationAwareAspectJAutoProxyCreator 会先根据切点表达式查看是否有与切点表达式匹配的目标。
如果存在该目标的话,它就会自动创建相应的代理对象,创建的代理对象会放到单例池中。
最终我们通过 getBean() 拿到的对象都是代理对象。
当我们在调用代理对象内部的已经被增强过了的方法时,它就会找到每一个代理对象关联的 advisor 切面。
之后再通过 advisor 切面去匹配切点表达式。
匹配成功的话,它就会调用相应的通知。
annotationAwareAspectJAutoProxyCreator 类中重要的方法:
annotationAwareAspectJAutoProxyCreator 的调用时机:创建阶段、依赖注入阶段、初始化阶段
setProxyTargetClass(ture)
,统一采用 Cglib 代理。首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱
set 方法(包括成员变量)的循环依赖如图所示
示例代码
public class App60_1 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A() { log.info("A()"); }
@Autowired
public void setB(B b) {
log.info("setB({})", b.getClass());
this.b = b;
}
@PostConstruct
public void init() { log.info("init()"); }
public void foo() { System.out.println("foo()"); }
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B() { log.info("B()"); }
@Autowired
public void setA(A a) {
log.info("setA({})", a.getClass());
this.a = a;
}
@PostConstruct
public void init() { log.info("init()"); }
}
@Aspect
static class MyAspect {
@Before("execution(* foo())")
public void before() { System.out.println("before ..."); }
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
context.registerBean(MyAspect.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
context.getBean(A.class).foo();
}
}
控制台打印信息
构造方法的循环依赖如图所示,显然无法用前面的方法解决(三级缓存的思路是在对象实例创建完成后,把对象的实例 a 封装成一个工厂对象,放入 singletonFactories 缓存中的,这样 b 就可以从缓存中直接拿出 a 来完成自己内部的注入(即 setA()),进而完成整个依赖注入流程。但这里连半成品的实例对象都没有,所以无法使用上面的方法来解决这里的问题)
@Lazy
为构造方法参数生成代理(示例一)示例代码
public class App61 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(@Lazy B b) {
log.info("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() { log.info("init()"); }
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.info("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.info("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println();
}
}
控制台打印信息
public class App62 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private ObjectProvider<B> b;
public A(ObjectProvider<B> b) {
log.info("A({})", b);
this.b = b;
}
@PostConstruct
public void init() { log.info("init()"); }
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.info("B({})", a);
this.a = a;
}
@PostConstruct
public void init() { log.info("init()"); }
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println("==========================================");
System.out.println(context.getBean(A.class).b.getObject());
System.out.println(context.getBean(B.class));
System.out.println("==========================================");
}
}
控制台打印信息
ObjectProvider 是 ObjectFactory 的子类型,用 ObjectFactory 替换掉上面代码中的 ObjectProvider 也是没有问题的。
@Scope
产生代理(示例三)示例代码
circularDependency/test2/others/A.java
@Component
public class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(B b) {
log.info("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.info("init()");
}
}
circularDependency/test2/others/B.java
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.info("B({})", a.getClass());
this.a = a;
}
@PostConstruct
public void init() {
log.info("init()");
}
}
circularDependency/test2/App63.java
public class App63 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
scanner.scan("circularDependency.test2.others");
context.refresh();
System.out.println();
}
}
控制台打印信息
用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
示例代码
public class App64 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private Provider<B> b;
public A(Provider<B> b) {
log.info("A({}})", b);
this.b = b;
}
@PostConstruct
public void init() { log.info("init()"); }
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.info("B({}})", a);
this.a = a;
}
@PostConstruct
public void init() { log.info("init()"); }
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println("-------------------------------------------");
System.out.println(context.getBean(A.class).b.get());
System.out.println(context.getBean(B.class));
System.out.println("-------------------------------------------");
}
}
控制台打印信息
!注意事项:教程中给的几张图是根据解决循环依赖的演进过程而画的,并不是和源码一一对应的,在叫法上也有区别。
一级缓存:限制 bean 在 beanFactory 中只存一份,即实现 singleton scope
作用:保证单例对象仅被创建一次
getBean("a")
流程时,会创建实例对象 a,最后将成品 a 放入 singletonObjects 一级缓存getBean("a")
流程的时后,会先从一级缓存中找 a。因为此时已有成品 a,故无需再次创建。A 中有 B,B 中有 A
一级缓存无法解决循环依赖问题,分析如下
getBean("a")
a.setB()
时,需要走 getBean("b")
流程【红色箭头 1】b.setA()
时,又回到了 getBean("a")
的流程【红色箭头 2】针对 “一级缓存无法解决循环依赖问题” 的解决思路如下:
a.setB()
以及 b.setA()
会将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存这里只是教程中称其为二级缓存,Spring 中是叫它三级缓存的。可以用它来解决循环依赖问题。
对于上面的图
a = new A()
执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)
a.setB()
,走入 getBean("b")
流程【红色箭头 3】b.setA()
时,需要一个 a 对象,有没有呢?有!factories.get()
在 singletonFactories 缓存中就可以找到【红色箭头 4 】【红色箭头5】这里只是教程中称其为二级缓存,Spring 中是叫它三级缓存的。可以用它来解决循环依赖问题。
二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下
a.init
完成之后才能创建代理 pa = proxy(a)
factories.put(a)
向 singletonFactories 中放入的还是原始对象这里只是教程中称其为三级缓存,其实它是 Spring 中的二级缓存的。
工厂和缓存相互配合可以解决循环依赖中代理对象创建过晚的问题。
简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 Spring 仍然希望代理的创建时机在 init 之后。
只有在发生了循环依赖的时候,才会将代理的创建时机提前。
思路如下:
factories.put(fa)
放入 singletonFactories 的既不是原始对象,也不是代理对象,而是工厂对象 fafa -> pa || a
会检查 bean 对象是否需要 AOP
factories.put(fb)
的时候,它也会放入缓存 singletonFactories 中放入工厂对象 fbb.setA()
的时候,它会调用 factories.get()
(去 singletonFactories 去找对象),拿到了 fab.setA()
得以成功注入代理对象,保证了正确性【红色箭头 7】a.init
完成后,就无需二次创建代理了。那么从哪儿找到 pa 呢?当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处了,清除即可
单例 set 方法(包括成员变量)循环依赖,Spring 会利用三级缓存来解决,无需额外的配置
构造方法及多例循环依赖解决的办法
@Lazy
注解(使用代理方式来解决问题)@scope
注解(使用代理方式来解决问题)要求:掌握 Spring 事务失效的八种场景及原因
@Transactional(rollbackFor = Exception.class)
)try-catch
导致事务不能正确回滚
throw new RuntimeException(e);
TransactionStatus.setRollbackOnly()
@Transactional
没有保证原子行为
insert
、update
、delete
、select … for update
语句,select
方法并不阻塞@Transactional
方法导致的 synchronized 失效
commit
等操作,它们并未处于 sync 块内select … for update
替换 select
(这里更推荐解法②,在数据库层面解决原子性和加锁问题会更好)@Service
public class Service1 {
@Autowired
private AccountMapper accountMapper;
@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Transactional(rollbackFor = Exception.class)
,如此可以让事务在遇到非运行时异常时也执行回滚操作。@Service
public class Service2 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
try {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
throw new RuntimeException(e);
TransactionStatus.setRollbackOnly()
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
@Service
public class Service3 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Aspect
public class MyAspect {
@Around("execution(* transfer(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
LoggerUtils.get().debug("log:{}", pjp.getTarget());
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
@Order(Ordered.LOWEST_PRECEDENCE - 1)
(不推荐)@Service
public class Service4 {
@Autowired
private AccountMapper accountMapper;
@Transactional
void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
@Bean
public TransactionAttributeSource transactionAttributeSource() {
// 使得 Transaction 对非公共方法也生效
return new AnnotationTransactionAttributeSource(false);
}
package day04.tx.app.service;
public class Service5 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
package day04.tx.app.controller;
@Controller
public class AccountController {
@Autowired
public Service5 service;
public void transfer(int from, int to, int amount) throws FileNotFoundException {
service.transfer(from, to, amount);
}
}
@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
// ... 有事务相关配置
}
@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
// ... 无事务配置
}
现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
@Service
public class Service6 {
@Autowired
private Service6 proxy; // 本质上是一种循环依赖
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
System.out.println(proxy.getClass());
proxy.bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
@EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
((Service6) AopContext.currentProxy()).bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
@Transactional
没有保证原子行为@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
上面的代码实际上是有 bug 的,假设 from
余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况
insert
、update
、delete
、select … for update
语句,select
方法并不阻塞@Transactional
方法导致的 synchronized 失效针对上面的问题,能否在方法上加 synchronized 锁来解决呢?
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public synchronized void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
答案是不行,原因如下:
commit
等操作,它们并未处于 sync 块内select … for update
替换 select
这里涉及到一些数据库的基础知识,如果各位有兴趣了解的话,不妨看一下我之前写的关于 MySQL 的文章:【MySQL 学习笔记②】
要求
- 掌握 Spring MVC 的执行流程
- 了解 Spring MVC 的重要组件的作用
本文在这里把整个流程分成了三个阶段:准备阶段、匹配阶段、执行阶段
@RequestMapping
的优先级最高@RequestMapping
的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息@ResponseBody
注解,则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染这里笔者推荐一篇蛮不错的的文章:Spring & SpringBoot 常用注解总结
在官方提供的资料里,有一个文件:面试题-spring-注解.xmid
@EnableTransactionManagement
:启用声明式的事务控制。
@Transactional
)名称 | @EnableTransactionManagement |
---|---|
类型 | 配置类注解 |
位置 | 配置类定义上方 |
作用 | 设置当前 Spring 环境中开启注解式事务支持 |
@Transactional
:启用声明式的事务控制。名称 | @Transactional |
---|---|
类型 | 接口注解 / 类注解 / 方法注解 |
位置 | 业务层接口上方 / 业务层实现类上方 / 业务方法上方 |
作用 | 为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务) |
@Order
:控制 bean 的优先级(执行顺序)。
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy
,又需要用到代理(如事务),名称 | @EnableAspectJAutoProxy |
---|---|
类型 | 配置类注解 |
位置 | 配置类定义上方 |
作用 | 开启注解格式 AOP 功能 |
下方的三个注解虽然在 AOP 中常常出现,但它们并不是 Spring 的注解。
@Aspect
、@Pointcut
、@Before
三个注解都属于 AspectJ 框架。
AspectJ 是一个面向切面的框架,是目前最好用的 AOP 框架。
@Aspect
(org/aspectj/lang/annotation/Aspect.java
)名称 | @Aspect |
---|---|
类型 | 类注解 |
位置 | 切面类定义上方 |
作用 | 设置当前类为 AOP 切面类 |
@Pointcut
(org/aspectj/lang/annotation/Pointcut.java
)名称 | @Pointcut |
---|---|
类型 | 方法注解 |
位置 | 切入点方法定义上方 |
作用 | 设置切入点方法 |
属性 | value(默认):切入点表达式 |
@Before
(org/aspectj/lang/annotation/Pointcut.java
)名称 | @Before |
---|---|
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
@Component
:一个通用的注解,可以标注任意类为 Spring 组件
@Controller
:对应 SpringMVC 中的控制层@Service
:对应表现层@Repository
:对应数据层名称 | @Component /@Controller /@Service /@Repository |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为 Spring 管理的 bean |
属性 | value(默认):定义 bean 的 id |
@ComponentScan
:扫描包,把包里面所有标注了特定注解的类交给 Spring 容器管理名称 | @ComponentScan |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置 Spring 配置类扫描路径,用于加载使用注解格式定义的 bean |
相关属性 | value(默认):扫描路径,此路径可以逐层向下扫描 excludeFilters:排除扫描路径中加载的 bean,需要指定类别(type)和具体项(classes) includeFilters:加载指定的 bean,需要指定类别(type)和具体项(classes) |
@Conditional
@Bean
配合使用。在配置类解析 @Bean
注解时,会使用到该注解,发挥上述的功能。@Configuration
:可以用来声明配置类。可以使用 @Component
注解替代该注解,但 @Configuration
注解更加语义化。
@Bean
注解的方法相当于工厂方法@Bean
不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法(方法参数越多,注入优先级越高)@Configuration
默认会为标注的类生成代理,其目的是保证 @Bean
方法相互调用时,仍然能保证其单例特性
proxyBeanMethods = false
来关闭这个代理@Configuration
中如果含有 BeanFactory 后处理器,则实例工厂方法会导致 MyConfig 提前创建,使其依赖注入失败。
@Bean
的方法进行参数依赖注入的操作;针对 Mapper 扫描,用注解方式。名称 | @Configuration |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类为 Spring 配置类 |
属性 | value(默认):定义 bean 的 id |
@Bean
名称 | @Bean |
---|---|
类型 | 方法注解 |
位置 | 方法定义上方 |
作用 | 设置该方法的返回值作为 Spring 管理的 bean |
属性 | value(默认):定义 bean 的 id |
@Import
:指定一个类型,根据类型或类型本身找到一些其他的类,之后再把它们交给 Spring 容器管理。
@Import
先解析 @Bean
后解析@Bean
,再 Import名称 | @Import |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 导入配置类 |
属性 | value(默认):定义导入的配置类类名 当配置类有多个时使用数组格式一次性导入多个配置类 |
@Lazy
@PropertySource
名称 | @PropertySource |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 加载 properties 文件中的属性值 |
属性 | value(默认):设置加载的 properties 文件对应的文件名或文件名组成的数组 |
Scope
名称 | @Scope |
---|---|
类型 | 类注解 |
位置 | 类定义上方 |
作用 | 设置该类创建对象的作用范围 可用于设置创建出的 bean 是否为单例对象 |
属性 | value(默认):定义 bean 作用范围 默认值 singleton(单例),可选值 prototype(非单例) |
@Autowired
:@Autowired
是按照类型注入的。
@Qualifier
了,可以靠该注解来指定注入哪个名称的 bean 对象。@Qualifier
:该注解后的值就是需要注入的 bean 的名称。
@Qualifier
不能独立使用,必须和 @Autowired
一起使用名称 | @Autowired |
---|---|
类型 | 属性注解 或 方法注解 或 方法形参注解 |
位置 | 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 或 方法形参前面 |
作用 | 为引用类型属性设置值 |
属性 | required:true / false,定义该属性是否允许为 null |
名称 | @Qualifier |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 |
作用 | 为引用类型属性指定注入的 beanId |
属性 | value(默认):设置注入的 beanId |
@Value
:一般会被用在从 properties 配置文件中读取内容进行使用名称 | @Value |
---|---|
类型 | 属性注解 或 方法注解(了解) |
位置 | 属性定义上方 或 标准 set 方法上方 或 类 set 方法上方 |
作用 | 为 基本数据类型 或 字符串类型 属性设置值 |
属性 | value(默认):要注入的属性值 |
关于 SpringMVC 方面的注解,其实我之前也写过一篇博文:SpringMVC 相关注解【学习笔记】。诸位若有兴趣的话,不妨阅览一二。
@RequestMapping
:可以派生多个注解如 @GetMapping
等名称 | @RequestMapping |
---|---|
类型 | 类注解 或 方法注解 |
位置 | SpringMVC 控制器类或方法定义上方 |
作用 | 设置当前控制器方法请求访问路径 |
相关属性 | value(默认),请求访问路径 |
在 Rest 风格的代码里,每个方法的 @RequestMapping
注解中都要使用 method 属性定义请求方式,这样一来,重复性太高了。
可以使用 @GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
代替
名称 | @GetMapping + @PostMapping + @PutMapping + @DeleteMapping |
---|---|
类型 | 方法注解 |
位置 | 基于 SpringMVC 的 Restful 开发控制器方法定义上方 |
作用 | 设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如 @GetMapping 对应 GET 请求 |
相关属性 | value(默认):请求访问路径 |
@RequestBody
:使用该注解可以将请求的 body 中的 json 字符串转换为 Java 对象名称 | @RequestBody |
---|---|
类型 | 形参注解 |
位置 | SpringMVC 控制器方法形参定义前面 |
作用 | 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次 |
@ResponseBody
:把控制器方法返回的 Java 对象解析为 json 数据,然后写入响应体(不走视图解析层)名称 | @ResponseBody |
---|---|
类型 | 方法注解 或 类注解 |
位置 | SpringMVC 控制器方法定义上方和控制类上 |
作用 | 设置当前控制器返回值作为响应体,写在类上,该类的所有方法都有该注解功能 |
相关属性 | pattern:指定日期时间格式字符串 |
@RestController
使用 @RestController
注解替换 @Controller
与 @ResponseBody
注解,简化书写
名称 | @RestController |
---|---|
类型 | 类注解 |
位置 | 基于 SpringMVC 的 RESTful 开发控制器类定义上方 |
作用 | 设置当前控制器类为 RESTful 风格,等同于 @Controller 与 @ResponseBody 两个注解组合功能 |
@RestController
注解标注在类上,表明这是一个控制器 bean,直接将函数的返回值填入 HTTP 响应体中。
@ResponseStatus
:可以控制响应的状态码@ControllerAdvice
,结合 @ResponseBody
可为 @RestControllerAdvice
名称 | @RestControllerAdvice |
---|---|
类型 | 类注解 |
位置 | REST 风格开发的控制器增强类定义上方 |
作用 | 为 REST 风格开发的控制器类做增强 |
说明:此注解自带 @ResponseBody
注解与 @Component
注解,具备对应的功能
@ExceptionHandler
名称 | @ExceptionHandler |
---|---|
类型 | 方法注解 |
位置 | 专用于异常处理的控制器方法上方 |
作用 | 设置指定异常的处理方案,功能等同于控制器方法, 出现异常后终止原始控制器执行,并转入当前方法执行 |
说明:此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常。
注意
@ControllerAdvice
标注的类中,那就相当于一个全局的异常处理器一般我们可以用 @RestControllerAdvice
+ @ExceptionHandler
做统一异常处理
@PathVariable
名称 | @PathVariable |
---|---|
类型 | 形参注解 |
位置 | SpringMVC 控制器方法形参定义前面 |
作用 | 绑定路径参数与处理器方法形参间的关系,要求 路径参数名 与 形参名 一一对应 |
@RequestMapping(value="/users/{id}", method = RequestMethod.DELETE)
@ReponseBody
public String delete(@PathVariable Integer id){ }
@RequestParam
名称 | @RequestParam |
---|---|
类型 | 形参注解 |
位置 | SpringMVC 控制器方法形参定义前面 |
作用 | 绑定请求参数与处理器方法形参间的关系 |
相关参数 | required:是否为必传参数 defaultValue:参数默认值 |
@RequestParam
可以用来获取查询参数(比如 http://localhost/user?type=student
)。
此外,它还可以传递集合参数(http://localhost/listParam?likes=game&likes=music&likes=travel
)
同名请求参数可以使用 @RequestParam
注解映射到对应名称的集合对象中作为数据
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
System.out.println("集合参数传递 likes ==> "+ likes);
return "{'module':'list param'}";
}
@CrossOrigin
:可以解决 Ajax 的跨域问题
@SpringBootApplication
@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
@EnableAutoConfiguration
:主要作用是去找到自动配置类,然后把自动配置类里相关联的 bean 都注册到容器@SpringBootConfiguration
:仅说明这个是一个 Spring Boot 的配置类,几乎与原生的 @Configuration
的功能等价。只有在条件成立的时候,才会执行后续的操作
@ConditionalOnClass
,classpath 下存在某个 class 时,条件才成立@ConditionalOnMissingBean
,beanFactory 内不存在某个 bean 时,条件才成立@ConditionalOnProperty
,配置文件中存在某个我们期望的 property(键、值)时,条件才成立@ConfigurationProperties
,会将当前 bean 的属性与配置文件中的键值进行绑定,可以简化 bean 的初始化@EnableConfigurationProperties
,会添加两个较为重要的 bean
要求:掌握 SpringBoot 自动配置原理
@Configuration
的底层原理(前置知识)org/springframework/context/annotation/Configuration.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
@Configuration
:可以用来声明配置类。
可以用 @Component
注解替代该注解,但 @Configuration
注解更加语义化。
@Bean
注解的方法相当于工厂方法@Bean
不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法(方法参数越多,注入优先级越高)@Configuration
默认会为标注的类生成代理,其目的是保证 @Bean
方法相互调用时,仍然能保证其单例特性
proxyBeanMethods = false
来关闭这个代理@Configuration
中如果含有 BeanFactory 后处理器,则实例工厂方法会导致 MyConfig 提前创建,造成其依赖注入失败。
@Bean
的方法进行参数的依赖注入;针对 Mapper 扫描可以改用注解方式。注意点(一)
- 配置类相当于一个工厂,标注了
Bean
注解的方法相当于工厂方法
这个是测试代码的主方法,我先贴在这里了(之后不再赘述)。
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
// BeanFactory 的后处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean("myConfig", MyConfig.class);
context.refresh();
System.out.println();
// System.out.println(context.getBean(MyConfig.class).getClass());
}
@Configuration
static class MyConfig {
@Bean
public Bean1 bean1() {
System.out.println("bean1()");
return new Bean1();
}
}
static class Bean1 {}
@Bean
不支持方法重载注意点(二)
@Bean
不支持方法重载,如果有多个重载方法,仅有一个能入选为工厂方法- 方法的参数越多,注入的优先级越高
@Configuration
static class MyConfig {
@Bean
public Bean1 bean1() {
System.out.println("bean1()");
return new Bean1();
}
@Bean
public Bean1 bean1(@Value("${java.class.version") String a) {
System.out.println("bean1(" + a + ")");
return new Bean1();
}
@Bean
public Bean1 bean1(@Value("${java.class.version}") String a, @Value("${JAVA_HOME}") String b) {
System.out.println("bean1(" + a + "," + b + ")");
return new Bean1();
}
}
static class Bean1 { }
最终输出的是参数最多的方法
bean1(52.0,C:\Program Files\Java\jdk1.8.0_144)
注意点(三)
@Configuration
默认会为标注的类生成代理,其目的是保证@Bean
方法相互调用时,仍然能保证其单例特性。- 可以使用
proxyBeanMethods = false
来关闭这个代理。
// @Configuration
static class MyConfig {
@Bean
public Bean1 bean1(Bean2 bean2) {
System.out.println("bean1()");
System.out.println(bean2);
System.out.println(bean2);
System.out.println(bean2);
return new Bean1();
}
@Bean
public Bean2 bean2() {
System.out.println("bean2()");
return new Bean2();
}
}
static class Bean1 { }
static class Bean2 { }
上方代码类似于手动调用 set 方法建立依赖关系,最终打印的 bean2 都是同一个对象
bean2()
bean1()
annotation.ConfigurationTest$Bean2@176d53b2
annotation.ConfigurationTest$Bean2@176d53b2
annotation.ConfigurationTest$Bean2@176d53b2
当然,我们也可以不注入参数直接调用 bean2() 方法
@Configuration
static class MyConfig {
@Bean
public Bean1 bean1() {
System.out.println("bean1()");
System.out.println(bean2());
System.out.println(bean2());
System.out.println(bean2());
return new Bean1();
}
// ... ...
}
最终输出的仍然是同一个对象
bean1()
bean2()
annotation.ConfigurationTest$Bean2@4b14c583
annotation.ConfigurationTest$Bean2@4b14c583
annotation.ConfigurationTest$Bean2@4b14c583
但是如果没有在配置类上加 @Configuration
注解的话,那么程序每次都会重新创建对象
bean1()
bean2()
annotation.ConfigurationTest$Bean2@345965f2
bean2()
annotation.ConfigurationTest$Bean2@429bd883
bean2()
annotation.ConfigurationTest$Bean2@4d49af10
bean2()
@Configuration
默认会为标注的类生成代理对象,之后就不会再创建了。
这样可以保证各个(@Bean
)方法相互调用时的的单例特性。
class annotation.ConfigurationTest$MyConfig$$EnhancerBySpringCGLIB$$46049279
可以使用 proxyBeanMethods = false
来关闭这个代理。
@Configuration(proxyBeanMethods = false)
注意点(四)
@Configuration
中如果含有 BeanFactory 后处理器,则实例工厂方法会导致 MyConfig 提前创建,造成其依赖注入失败。- 解决方法:
- 改用静态工厂方法;
- 直接在
@Bean
的方法上进行参数的依赖注入;- 针对 Mapper 扫描可以改用注解方式。
@Configuration
static class MyConfig {
@Value("${java.class.version}")
private String version;
@Bean
public MapperScannerConfigurer configurer() {
MapperScannerConfigurer scanner = new MapperScannerConfigurer();
scanner.setBasePackage("aaa");
return scanner;
}
@Bean
public Bean3 bean3() {
System.out.println("bean3()" + version);
return new Bean3();
}
}
static class Bean3 { }
由下面的输出信息可以得知依赖注入失败了,原因就是配置类被提前创建了。
[INFO] 11:49:13.289 [main] -
Cannot enhance @Configuration bean definition 'myConfig' since its singleton instance has been created too early.
The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type:
Consider declaring such methods as 'static'.
bean3()null
后处理器是在 refresh() 的第五步阶段创建的
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
在上面的代码中,后处理器工厂方法作为配置类的成员方法,要创建后处理器,就必须先创建配置类的对象。
但是配置类应该在 refresh() 的第十一步阶段创建的
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
@Bean
public static MapperScannerConfigurer configurer(){ ... }
@Bean
的方法上使用参数的依赖注入(不管之前如何创建,bean3() 方法是不受影响的)@Bean
public Bean3 bean3(@Value("${java.class.version}") String version) { ... }
@Configuration
@MapperScan("aaa")
static class MyConfig { ... }
@Import
的底层原理(前置知识)org/springframework/context/annotation/Import.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
@Import
:指定一个类型,根据类型或类型本身找到一些其他的类,之后再把它们交给 Spring 容器管理。
@Import
先解析 @Bean
后解析@Bean
,再 Import下方是测试代码中的主方法,我先贴在这里了。
public class ImportTest {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean(MyConfig.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}
第一种用法:引入单个 bean
@Configuration
@Import(Bean1.class) // 引入单个 bean
static class MyConfig { }
static class Bean1 { }
控制台输出(部分)
annotation.ImportTest$MyConfig
annotation.ImportTest$Bean1
第二种用法:引入一个配置类
@Configuration
@Import(OtherConfig.class) // 引入配置类
static class MyConfig { }
@Configuration
static class OtherConfig {
@Bean
public Bean2 bean2() { return new Bean2(); }
}
static class Bean1 { }
static class Bean2 { }
控制台输出(部分)
annotation.ImportTest$MyConfig
annotation.ImportTest$OtherConfig
bean2
第三种用法:先实现 ImportSelector 接口,然后重写 selectImports 方法,最后再通过这个 Selector 引入多个类
@Configuration
@Import(MySelector.class) // 通过 Selector 引入多个类
static class MyConfig { }
static class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Bean3.class.getName(), Bean4.class.getName()};
}
}
static class Bean3 { }
static class Bean4 { }
输出结果(部分)
annotation.ImportTest$MyConfig
annotation.ImportTest$Bean3
annotation.ImportTest$Bean4
由上面的输出结果可以得知 ImportSelector 接口的实现类并不会变成一个 Bean,它只是一个选择器。
第四种用法:通过 beanDefinition 注册器
@Configuration
@Import(MyRegistrar.class) // 通过 beanDefinition 注册器来注册 bean
static class MyConfig {
}
static class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("bean5", BeanDefinitionBuilder.genericBeanDefinition(Bean5.class).getBeanDefinition());
}
}
控制台输出(部分)
annotation.ImportTest$MyConfig
bean5
同上面介绍的选择器一样,注册器自身是不会变成 Bean 的
解析规则
- 同一配置类中,
@Import
先解析@Bean
后解析- 同名定义,默认后面解析的会覆盖前面解析的
public class ImportSelectorTest {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// beanFactory.setAllowBeanDefinitionOverriding(false); // 不允许同名定义覆盖
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
context.registerBean(MyConfig.class);
context.refresh();
System.out.println(context.getBean(MyBean.class));
}
@Configuration
@Import(OtherConfig.class)
static class MyConfig { // 主配置(程序员编写)
@Bean
public MyBean myBean() { return new Bean1(); }
}
@Configuration
static class OtherConfig { // 从属配置(自动配置)
@Bean
public MyBean myBean() { return new Bean2(); }
}
interface MyBean { }
static class Bean1 implements MyBean { }
static class Bean2 implements MyBean { }
}
输出结果
annotation.ImportSelectorTest$Bean1@49438269
显然,运行上方的代码没有报错,可以正常运行。
在有同名定义的 bean 的时候,默认后面解析的会覆盖前面解析的。
同一配置类中,@Import
先解析 @Bean
后解析。
我们可以在拿到 beanFactory 后,通过设置它的属性,让其不能使用同名定义覆盖的规则
beanFactory.setAllowBeanDefinitionOverriding(false); // 默认值是 true,这里设置为 false(不允许同名定义覆盖)
此时再次运行上方的代码后,控制台输出报错信息(这里只贴了部分关键信息)
Exception in thread "main"
org.springframework.beans.factory.support.BeanDefinitionOverrideException:
Invalid bean definition with name 'myBean' defined in annotation.
ImportSelectorTest$MyConfig: Cannot register bean definition [ ... ] for bean 'myBean':
There is already [ ...
factoryBeanName=annotation.ImportSelectorTest$OtherConfig;
factoryMethodName=myBean;
defined in annotation.ImportSelectorTest$OtherConfig]
bound.
那么在不允许覆盖的情况下,如何能够让 MyConfig(主配置类) 的配置优先呢?
使用 @ConditionalOnMissingBean
注解?程序依然会报错
@Import
注解先解析的,这时候容器里还没有 MyConfig。正确解法是使用 DeferredImportSelector,延迟 Import 操作,让它最后工作。
可以简单认为先解析主配置中的 @Bean
,最后才解析 @Import(XXX.class)
中出现的类。
DeferredImportSelector 是 ImportSelector 的子接口
public interface DeferredImportSelector extends ImportSelector
使用 DeferredImportSelector,可以延迟 Import 操作,让它最后工作。
可以简单认为先解析主配置中的 @Bean
,最后才解析 @Import(XXX.class)
中出现的类。
在 SpringBoot 的自动装配里会用到它:
DeferredImportSelector +
@ConditionalOnMissingBean
(条件检查)
public class DeferredImportSelectorTest {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
beanFactory.setAllowBeanDefinitionOverriding(false);
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
context.registerBean(MyConfig.class);
context.refresh();
System.out.println(context.getBean(MyBean.class));
}
@Configuration
@Import(MySelector.class)
static class MyConfig { // 主配置
@Bean
public MyBean myBean() { return new Bean1(); }
}
static class MySelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{OtherConfig.class.getName()};
}
}
@Configuration
static class OtherConfig { // 从属配置
@Bean
@ConditionalOnMissingBean
public MyBean myBean() { return new Bean2(); }
}
interface MyBean { }
static class Bean1 implements MyBean { }
static class Bean2 implements MyBean { }
}
运行上面的程序后,控制台没有报错,成功打印了 Bean1
annotation.DeferredImportSelectorTest$Bean1@672872e1
@SpringBootApplication
)@SpringBootConfiguration
是一个组合注解:由 @ComponentScan
、@EnableAutoConfiguration
和 @SpringBootConfiguration
组成
@SpringBootConfiguration
与普通 @Configuration
相比
@SpringBootConfiguration
所注释的配置类在整个 app 中只可以出现一次@ComponentScan
:组件扫描
@EnableAutoConfiguration
也是一个组合注解,由下面注解组成
@AutoConfigurationPackage
:用来记住扫描的起始包@Import(AutoConfigurationImportSelector.class)
:用来加载 META-INF/spring.factories 中的自动配置类
org/springframework/boot/autoconfigure/SpringBootApplication.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@Import
直接引入自动配置类有两个原因:
@Import(自动配置类.class)
,引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置因此,采用了 @Import(AutoConfigurationImportSelector.class)
org/springframework/boot/autoconfigure/EnableAutoConfiguration.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
org/springframework/boot/autoconfigure/AutoConfigurationImportSelector.java
public class AutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware,
EnvironmentAware, Ordered { ... }
要求:掌握 Spring 中常见的设计模式
请大家区分 singleton pattern 与 Spring 中的 singleton bean
当然 Spring 中也用到了单例模式,例如
org.springframework.transaction.TransactionDefinition#withDefaults
org.springframework.aop.TruePointcut#INSTANCE
org.springframework.aop.interceptor.ExposeInvocationInterceptor#ADVISOR
org.springframework.core.annotation.AnnotationAwareOrderComparator#INSTANCE
org.springframework.core.OrderComparator#INSTANCE
定义:Separate the construction of a complex object from its representation so that the same construction process can create different representations
它的主要亮点有三处:
Spring 中体现 Builder 模式的地方:
org.springframework.beans.factory.support.BeanDefinitionBuilder
org.springframework.web.util.UriComponentsBuilder
org.springframework.http.ResponseEntity.HeadersBuilder
org.springframework.http.ResponseEntity.BodyBuilder
定义:Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses
根据上面的定义,可以得知 Spring 中的 ApplicationContext 与 BeanFactory 中的 getBean 都可以视为工厂方法
在 getBean 方法中,它实际上是隐藏了 bean (产品)的创建过程和具体实现
Spring 中的其它工厂:
org.springframework.beans.factory.FactoryBean
@Bean
标注的静态方法及实例方法前两种工厂主要封装第三方的 bean 的创建过程,后两种工厂可以推迟 bean 创建,解决循环依赖及单例注入多例等问题
定义:Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces
在 Spring 中,有两处的实现是比较典型的:
org.springframework.web.servlet.HandlerAdapter
:
@RequestMapping
标注的控制器实现@Controller
注解)的实现org.springframework.beans.factory.support.DisposableBeanAdapter
定义:Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly
典型实现有:
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
org.springframework.web.servlet.view.ViewResolverComposite
composite 对象的作用是,将分散的调用集中起来,提供了一个统一的调用入口。
它的特征是与具体干活的实现实现同一个接口。
当调用 composite 对象的接口方法时,其实是委托具体干活的实现来完成。
定义:Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality
典型实现:
org.springframework.web.util.ContentCachingRequestWrapper
定义:Provide a surrogate or placeholder for another object to control access to it
典型实现:
org.springframework.aop.framework.JdkDynamicAopProxy
org.springframework.aop.framework.ObjenesisCglibAopProxy
定义:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it
典型实现:
org.springframework.web.servlet.HandlerInterceptor
定义:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically
典型实现:
org.springframework.context.ApplicationListener
org.springframework.context.event.ApplicationEventMulticaster
org.springframework.context.ApplicationEvent
定义:Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it
典型实现:
org.springframework.beans.factory.support.InstantiationStrategy
org.springframework.core.annotation.MergedAnnotations.SearchStrategy
org.springframework.boot.autoconfigure.condition.SearchStrategy
定义:Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure
典型实现:
这里推荐某位视频博主的 Spring 相关视频
这个 UP 主做的小动画也是蛮不错的,如果感兴趣的话,诸位也可以看看。(PS:重点是他做了小动画,蛮形象的,没有那么干)