Spring @Autowired注解解析

前言

在我们学习 spring 的时候,尤其是使用注解的方式实现自动装配,总会感到很神奇,也产生了很多的疑问。

  • 注解是什么时候被解析的?
  • 注解的生效逻辑是什么?

以@Autowired 为例,为什么加了这个直接就可以直接得到对应的属性实例,并调用它的方法呢?

我们越学感觉到疑问越多,而光看源码又容易被带偏,总是一个问题深入研究下去就找不到回来的路了。

本文试着从问题的角度出发,边看源码边解答这些疑问,这样思路清晰一些。

阅读本文时,我假定阅读者已经粗略的看过 springioc 和 springaop 相关的源码,有一定的基础,所以可能非关键步骤会略过。

解答

下面跟着一起来看看吧,大家打开自己的源码工程跟着一起做。

我们知道 spring 的启动配置有 3 中方式:xml 的方式、注解的方式、java 配置方式,其实主要分析 xml 和注解方式就可以了,他们在解析注解有一定的差异,我们分开分析。

我们主要分析 xml 方式和注解方式启动,解答之前我们先构建一下项目代码:

XML 配置文件的方式启动服务

注解是什么时候被解析的

测试启动类

public class MainStart {
   
    
    public static void main(String[] args) {
   
        ApplicationContext context = new ClassPathXmlApplicationContext("beans/beans2.xml");
    }
}

定义几个 bean

// 使用注解方式注入
@Service(value = "ioc2")
public class IOCService2Impl implements IOCService {
   
	
	@Autowired
	@Qualifier(value = "iocService")
	private IOCService iocService;
	
	@Override
	public String hollo() {
   
		return "Hello,IOC";
	}
}
// 使用配置文件的方式注入
public class IOCServiceImpl implements IOCService {
   
    
    @Override
    public String hollo() {
   
        return "Hello,IOC";
    }
}
// 接口
public interface IOCService {
   
    public String hollo();
}

配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/tool"
		xmlns:context="http://www.springframework.org/schema/context"
		xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
		default-autowire="byName">
    <!-- 手动注入bean -->
	<bean class="top.ybq.ioc.beanlifecycle.xml2.IOCServiceImpl" id="iocService"/>
    <!-- 开启扫包自动注入 -->
	<context:component-scan base-package="top.ybq.ioc.beanlifecycle.xml2"/>
</beans>

我们在 org.springframework.context.support.AbstractApplicationContext#refresh 这个方法打个断点然后启动

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

image-20200426155755286

image-20200426155955985

我们看到第一张图 BeanFactory 是 null ,然后我们跳到下一步看第二张图,这里又有值了,说明 spring 在 xml 配置方式启动的时候,会在这个方法里去解析所有的注解。

obtainFreshBeanFactory()做了什么?

我们跟踪到了 AbstractRefreshableApplicationContext#refreshBeanFactory

