为什么要强调SpringBoot中的BeanDefinition加载过程?

在阅读很多相对比较旧的讲解Spring容器的书籍或文章时,由于当时SpringBoot并不是很盛行,甚至还没有SpringBoot,导致对于Spring容器启动的讲解并没有提到与SpringBoot容器启动过程的差异,导致很多读者默认为这两者是一样的。

事实上,在容器启动时,传统的配置式的Spring项目与使用了SpringBoot的项目在启动时大体上是相近的,但是区别也还是不小的,本文要提到的BeanDefinition 加载过程中是其中一个例子。

阅读全文 »

初始BeanFactory

Spring中,如果我们需要从IOC容器中获取一个对象,在不考虑自动注入的场景,可以通过调用BeanFactory#getBean(String)方法。

这个方法不但承担了从容器中取出已创建好的对象这一个职责,容器在启动的时候,依次初始化所有需要实例化的对象时,也调用了这个对象,所以我们可以判断这个方法应该有两个功能:

  1. 如果容器中存在该对象,则直接返回
  2. 如果容器中不存在该对象,则创建

其中第一点的实现方式很容易猜到,应该是有一个Map对象,其中存储了beanNamebean的映射关系,在调用getBean方法时,从Map中查询,如果存在该对象,则直接返回。

所以本文主要分析第二种情况,当bean不存在时,是如何进行初始化的。

为了更加深入理解bean初始化的过程,本文还会解释如下几个问题,读者可以带着这几个问题进行阅读:

  1. Spring支持的扩展接口一文中提到的跟bean初始化相关的扩展接口是如何实现的?
  2. 如果两个bean 循环引用的情况如何处理?
  3. 如果两个bean互相依赖(depend-on)如何处理?
  4. SingletonPrototype在实现上的差别是什么?
阅读全文 »

初识ProxyFactory

Spring常用工具一文中提到,ProxyFactory可以用来手动生成代理类,以实现对某些对象进行增强。

简单再贴一下ProxyFactory的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Bean {
public String hello() {
return "hello";
}

public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Bean());
proxyFactory.addAdvice((MethodInterceptor) invocation -> {
if (!"hello".equals(invocation.getMethod().getName())) {
return invocation.proceed();
}
return "elin, " + invocation.proceed() + "!";
});
Object proxy = proxyFactory.getProxy();
System.out.println(((Bean) proxy).hello());
}
}

当需要生成一些对象的代理类进行增强需求时,用这个工具比自己手动使用动态代理等方式创建代理类方便许多。

其实,ProxyFactory也是Spring AOP的实现方式,本文将解析一下ProxyFactory的实现方式。

阅读全文 »

本文为翻译稿,翻译自How to Synchronize Blocks by the Value of the Object in Java

以下为翻译正文,译者英文水平有限,有些复杂语句没有直译,而是翻译成了自己理解的内容。

问题

有时候,我们需要通过一个变量来同步一个代码块。

为了理解遇到的问题,我们假设有一个银行业务系统,系统中的转账业务在每次客户端操作时都需要做如下的操作:

  1. 通过一个外部系统的服务(CashBackService)计算转账时的返现(cash back)金额
  2. 在数据库中执行转账操作(AccountService)
  3. 在返现计算系统中更新数据

这个现金转账操作流程大概如下:

1
2
3
4
5
6
7
public void withdrawMoney(UUID userId, int amountOfMoney) {
synchronized (userId) {
Result result = externalCashBackService.evaluateCashBack(userId, amountOfMoney);
accountService.transfer(userId, amountOfMoney + result.getCashBackAmount());
externalCashBackService.cashBackComplete(userId, result.getCashBackAmount());
}
}
阅读全文 »

代理

ProxyFactory

当需要手动给Bean创建代理对象时,一般情况下通过CGLib或动态代理实现,在有些情况下还需要根据对象的类型区分使用生成代理的方式,可以通过ProxyFactory快速生成一个代理类,并支持多个切面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Bean {
public String hello() {
return "hello";
}

public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Bean());
proxyFactory.addAdvice((MethodInterceptor) invocation -> {
if (!"hello".equals(invocation.getMethod().getName())) {
return invocation.proceed();
}
return "elin, " + invocation.proceed() + "!";
});
Object proxy = proxyFactory.getProxy();
System.out.println(((Bean) proxy).hello());
}
}

AopUtils

Spring中,通过一些方式获取到的Bean可能已经被增强,即可能被代理,获取被代理的对象可能会有一些意想不到的情况发生,例如通过代理对象获取类注解时可能将会获取不到。

AopUtils提供一些方法来判断是否为代理对象,例如AopUtils#isAopProxy;也可以调用AopUtils#getTargetClass获取到被代理的对象类型等等。

Bean

BeanDefinitionBuilder

有些场景下,需要手动注册BeanDefinition,可以通过BeanDefinitionBuilder进行创建BeanDefinition。建议通过BeanDefinitionBuilder#genericBeanDefinition创建,因为GenericBeanDefinition涵盖了RootBeanDefinitionChildBeanDefinition的功能。

1
2
3
4
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Bean.class);
//通过变量名设置属性
definitionBuilder.addPropertyValue("name", "elin");
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();

ClassPathBeanDefinitionScanner

在一些场景下,可能需要手动扫描某个路径下的类进行处理。例如MyBatis通过扫描某个路径下的mapper接口自动生成mapper实例。

ClassPathBeanDefinitionScanner#scan(String)将会扫描入参路径,将其中的类解析为BeanDefinition注册到当前容器中。

1
2
3
4
5
6
7
8
9
BeanDefinitionRegistry registry = null;
ApplicationContext applicationContext = null;

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
scanner.setResourceLoader(applicationContext);
//此处设置扫描包含类的过滤去,AnnotationTypeFilter将会通过注解进行过滤,举个例子,只有加了Mapper注解的类会被注册进来
scanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
//要扫描的包路径
scanner.scan("com.xxelin.mappers");

如果有必要,例如修改扫描到的BeanDefinition,可以重写doScan方法;如果不满足于addIncludeFilteraddExcludeFilter两类过滤器,可以重写isCandidateComponent方法手动写条件进行过滤。