暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

为什么Spring中xml注入Bean优先于注解

来搞笑的Yuan 2020-08-10
810

上周遇到一个的注入bean的问题,而且是发生在常用的@Autowired注解上,于是点开源码,调试到报错的代码,进行探究。本文有点长,而且源码居多,目前还不太会组织读源码的文章,因为Spring实在是太过庞大,写的不清楚,就当记录了。

问题描述

项目是同时由xml和注解配置的bean,通常xml里面引入的是外部服务的bean,在其中引入了bean如下

<bean id="classA" class="com.java.packagea.ClassA" init-method="init">
</bean>

通过注解引入另一个bean如下:

package com.java.packageb


@Service("classA")
public class ClassA{
}

在某个ClassB中,需要引入packageB中的ClassA,如下

package com.java.packagec


import com.java.packageb.ClassA


@Component
public class ClassB{

@Autowired
ClassA classA;
}

启动Spring报错can not autowired class[com.java.packageb.ClassA]。

经排查,在Spring的BeanFactory中,只加载了xml的bean,没有加载注解注释的同名bean。

Spring加载Bean的源码

开始读源码,先找到 AbstractApplicationContext
,通篇看一下这个类的目录结构,我们知道AbstractApplicationContext最顶层继承的是BeanFactory,其重要用途就是加载Bean和获取Bean,抛开细枝末节,来到refresh()方法,在refresh的第一行打一个断点,可以大致看到容器启动以后是如何调用到refresh()方法,可以看到web.xml中配置的 ContextLoaderListener
中有一个 initWebApplicationContext
方法,继而调用到 ContextLoader
initWebApplicationContext
方法,最终会调用到ApplicationContext的具体实现类的refresh方法。Spring中大量使用了模板方法,在父类中写了方法调用流程,而具体的方法实现由子类完成,所以读源码的时候可以从基类开始读,需要看具体实现时再打开子类查看。回到 AbstractApplicationContext
的refresh方法,和Bean加载相关的主要看前两句,如下:

  public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 检查环境中的占位符是否完整
prepareRefresh();


// Tell the subclass to refresh the internal bean factory.
// 将需要注入的业务bean转化为beandefinition,初始化BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();


// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);


//后续代码省略
}
}

进入obtainFreshBeanFactory()看下具体做了哪些事

  protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}


首先这是一个抽象方法,所以具体的实现是在子类中的,从方法上的注释可以看出这个是初始化BeanFactory的方法,我们点击这个refreshBeanFactory(),可以看到是个抽象方法

  protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

进入具体的实现类,可以看到AbstractApplicationContext下有很多实现类,又分为两个分支,一个基类是 AbstractRefreshableApplicationContext
,一个是 GenericApplicationContext
,从名字和 注释可以了解到前者是可以多次调用refresh()方法,后者只能调用一次refresh()方法,找到 AbstractRefreshableApplicationContext
下面的 refreshBeanFactory
方法,如下

  @Override
protected final void refreshBeanFactory() throws BeansException {
//如果已有beanFactory,执行clean
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建默认的DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();

beanFactory.setSerializationId(getId());
// 设置BeanFactory的一些参数,是否可以循环引用,是否可以覆盖等
customizeBeanFactory(beanFactory);
// 关键的一步,将bean definition加载到 bean factory
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

继续看,loadBeanDefinitions是个抽象方法,我们点到一个子类实现,也就是说进入了AbstractRefreshableApplicationContext的子类中,我们找到xml配置对应的web项目的子类实现, XmlWebApplicationContext
,实现如下:

  @Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
    // 生成beanFactory对应的XmlBeanDefinitionReader,实际上是将beanFactory注入,在后续方法设置对应的值
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);


// Configure the bean definition reader with this context's
// resource loading environment.
// 环境配置
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));


// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
// 父类方法是一个空方法,主要留给子类实现。
initBeanDefinitionReader(beanDefinitionReader);

// 加载BeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}

继续看下loadBeanDefinitions里面实际做了什么,在这个类中继续找这个方法(而不是直接看哪里调用了这个类,这样会跳到父类的抽象方法里)

  protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//加载Resource列表,看子类实现,从classPath加载xml或者从文件加载Resource
Resource[] configResources = getConfigResources();

//如果加载到Resources,直接用reader解析
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//没有加载到Resources,用过configLocation地址进行解析
//即看子类是否自己完成加载,如果没有则父类用location完成Resource的注入
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

