john's tech blog

hope is coming


  • 首页

  • 标签

  • 归档

悟透Spring之Xml配置路径查找的秘密

发表于 2021-08-25

字符串路径怎么查找XML资源

Spring中使用Xml配置时会使用ResourceLoader查找,官网文档里提到了很重要的一句:

NOTE:After you learn about Spring’s IoC container, you may want to know more about Spring’s Resource abstraction (as described in Resources), which provides a convenient mechanism for reading an InputStream from locations defined in a URI syntax. In particular, Resource paths are used to construct applications contexts, as described in Application Contexts and Resource Paths.

这边我们先直接定位到最主要的一个实现类PathMatchingResourcePatternResolver,文章最后会解释为什么会是这个类以及它的作用。

classpath: 查找资源

我们通过spring-bean*.xml查找Xml配置文件的时候,流程如下:

PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver

DefaultResourceLoader

ClassPathResource

ClassPathResource

ClassLoader

也就是classLoader.getResource返回一个classpath路径,所以如果spring-bean.xml不存在该路径下那么就找不到了。

classpath* 带通配符查找资源

而我们通过classpath*:spring-bean*.xml来查找的时候,流程如下:

image-20210825105623631

image-20210825105702416

findAllClasspathResources

image-20210825105812891

image-20210825110030410

addAllClassLoaderJarRoots

所以区别还是发生在PathMatchingResourcePatternResolver类的getResources方法内部

1
2
@Override
public Resource[] getResources(String locationPattern) throws IOException {}

ClasspathXmlApplicationContext加载BeanDefinition会调用AbstractBeanDefinitionReader类的这个方法,方法内部会去获取当前的ResourceLoader:

AbstractBeanDefinitionReader#loadBeanDefinitions

AbstractBeanDefinitionReader

那么当前的ResourceLoader到底是哪个呢?我们又要回到AbstractXmlApplicationContext去加载BeanDefinition的时候:XmlBeanDefinitionReader实例化后会把当前ApplicationContext设置为它的ResourceLoader

AbstractXmlApplicationContext#loadBeanDefinitions

AbstractXmlApplicationContext

为啥能设置呢?因为当前ApplicationContext的父类AbstractApplicationContext继承了DefaultResouceLoader。

1
public abstract class AbstractApplicationContext extends DefaultResourceLoader{}

且因为AbstractApplicationContext实现了ApplicationContext接口,ApplicationContext接口我们知道是继承自ResourcePatternResolver接口,从上篇文章的类图中我们可以一窥全貌。

所以从location获取资源就会调用AbstractApplicationContext的getResources方法:

1
2
3
4
5
6
7
8
//---------------------------------------------------------------------
// Implementation of ResourcePatternResolver interface
//---------------------------------------------------------------------
@Override
public Resource[] getResources(String locationPattern) throws IOException {
//todo 调用 resourcePatternResolver去 getResources 默认是 2020-09-05
return this.resourcePatternResolver.getResources(locationPattern);
}

这边就很重要了,resourcePatternResolver就是在构造函数里实例化的

1
2
3
4
5
6
7
8
9
10
/**
* Create a new AbstractApplicationContext with no parent.
*/

public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}

protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}

到这里我们就看到了本文一开始提到的PathMatchingResourcePatternResolver,需要注意的是在实例化该ResourcePatternResolver的时候把当前ResourceLoader当作构造函数参数传了进去。ResourcePatternResolver最终返回一个Resource数组给到AbstractBeanDefinitionReader。

BeanFactory接口的实现理解

发表于 2021-08-11 | 更新于 2021-08-13

BeanFactory接口中定义了获取Bean的若干方法,而这些方法分别由不同的抽象类实现,我们知道在Java中抽象类实现接口的时候不是一定要实现接口中的每个方法的,Spring就是巧妙地利用了这点来设计它的架构。下面我们来逐一看这些方法的作用以及分别是由哪些类来实现的。

getBean方法

1
Object getBean(String name) throws BeansException;

从方法名就可以看出,用户可以传入Bean的名称获取到对应的Bean实例,这个实例可以是单例也可以是原型的,如果是单例就返回引用,如果是原型那么每次都返回不一样的实例,这个方法的api文档中有一句话说的比较好:

