Spring学习Bean的扫描注册
Posted pur_e
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring学习Bean的扫描注册相关的知识,希望对你有一定的参考价值。
在前面的文章《使用IDEA创建Spring mvc工程及简要分析》中,稍微讲过MVC寻找配置文件的过程,现在在这个基础上,看一下配置文件是如何加载的,着重看一下Bean的扫描注册过程。其实稍微用过Spring的人都知道,Bean可以通过Xml配置文件与注解两种方式来配置,看过本文后可以看到,这两种方式最后调用的都是相同的接口进行Bean的注册,只不过扫描的过程不一样。
一、配置文件读取
上面文章最后提到XmlWebApplicationContext类,找到了名为mvc-dispatcher-servlet.xml的配置文件,现在继续跟踪是如何处理这个文件的。
FrameworkServlet.java:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//取上下文类,默认为XmlWebApplicationContext
Class<?> contextClass = getContextClass();
...
//配置上下文类
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
//初始化上下文,这是非常重要的一个函数,很多Spring相关属性,Bean注册,初始化都是在这里完成的
wac.refresh();
}
AbstractApplicationContext.java:
//这个函数就直接都贴出来了,不过其他逻辑暂不分析,只看解析配置文件,注册Bean相关
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//就是这一句
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset ‘active‘ flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//刷新Bean工厂
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
AbstractRefreshableApplicationContext.java:
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//加载Bean配置到Bean工厂
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
XmlWebApplicationContext.java:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
...
//使用XmlBeanDefinitionReader去加载相关Bean配置
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
//这会取到我们的配置文件,也就是mvc-dispatcher-servlet.xml,终于找到你了^_^
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
到这里,看到了我们的配置文件mvc-dispatcher-servlet.xml,后面的代码会去分析并注册其中配置的Bean。
XmlBeanDefinitionReader.java:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
...
//读取配置文件mvc-dispatcher-servlet.xml为InputStream,及额外的异常处理后,继续加载
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
...
}
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
//加载XML文件
Document doc = doLoadDocument(inputSource, resource);
//继续加载
return registerBeanDefinitions(doc, resource);
}
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(getEnvironment());
int countBefore = getRegistry().getBeanDefinitionCount();
/*加载Bean配置,这里有一个重要处理,会加载XML不同命名空间对应的处理类
在函数createReaderContext(resource)中,创建DefaultNamespaceHandlerResolver
后面在处理自定义节点时,会调用这里的处理函数
*/
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
这里的DefaultNamespaceHandlerResolver还是需要详细说一下的,这个类会添加不同命名空间的处理函数:
- 有一个命名空间对应处理类的配置
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = “META-INF/spring.handlers”;
在各个spring的jar包中会找到这个文件 - 如spring-context-4.1.1.RELEASE-sources.jar!/META-INF/spring.handlers,文件中有配置:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
- 这样,不同的Xml节点,会调用各自命名空间的处理类,如:
<context:component-scan base-package="com.springapp.mvc"/>
调用:
org.springframework.context.config.ContextNamespaceHandler
来处理
- 处理类中,针对不同的节点,也会注册不同的处理类:
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
- 后续处理到context:component-scan节点时,就会调用ComponentScanBeanDefinitionParser处理类来处理,这个等后面提到解析不同节点时会再提到
- 知道了这个流程,我们就可以注册自己的命名空间,添加自己的处理类
DefaultBeanDefinitionDocumentReader.java:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
//继续加载
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
...
//这个函数在碰到<beans>会有递归调用,有一部分保证递归调用正确的逻辑
//默认空函数
preProcessXml(root);
//扫描注册Bean
parseBeanDefinitions(root, this.delegate);
//默认空函数
postProcessXml(root);
...
}
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
//默认命名空间"http://www.springframework.org/schema/beans",调用默认处理,主要是import,alias,bean节
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)) {
parseDefaultElement(ele, delegate);
}
else {
//调用自定义处理,如component-scan节
delegate.parseCustomElement(ele);
}
}
}
}
else {
//调用自定义处理,如component-scan节
delegate.parseCustomElement(root);
}
}
从这里开始分两种不同处理
- 一种是默认命名空间,如bean节,这种就是直接在Xml文件中配置Bean定义
- 另一种如component-scan配置,在Xml中配置后,会根据配置,到包中去分析注解,也就是最终Bean是通过注解来定义的
二、XML文件中定义Bean
在XML中最简单的Bean定义,定义一个bean name为loginService的bean
<bean id="loginService" class="com.springapp.mvc.service.impl.LoginServiceImpl"/>
接着上一节看一下,是如何解析的:
DefaultBeanDefinitionDocumentReader.java:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
//处理<import>
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
//处理<alias>
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
//处理<bean>
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
//递归处理<beans>
doRegisterBeanDefinitions(ele);
}
}
/**
* Process the given bean element, parsing the bean definition
* and registering it with the registry.
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
/*
处理<bean>内部各种属性定义如id/class,子节点如constructor-arg/property等
这些都是对bean进行限制或说明的定义,非常重要的函数
*/
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//处理自定义的属性,可以通过之前说过的配置命名空间来实现,默认为空
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//bean定义完成,真正注册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));
}
}
这里又分两部分来说,一个是Bean的定义解析,一个是Bean的注册,注册稍微简单一些
1. Bean定义解析
Bean的定义很复杂,主要是可选配置项太多,当然,做为一个框架容器,这是不可避免的,即使我们平时使用的选项很少,在真正用到的时候无法实现,就不美了。
这里不会详细去分析配置如何解析,只会跟踪相关代码位置,后续有用得到的,才可能会去研究。
/**
* Parses the supplied {@code <bean>} element. May return {@code null}
* if there were errors during parse. Errors are reported to the
* {@link org.springframework.beans.factory.parsing.ProblemReporter}.
*/
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
...
//检查beanName的唯一性
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//解析定义
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
...
}
//真正解析Bean定义的函数
/**
* Parse the bean definition itself, without regard to name or aliases. May return
* {@code null} if problems occurred during the parsing of the bean definition.
*/
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
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);
}
//创建Bean定义,通过类名与parent定义
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析Bean属性,如scope/lazy-init等
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析meta
parseMetaElements(ele, bd);
//处理lookup-method子节点配置
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//处理方法替换replaced-method子节点配置
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//处理constructor-arg子节点配置
parseConstructorArgElements(ele, bd);
//处理property子节点配置
parsePropertyElements(ele, bd);
//处理qualifier子节点配置
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
2.Bean注册
BeanDefinitionReaderUtils.java
/**
* Register the given bean definition with the given bean factory.
* @param definitionHolder the bean definition including name and aliases
* @param registry the bean factory to register with
* @throws BeanDefinitionStoreException if registration failed
*/
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
//注册Bean
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}
org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition:
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...
synchronized (this.beanDefinitionMap) {
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
//如果Bean已经有定义
if (!this.allowBeanDefinitionOverriding) {
//配置不允许覆盖定义,异常报错
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 + "]");
}
}
}
else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
//添加Bean定义,其实就是一个map,key是beanName
this.beanDefinitionMap.put(beanName, beanDefinition);
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
//如果Bean被新定义覆盖,将之前所有注入过的,销毁掉,以后需要时再重新创建
resetBeanDefinition(beanName);
}
}
三、使用自动扫描注解来定义Bean
使用XML配置Bean,当项目文件非常多时,会非常麻烦,所以在Spring在2.5以后版本,提供了使用注解来进行Bean配置。首先,我们在配置文件中配置:
<!--配置包解析配置-->
<context:component-scan base-package="com.springapp.mvc"/>
这是最简单的配置,其他选项以后再分析,先看看是如何实现自动扫描注解添加Bean的。
第一节其实已经提到过,有一个专门解析context:component-scan节点的类:org.springframework.context.annotation.ComponentScanBeanDefinitionParser
@Override
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;
}
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan:
/**
* Perform a scan within the specified base packages,
* returning the registered bean definitions.
* <p>This method does <i>not</i> register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
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) {
//找到包下所有Bean定义
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//生成beanName,后面的操作和Xml文件中添加Bean的流程就是一样的了
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//进行Bean注册,里面调用的是和第二节注册时相同的方法
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
后面太细的就不再跟进去了。这种方法扫描的是@Component及其特例化的注解@Repository、@Service 和 @Controller所注解的类,当前这4个注解是完全一样的,只是用来标记Bean。
四、总结
以后这么细的去看框架代码会比较少了,因为这样看代码反而钻了牛角尖,学不到多少东西,甚至还不如多写多用,多看相关框架的书籍,学习框架的设计思路。
以上是关于Spring学习Bean的扫描注册的主要内容,如果未能解决你的问题,请参考以下文章
Spring 3.0 学习-DI 依赖注入_创建Spring 配置-使用一个或多个XML 文件作为配置文件,使用自动注入(byName),在代码中使用注解代替自动注入,使用自动扫描代替xml中bea(