而后我们进入到 XmlBeanDefinitionReader
,进入父类 AbstractBeanDefinitionReader
找到 loadBeanDefinitions(Stringlocation)
方法,可以看到在通过地址找配置时,最终是通过自己实现的ResourcePatternResolver解析自定义的路径下的配置,转化为Resource数组。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}


if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//用自定义的ResourcePatternResolver加载location下面的配置
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}

...省略

对于有配置的情况,

  @Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
//由子类XmlBeanDefinitionReader实现具体加载方法
counter += loadBeanDefinitions(resource);
}
return counter;
}

再次回到子类XmlBeanDefinitionReader中找到loadBeanDefinitions方法,先转换为一个包装类,然后进入load方法

  @Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 包装为有编码的Resource
return loadBeanDefinitions(new EncodedResource(resource));
}


public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}


Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
//这里是一处防止循环加载的设计,将已经加载的EncodedResource放入Set<EncodedResource>类型的Threadlocal对象,每次往里面添加,如果set失败说明重复引用了Source,会报错循环加载
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}

// 进行加载的实际方法
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}

上面通过HashSet类的ThreadLocal来保证重复引用Source的时候抛出异常,我们继续进入 doLoadBeanDefinitions
,截取前半部分代码

  try {
//将流转化为Document对象,这部分是Dom处理和Spring本身关系不大,略过
Document doc = doLoadDocument(inputSource, resource);
//注册bean
return registerBeanDefinitions(doc, resource);
}

...异常处理省略

直接进入上面的registerBeanDefinitions

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//documentReader DefaultBeanDefinitionDocumentReader为实际的类型
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
//开始注册dom中的具体内容
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

继续进入的registerBeanDefinitions方法

  @Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获得root节点,开始解析
Element root = doc.getDocumentElement();
//解析
doRegisterBeanDefinitions(root);
}

进入解析方法

  protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;

//这段逻辑防止递归调用时不断生成子delegate,每次操作完以后将初始的delete重新赋值回原来的值

this.delegate = createDelegate(getReaderContext(), root, parent);


if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
//抽象空方法 交给子类实现
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
//抽象空方法 交给子类实现
postProcessXml(root);


this.delegate = parent;
}

继续进入到 parseBeanDefinitions

  protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
//处理默认命名空间的bean,例如http://www.springframework.org/schema/beans 开头的内置的bean
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
//其他类型的外部命名空间的bean,比如公司自建的组件的bean
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}

进入 parseDefaultElement
,发现有针对不同类型的解析,其中跳过import,alias的解析,直接进入bean的解析

  private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}

//这一步是bean解析
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

可以看到上面是把最开始的delegate委托类一直引入到这个方法中了,进入processBeanDefinition,

  protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//这一句即为实际的解析,其中Bean
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

直接进入delegete委托类 BeanDefinitionParserDelegate
找到parseBeanDefinitionElement方法,我们分解一下这块代码,太长,首先前部分是做一些校验和预处理

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//获取id标签对应的值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取name标签你对应的值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

//将nameStr转化为数组,即别名的数组
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}


//如果有id,则id即为beanName,否则用别名做name
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}

if (containingBean == null) {
//这里会对beanName的唯一性进行判断
checkNameUniqueness(beanName, aliases, ele);
}
....

查看 checkNameUniqueness
方法,可以看到如果beanName即xml中的id如果重复会报错,在userNames里面维护了一个列表,通过校验的beanName会存放在这个Set里面。

  protected void checkNameUniqueness(String beanName, List<String> aliases, Element beanElement) {
String foundName = null;


if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
foundName = beanName;
}
if (foundName == null) {
foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases);
}
if (foundName != null) {
error("Bean name '" + foundName + "' is already used in this <beans> element", beanElement);
}


this.usedNames.add(beanName);
this.usedNames.addAll(aliases);
}

继续查看上面的方法,进入到解析部分,解析部分调用到了这个方法:

    public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {


//将beanName放入一个栈结构中,用于追踪当前解析到哪里
this.parseState.push(new BeanEntry(beanName));


String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}


try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}

//创建AbstractBeanDefinition对象
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//用element对bd赋值
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));


parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());


parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);


bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
//返回bd
return bd;
}
...
}