This method allows a Spring BeanFactory to be used as a replacement for the Singleton or Prototype design pattern.

该方法使得Spring中的BeanFactory成功应用了单例或者原型设计模式。文档中还提到了一点:

Translates aliases back to the corresponding canonical bean name

这句话代表如果传入的name是别名,那么会根据它对应的真正的bean名称去查找bean实例,就像Vlad Mihalcea在Why I like Spring bean aliasing中所说的那样,如果根据别名查找,那么实际会依据别名背后真正的bean名称去查找,那么原来如果存在跟这个别名相同的bean名称将被忽略,这种方法在需要切换数据源测试的时候非常有用,因为你只要改配置就好了。可以简单通过下面的截图源码了解下:

image-20210813125606260

image-20210813125520863

image-20210813125648792

核心部分就是通过循环遍历aliasMap集合查找是否有该参数对应的别名Key,如果有那么再按照名称尝试作为别名Key去查找

这个方法最后还提到了一点:

Will ask the parent factory if the bean cannot be found in this factory instance

也就是说在当前bean容器中找不到的话还会去父容器中去查找。

这个getBean方法还有个重载的方法,对比上面就多传入了一个requiredType参数,方法签名如下:

1
<T> T getBean(String name, Class<T> requiredType) throws BeansException;

这个方法就强大多了,除了拥有上面的方法具备的功能之外,首先它支持了泛型,也就是返回类型不用再强制转换了。从它的api文档描述就可以看出:

Behaves the same as {@link #getBean(String)}, but provides a measure of type safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the required type. This means that ClassCastException can’t be thrown on casting the result correctly, as can happen with {@link #getBean(String)}.

也就是提供了类型安全的保障,Java的ClassCastException就不可能抛出了,getBean(String)对返回结果强制转换的时候有可能会遇到这个异常。那Spring是怎么做到这点的呢?

image-20210813130339775

也就是通过Java中Class类的isInstance方法判断

另外api文档也提到了如下:

requiredType type the bean must match; can be an interface or superclass

参数requiredType可以是接口或者父类型,这个也是比较人性化的功能吧。

本文还会持续更新。。。

从Primary注解的角度看Spring的设计之美

发表于 2021-06-22

前言

上篇文章我们总结了开发Spring应用时出现NoUniqueBeanDefinitionException问题的解决方案,那么这篇文章我们就来回答下存在多个相同类型的Bean时Primary注解标注的Bean怎么就能够成功注入,也可以借此从点到面领略下Spring的设计之美。

揭秘

Primary注解的本质

还记得我们在使用xml配置Bean的时候也可以使用primary属性吗?其实它本质就是会在生成BeanDenifinition的时候设置它的primary属性为true。以下示例代码在BeanDefinitionParserDelegate类中可以找到。

1
2
3
4
//设置primary 属性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}

那么我们可以猜想下Primary注解是否也是做了同样的事情呢?答案是肯定的。

Spring在扫描它关注的类后,会检查是否标注有Primary注解,如果有的话就设置该BeanDefinitiond的primary属性为true.

扫描关注类的示例源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
//省略部分代码
if (candidate instanceof AnnotatedBeanDefinition) {
//内部会处理@Primary注解
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
//内部调用 GenericApplicationContext registerBeanDefinition 2020-09-11
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

AnnotationConfigUtils内部会继续对该candidate(候选者)的一些注解做处理:

1
2
3
4
5
6
7

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
//省略部分源码
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
}

那么标注Bean注解的方法上的Primary注解是否也是这样类似处理的呢?可以说Spring内部尽量让封装进行到底,我们来一探究竟:

1
2
3
4
5
6
7
8
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
//省略前后代码
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
//省略前后代码
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
}

看到最后一行代码又利用了AnnotationConfigUtils.processCommonDefinitionAnnotations方法来处理我们的Primary注解。

到这里我们又可以猜测Spring最终还是对BeanDefinition的primary属性做筛选判断。

Primary筛选机制