@Override
protected final void refreshBeanFactory() throws BeansException {
   
    if (hasBeanFactory()) {
   
        destroyBeans();
        // 关闭工厂
        closeBeanFactory();
    }
    try {
   
        /**
		 * 创建 DefaultListableBeanFactory 类型的 BeanFactory,目前还没有任何属性
		 * 这个类超级强大,所有关于容器的接口、抽象类它都继承了
		 */
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        /**
		 * 为容器设置一个序列化 id
		 */
        beanFactory.setSerializationId(getId());
        /**
		 * 设置 BeanFactory的2个配置属性, 是否允许bean覆盖,是否允许循环引用
		 */
        customizeBeanFactory(beanFactory);
        /**
		 * 加载我们的 bean 定义(最最最主要的作用就是保存我们的传递进去的配置类)
		 * 此方法是一个抽象方法,需要交给子类去实现,用于加载自定义的 BeanDefinition
		 * 其中 4 个子类:
		 * FileSystemXmlApplicationContext: 从文件系统加载配置类
		 * ClassPathXmlApplicationContext: 从 classpath 加载配置文件,子类 AbstractXmlApplicationContext 实现了此方法,一般用 xml 配置启动的,看这个类的实现
		 * AnnotationConfigWebApplicationContext: 以注解的方式加载配置的bean
		 * XmlWebApplictaionContext:
		 */
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
   
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
   
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

看我的注释其他的方法大致做什么就知道了,我们去看AbstractXmlApplicationContext#loadBeanDefinitions方法,跟进到XmlBeanDefinitionReader#loadBeanDefinitions,然后XmlBeanDefinitionReader#doLoadBeanDefinitions

spring 框架中真正干活的方法都是 do、parse 等开头的,所以看到这几个前缀就知道它要干活了。

// 摘取部分代码
try {
   
    // 使用 document 的方式读取配置文件信息
    Document doc = doLoadDocument(inputSource, resource);
    /**
	 * 依据 document 信息解析 bean,注册 BeanDefinition,关键位置
	 */
    int count = registerBeanDefinitions(doc, resource);
    if (logger.isDebugEnabled()) {
   
        logger.debug("Loaded " + count + " bean definitions from " + resource);
    }
    return count;
}

跟进之后

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   
    // 构建读取 document 的工具类
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 获取已经注册的 bean 的数量
    int countBefore = getRegistry().getBeanDefinitionCount();
    /**
	 * 注册BeanDefinition, 看 registerBeanDefinitions() 方法
	 */
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 总注册 bean 数量减去之前注册的数量,就是本次注册的 bean 数量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

再次看到了 do 方法DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions

这个方法的第一部分的作用是:获取 <beans … profile="***" /> 中的 profile参数与当前环境是否匹配,如果不匹配则不再进行解析。

这个和环境变量相关,我们先不讨论,看后面的DefaultBeanDefinitionDocumentReader#parseBeanDefinitions方法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   
    /**
	 * 判定是否是默认节点,spring 默认的标签有且只有 4 个:<import/>、<alias>、<bean>、<beans>
	 * spring 只有 4 个默认标签,其他的标签都属于自定义标签,
	 * 比如:<tx:annotation-driven/>、注解扫描标签<context:component-scan/>,对应的是 注解 @Component @trans...
	 * 这些都是自定义节点
	 */
    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;
                if (delegate.isDefaultNamespace(ele)) {
   
                    /**
					 * 默认标签处理,我们不展开,留给读者自己去跟踪,如果按照demo 中的代码,这里就是解析 <bean> 节点
					 */
                    parseDefaultElement(ele, delegate);
                }
                else {
   
                    /**
					 * 自定义标签的解析, 这是我们重点分析的
					 */
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
   
        // 自定义标签解析
        delegate.parseCustomElement(root);
    }
}

老铁们,好像跟到这里有点苗头了,我们看到了和标签相关的说明。

good

跟进到这里,parse 开头的方法就是真正干活的苦力。

默认标签不是我们分析的重点,大家参考自定义标签的分析再去看看就好了,我们这里分析的是注解什么时候解析的,所以我们只关注自定义标签。

来,继续看自定义标签的解析

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
   
    // 获取命名空间,
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
   
        return null;
    }
    // 通过 namespace 获取到 NamespaceHandler,重点看 resolve() 方法
    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));
}

我们调试到这里断点发现

image-20200426162226931

这个就是来处理我们自定义标签<context:component-scan base-package="top.ybq.ioc.beanlifecycle.xml2"/>,同时我们得到了一个叫做命名空间的串:"http://www.springframework.org/schema/context”和我们在 xml 文件中配置的xmlns:context="http://www.springframework.org/schema/context”关联起来的。

那么这个 namespaceUri 有什么作用呢,我们跟着注释去看看 reslove 方法DefaultNamespaceHandlerResolver#resolve

public NamespaceHandler resolve(String namespaceUri) {
   
    /**
	 * 获取所有一级配置的 handler ,getHandlerMappings():方法获取到所有命名空间对应的 uri,
	 * 比如 beans.xml 文件中配置包扫描的时候用到的自定义标签<context:component-scan xx>
	 *     它的对应的 uri = http://www.springframework.org/schema/context,
	 *
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值