读到这里大致清楚了如何将XMl转化为Java类即Spring认识的BeanDefinition,流程大致是将xml文件通过XmlBeanDefinitionReader将流组装成的Resource进行解析,reader将Resource转成Document,再委托BeanDefinitionParserDelegate类来解析Document中的每个Element。最终将Element解析为BeanDefinition

回到XmlBeanDefinitionReader,我们看下解析完成以后,reader又做了什么

  protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
// 拿到BeanDefinitionHolder,对Bean进行注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

看下注册具体有哪些操作,进入BeanDefinitionReaderUtils

  public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {


// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
// 对bean和BeanDefinition进行注册
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());


// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

继续看registry做了什么,进入DefaultListableBeanFactory

  @Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {


Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");


if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}


BeanDefinition oldBeanDefinition;


oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {

//如果存在这个BeanName且不允许继承Bean 抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}

//beanDefinitionNames 添加beanName
//beanDefinitionMap 中添加beanName和beanDefinition
else {
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);


if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}

读完上面这段我们可以知道,最终是将XML中的配置转化为了BeanDefinition,并且将BeanName和BeanDefinition存放在Map中,供后续调用。

从上面我们也可以知道,如果是XML注入的话,回去看BeanFactory中是否已经有同名的BeanName,如果有且配置为不允许复写,才会报错,否则后面注入的bean将前面的bean覆盖。

然后我们来快速看看注解形式是如何生效的,首先我们在xml中配置

<context:component-scan base-package="com.xxx"/>

我们看如何调用到注解的bean,首先回到 DefaultBeanDefinitionDocumentReader
这个类的委托这段代码,当不是http://www.springframework.org/schema/beans的命名空间时,调用下面这个方法

  if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}


进入这个方法,看到

  public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
//进入这个方法
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

说实话,看到这里有点晕,因为这个readerContext是前面注入进来的,大致可以猜出是某个地方set了一个map这里又拿出来,一通回看时候我已经彻底晕了。决定删繁就简先看下我要关注的问题,直接debug过来,发现handler.parse走到了NamespaceHandlerSupport这个类。

  @Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}

继续下去,最终通过这个map映射到了实际的执行类里面ComponentScanBeanDefinitionParser

  public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);


// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
//这一步 注册所有的注解bean
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);


return null;
}

进去看注入的bean怎么操作

  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
//这里有个校验,很容易看出,校验通过就进入注册bean到Beanfactory
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}

所以我们看看这个check方法是做什么的

  protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
//如果beanFactory里面没有这个bean,直接return true 进行register
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
//找到beanFactory中的该beanName对应的bean
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
// 关键判断如果判断为true 则不会注入 我们看看判断是什么
if (isCompatible(beanDefinition, existingDef)) {
return false;
}
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}

在BeanFactory里面有个关键判断,如果为true则会跳过后面的注入逻辑,我们看看这里判断是什么

  protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
newDefinition.getSource().equals(existingDefinition.getSource()) || // scanned same file twice
newDefinition.equals(existingDefinition)); // scanned equivalent class twice
}

其中ScannedGenericBeanDefinition类基于ASM ClassReader,与AnnotatedGenericBeanDefinition不同,用于能够被Spring容器扫描到的类(例如被@Component注解的类), 这里的意思是,如果已经注册的类不是注解类,这个判断为true,外层返回false,就会跳过注册逻辑。

所以本文最开始的疑问得到解开。根据我们的注解组织顺序,xml的bean的引入时间早于component-scan配置,则先注入xml,当存在同名bean时,注解注入会因为上述原因(已存在BeanFactory的bean不是注解类型)跳过注册,导致最终注入的是xml里面的bean。反过来,先注入了注解的bean,随后加载到xml配置时,如果遇到同名的beanName,直接选择覆盖。因此无论顺序如何,最终同名的beanName注入在beanFactory里面的一定是xml里面的配置。

经过测试也验证了上述观点。

总结

读Spring源码建议可以跳过细枝末节有目的性地读,否则会被复杂的继承结构绕晕。本次阅读也学到了一些模板方法,委托方法,包括用ThreadLocal做循环调用判断等技巧,没事读一读还是有所收获的。本文研究的内容也是在xml和注解同时存在时会出现的问题,实际工作中可能改一下bean的id或者统一使用一种方式即可避免。


文章转载自来搞笑的Yuan,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论