Spring在获取Bean的时候首先会获取到所有的符合所需类型的Bean,我们直奔主题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private <T> NamedBeanHolder<T> resolveNamedBean(
ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException
{


//调用ListableBeanFactory接口的getBeanNamesForType方法
//去 beanDefinitionNames 中去找
String[] candidateNames = getBeanNamesForType(requiredType);

if (candidateNames.length > 1) {
List<String> autowireCandidates = new ArrayList<>(candidateNames.length);
for (String beanName : candidateNames) {
//去 beanDefinitionMap 中去查找 没有 或者 找到了
//只要有一个是 autowireCandidate的 那就只会去找 autowireCandidates的
if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
autowireCandidates.add(beanName);
}
}
//如果autowireCandidates不为空 那就重新赋值candidateNames
//也就是只关心autowireCandidate的 bean
if (!autowireCandidates.isEmpty()) {
candidateNames = StringUtils.toStringArray(autowireCandidates);
}
}
}

上述代码出自DefaultListableBeanFactory,我们可以归纳下这段代码的主要逻辑:首先获取符合Bean类型的所有Bean名称集合,如果集合包含的数量大于1,那么就再遍历该集合继续筛选:如果容器中不包含该beanName所对应的BeanDefinition或者该对应的BeanDefinition的AutowireCandidate属性为true(可以理解为符合自动注入候选者之一),那么就加入autowireCandidates集合。最后如果autowireCandidates集合不为空,那么就把candidateNames重新引用autowireCandidates集合。这里我们也就可以解释上篇文章提到的把相同类型的Bean其中一个的autowire-candidate属性设为false也可以解决问题的原因了。

Spring不是筛选到这里就结束了,它会继续在最新的candidateNames集合上做筛选。请注意下述代码紧跟上述代码之后:

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
private <T> NamedBeanHolder<T> resolveNamedBean(
ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException
{


//紧跟上述代码逻辑
//获取到的候选名称只有1个的情况
if (candidateNames.length == 1) {
String beanName = candidateNames[0];
//内部调用getBean方法
//还是调用 父 抽象类 AbstractBeanFactory的getBean方法 根据名称去get
return new NamedBeanHolder<>(beanName, (T) getBean(beanName, clazz, args));
}
else if (candidateNames.length > 1) {////获取到的候选名称有多个的情况
Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
for (String beanName : candidateNames) {
//如果 已经存在单例对象了 singletonObjects 中 且参数为空
if (containsSingleton(beanName) && args == null) {
//直接获取bean
Object beanInstance = getBean(beanName);
candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance));
}
else {
candidates.put(beanName, getType(beanName));
}
}
//如果候选的bean数量大于1
//检查是否是有Primay
String candidateName = determinePrimaryCandidate(candidates, clazz);
if (candidateName == null) {
//https://blog.csdn.net/gamezjl/article/details/108572474
candidateName = determineHighestPriorityCandidate(candidates, clazz);
}
if (candidateName != null) {
Object beanInstance = candidates.get(candidateName);
if (beanInstance == null || beanInstance instanceof Class) {
beanInstance = getBean(candidateName, clazz, args);
}
return new NamedBeanHolder<>(candidateName, (T) beanInstance);
}
if (!nonUniqueAsNull) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
}
}
}

我们主要关注的还是candidateNames集合数量大于1的情况,一上来就会遍历candidateNames集合把对应的beanName以及根据beanName获取到的bean实例获取该bean类型放入candidates这个LinkedHashMap键值对集合里。然后就到了跟本文最为密切相关的函数里了,也就是

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

```java
protected String determinePrimaryCandidate(Map<String, Object> candidates, Class<?> requiredType) {
String primaryBeanName = null;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
//也会去父容器里判断是否是Primary的
if (isPrimary(candidateBeanName, beanInstance)) {
if (primaryBeanName != null) {
//判断当前bean工厂是否有对应的beandefinition
boolean candidateLocal = containsBeanDefinition(candidateBeanName);
boolean primaryLocal = containsBeanDefinition(primaryBeanName);
//判断是否有多于1个的primary bean
if (candidateLocal && primaryLocal) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"more than one 'primary' bean found among candidates: " + candidates.keySet());
}
else if (candidateLocal) {
primaryBeanName = candidateBeanName;
}
}
else {
primaryBeanName = candidateBeanName;
}
}
}
return primaryBeanName;
}

这段函数代码逻辑不算复杂,主要就是遍历candidates键值对集合对象(在这里我们也可以学到Map集合遍历的方法:**可以根据它的

1
2
3
4
5
6
7
8
9
10

```java
protected boolean isPrimary(String beanName, Object beanInstance) {
if (containsBeanDefinition(beanName)) {
return getMergedLocalBeanDefinition(beanName).isPrimary();
}
BeanFactory parent = getParentBeanFactory();
return (parent instanceof DefaultListableBeanFactory &&
((DefaultListableBeanFactory) parent).isPrimary(beanName, beanInstance));
}

这里一开始又判断了下是否包含bean名称对应的beanDefintition,如果当前beanFactory有该beanDefintition的话就获取合并过后的BeanDefinition然后返回它的isPrimary属性,没有就去parentBeanFactory中尝试获取。

也就是说isPrimary函数中做了两件事:

  1. 去本地Bean工厂判断是否有这个Bean定义,有就合并(简单理解合并主要是为了可以获取到继承Bean中的属性)并且返回Primary属性。
  2. 本地Bean工厂不存在该Bean定义那么就会去ParentBeanFactory中去执行相同逻辑了。

借助源码我们可以清晰地明白Primary筛选的主要过程了。如果Primary筛选过后发现没有符合的该怎么办呢?Spring不是到此结束了,它会继续尝试筛选Priority相关的属性,也就是我们上篇文章提到的解决方案。

Primary筛选失败处理

Primary筛选失败就进入

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

```java
protected String determineHighestPriorityCandidate(Map<String, Object> candidates, Class<?> requiredType) {
String highestPriorityBeanName = null;
Integer highestPriority = null;
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateBeanName = entry.getKey();
Object beanInstance = entry.getValue();
if (beanInstance != null) {
//获取优先级@Priority注解
//通过 OrderComparator 来比较
Integer candidatePriority = getPriority(beanInstance);
if (candidatePriority != null) {
if (highestPriorityBeanName != null) {
//如果优先级一样就抛出异常
if (candidatePriority.equals(highestPriority)) {
throw new NoUniqueBeanDefinitionException(requiredType, candidates.size(),
"Multiple beans found with the same priority ('" + highestPriority +
"') among candidates: " + candidates.keySet());
}
else if (candidatePriority < highestPriority) {
highestPriorityBeanName = candidateBeanName;
highestPriority = candidatePriority;
}
}
else {
highestPriorityBeanName = candidateBeanName;
highestPriority = candidatePriority;
}
}
}
}
return highestPriorityBeanName;
}

跟determinePrimaryCandidate函数差不多的逻辑,也是筛选candidates对象集合,主要我们关注它内部的

1
2
3
4
5
6
7
8
9

```java
protected Integer getPriority(Object beanInstance) {
Comparator<Object> comparator = getDependencyComparator();
if (comparator instanceof OrderComparator) {
return ((OrderComparator) comparator).getPriority(beanInstance);
}
return null;
}

到这里我们可以终于看到上篇文章提到的dependencyComparator的身影了,至于getPriority内部如何去判断已经无关大碍,限于本篇篇幅,有兴趣的朋友可以自行去Spring源码中一探究竟。

总结

从一个Primary注解的角度看Spring的设计:Spring内部的逻辑处理步骤比较深,虽然如此还是保持地很严密,几乎看不出任何漏洞且留给开发人员扩展的点非常多

Spring应用抛出NoUniqueBeanDefinitionException异常时该怎么办

发表于 2021-06-13 | 更新于 2021-06-17

前言

我们在开发Spring应用时可能会不小心注入两个相同类型的Bean,比如实现了两个相同Service接口的类,示例伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface SampleService {
String getName();
}

class ServiceA implements SampleService{
String getName(){
return "john";
}
}
class ServiceB implements SampleService{
String getName(){
return "wonder";
}
}

这时候我们用SampleService接口注入

1
2
@Autowired
SampleService sampleService;

启动应用后,Spring就会优雅地提示如下错误:

Exception in thread “main” org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.john.primary.SampleService’ available: expected single matching bean but found 2: ServiceA,ServiceB

但是我们不想报错且想获取其中某一个Bean,这时候我们该怎么办呢?

解决方案

既然包含了两个相同类型的Bean,通常来说我们只要把其中一个Bean不注入就好,那如果我们想保留这两个相同类型的Bean,但是又想让SampleService正常注入呢?

如果我们是用早期Spring的Xml配置Bean时,可以使用如下两种方式解决:

  1. 那么我们可以在其中一个Bean配置里加上autowire-candidate=”false”

    1
    2
    <bean id="serviceA" class="com.john.primary.ServiceA" />
    <bean id="serviceB" class="com.john.primary.ServiceB" autowire-candidate="false" />
  2. 或者在其中一个Bean配置里加上primary=”true”:

    1
    2
    <bean id="serviceA" class="com.john.primary.ServiceA" primary="true"/>
    <bean id="serviceB" class="com.john.primary.ServiceB" />
  3. 采用javax.annotation.Priority注解

    这种方式需要我们在BeanFactory里加上dependencyComparator,示例代码如下:

    1
    2
    3
    4
    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getBeanFactory();
    //@Priority注解比较
    beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
    SampleService sampleService= context.getBean(SampleService.class);
  4. 实现注解Order或者实现org.springframework.core.Ordered接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //@Order
    public class ServiceA implements SampleService,Ordered {

    @Override
    public int getOrder() {

    return 0;
    }

    @Override
    public String toString() {
    return "ServiceA{}";
    }
    }

    这种方式需要我们重写AnnotationAwareOrderComparator的getPriority方法,示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class PriorityOrderComparator extends AnnotationAwareOrderComparator {
    /**
    * Shared default instance of {@code PriorityOrderComparator}.
    */

    public static final PriorityOrderComparator INSTANCE = new PriorityOrderComparator();

    @Override
    public Integer getPriority(Object obj) {
    //先获取Priority
    Integer order = super.getPriority(obj);
    if(order == null)
    //获取Order注解或者Ordered接口返回值
    return super.findOrder(obj);
    return order;
    }
    }

我们还可以使用目前流行的注解方式来实现,Spring文档中也提到过:

Because autowiring by type may lead to multiple candidates, it is often necessary to have more control over the selection process. One way to accomplish this is with Spring’s @Primary annotation. @Primary indicates that a particular bean should be given preference when multiple beans are candidates to be autowired to a single-valued dependency. If exactly one primary bean exists among the candidates, it becomes the autowired value.

那么可以使用如下方式:

  1. @Primary注解:

    该注解可以标注在类上或者方法上,示例如下:

    1
    2
    3
    4
    5
    6
    7
    @Primary
    @Component
    class ServiceA implements SampleService{
    String getName(){
    return "john";
    }
    }

    注解在有@Bean注解的方法上:

    1
    2
    3
    4
    5
    @Bean
    @Primary
    SampleService sampleService(){
    return new ServiceA();
    }
  2. 还是采用Xml配置中的第三或者第四种解决方案,只是采用第四种方案的话还是需要重新扩展AnnotationAwareOrderComparator

总结

这篇文章介绍了解决org.springframework.beans.factory.NoUniqueBeanDefinitionException异常的一些解决方案,从这些解决方案可以看出Spring框架的设计精妙,留给我们开发者的扩展点实在很多了。下篇文章我们来了解下这些解决方案的实现原理,如果你也有其他的解决方案,请留言告诉我。

每日算法之爬楼梯

发表于 2020-07-09

今天在看极客时间直播的时候,看到老师面试的时候出了一道爬楼梯的题目,感觉值得学习,他是用python

来实现的,题目如下:

假设你正在爬楼梯,需要n阶才能到达楼顶。
每次你可以爬1或2个或3个台阶,你有多少种不同的方法可以爬到楼顶呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution(object):

def climbStairs(self,n):
steps = [1,2,3]
self._dfs(n,[],steps)

def _dfs(self,n,res,steps):
if(n == 0):
print(res);
return
for(step in steps):
if(n >= step):
//res+[step]即为已经走的台阶种数+当前走的一种台阶数
self._dfs(n - step,res+[step],steps);
1234…47

John

232 日志
43 标签
GitHub Twitter
欢迎关注我的公众号:沉迷Spring
© 2023 johnwonder
由 Hexo 强力驱动 v3.2.0
|
主题 – NexT.Pisces v7.1.1
23031 | 24292
0%