在上一篇博客中提到过Spring中的标签包括默认标签和自定义标签两种,而这两种标签的用法和解析方式存在着很大不同,今天的博客主要来讲解默认标签的解析过程。
默认标签的解析是在parseDefaultElement函数中进行,函数中的功能和逻辑一目了然,分别对4种不同的标签(import,alias,bean和beans)做了不同的处理。
如何进入到这个函数中呢?我相信使用idea开发工具的小伙伴肯定很有经验的写一个测试方法,并在parseDefaultElement方法中打一个断点,即可查看到方法调用链了。我们先随便写一个标签解析例子。举一个什么样的例子呢?举一个lookup-method使用例子吧。
1.创建父类
public class User { public void showMe() { System.out.println("i am user"); } }
2.创建子类并覆盖showMe方法
public class Student extends User { public void showMe() { System.out.println("i am student "); } } public class Teacher extends User { public void showMe() { System.out.println("I am Teacher"); } }
3. 创建调用方法
public abstract class GetBeanTest { public void showMe() { this.getBean().showMe(); } public abstract User getBean(); }
4.创建测试方法
public static void main(String[] args) { ApplicationContext bf = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_41_50/spring43.xml"); GetBeanTest test = (GetBeanTest) bf.getBean("getBeanTest"); test.showMe(); }
到现在为止,除了配置文件外,整个测试方法就完成了,如果之前没有接触过获取器注入的读者们可能会疑问:抽象方法还没有被实现,怎么可以直接调用呢?答案就在Spring帮我们提供的获取器中,看看配置文件是怎样配置的呢?
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="getBeanTest" class="com.spring_1_100.test_41_50.test43.GetBeanTest"> <lookup-method name="getBean" bean="teacher" ></lookup-method> </bean> <bean id="teacher" class="com.spring_1_100.test_41_50.test43.Teacher"></bean> <bean id="student" class="com.spring_1_100.test_41_50.test43.Student"></bean> </beans>
当getBean方法配置的bean标签是teacher时,结果输出
I am Teacher
当getBean方法配置的bean标签是student时,结果输出
I am Student
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { //对import标签的处理 if (delegate.nodeNameEquals(ele, "import")) { importBeanDefinitionResource(ele); } //对alias标签处理 else if (delegate.nodeNameEquals(ele, "alias")) { processAliasRegistration(ele); } //对bean标签处理 else if (delegate.nodeNameEquals(ele, bean)) { processBeanDefinition(ele, delegate); } //对beans标签处理 else if (delegate.nodeNameEquals(ele, beans)) { doRegisterBeanDefinitions(ele); } }
bean标签的解析及注册
在4种标签的解析中,对bean标签的解析最为复杂也最为重要的,所以我们从此标签开始深入分析,如果能理解此标签的解析自然会迎刃而解,首先我们进入函数processBeanDefinition(ele,delegate)。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
一看,似乎一头雾水,没有以前的函数那样清晰的逻辑了,大致逻辑总结如下:
- 首先委托给BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHoler,经过这个方法后,bdHolder实例己经包含我们配置文件中的各种属性了,例如class,name,id,alias之类的属性。
- 当返回的bdHolder不为空的情况下,若存在默认标签子节点下再有自定义属性,还需要再次对自定义标签进行解析。
- 解析完成后,需要对解析后的bdHolder进行注册,同样注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
- 最后发出响应事件,通知相关监听器,这个bean己经加载完成了。
bean标签的解析及注册
下面我们是针对各个操作作具体分析,首先我们从元素解析及信息提取开始,也就是BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入BeanDefinitionDelegate类的parseBeanDefinitionElement方法。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); } public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { String id = ele.getAttribute("id"); String nameAttr = ele.getAttribute("name"); List<String> aliases = new ArrayList<String>(); //分割name属性 if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; "); aliases.addAll(Arrays.asList(nameArr)); } 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) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { //如果不存在beanName,那么根据Spring中提供的命名规则为当前bean生成对应的beanName if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); //如果仍然可能,则为纯bean类名注册一个别名,如果生成器返回了类名加后缀, //则这是Spring 1.2 / 2.0向后兼容所期望的。 String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
以上便是对默认标签解析的全部过程了,当然 ,Spring的解析犹如洋葱剥皮一样,一层一层的进行,尽管现在只能看到对属性id及name的解析,但是很庆幸,思路我们己经了解了,在开始对属性展开全面解析前,Spring在外层又做了一个当前层功能架构,在当前层完成主要工作包括如下内容。
- 提取元素的id和name属性。
- 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
- 如果检测到bean没有指定beanName,那么使用默认规则为此bean生成beanName。
- 将获取到的信息封装到BeanDefinitionHolder的实例中。
我们将进一步查看步骤2对标签其他属性的解析过程。
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; //解析class属性 if (ele.hasAttribute("class")) { className = ele.getAttribute("class").trim(); } try { String parent = null; //解析parent属性 if (ele.hasAttribute("parent")) { parent = ele.getAttribute("parent"); } AbstractBeanDefinition bd = createBeanDefinition(className, parent); //创建用于承载属性的AbstractBeanDefinition类型GenericBeanDefinition parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description")); //解析元数据 parseMetaElements(ele, bd); //解析lookup-method属性 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); //解析replace-method属性 parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析构造函数参数 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; }
终于,bean标签的所有属性,无论常用还是不常用的我们都看到了,尽管有些复杂的属性还需要进一步的解析,不过丝毫不会影响我们兴奋的心情,接下来,我们继续一些复杂标签的属性解析。
创建用于属性承载的BeanDefinition。
BeanDefinition是一个接口,在Spring中存在三种实现,RootBeanDefinition,其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表示形式,<bean>元素标签拥有class,scope,lazy-init等配置属性,BeanDefinition则提供了相应的beanClass,scope,lazyInit属性,BeanDefinition和<bean>中的属性是一一对应的,其中RootBeanDefinition是最常用的实现类,它对应一般性的<bean>元素标签,GenericBeanDefinition是自2.5版本以后加入新的bean文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而没有父<bean>的<bean>就使用RootBeanDefinition表示,AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中,Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是Map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息,它们之间的关系如下图所示。
由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例,而代码createBeanDefinition(className,parent)的作用就是实现此功能。
protected AbstractBeanDefinition createBeanDefinition(String className, String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader()); } public static AbstractBeanDefinition createBeanDefinition( String parentName, String className, ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); bd.setParentName(parentName); //parentName可能为空 if (className != null) { if (classLoader != null) { //如果classLoader不为空,则使用传入的classLoader同一虚拟机加载类对象,否则只是记录className bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }
2.解析各种属性
当我们创建了bean信息的承载实例后,便可以进行bean信息的各种属性的解析了,首先我们进入parseBeanDefinitionAttribute方法,parseBeanDefinitionAttributes方法是对element所有元素的属性进行解析。
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, BeanDefinition containingBean, AbstractBeanDefinition bd) { //解析scope属性 if (ele.hasAttribute("singleton")) { //scope与singleton两个属性只能指定其中之一,不可以同时出现,否则Spring会报错。 error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); } else if (ele.hasAttribute("scope")) { bd.setScope(ele.getAttribute("scope")); } else if (containingBean != null) { //在嵌入的beanDefinition情况下且没有单独指定的scope属性,则使用父类默认的属性 bd.setScope(containingBean.getScope()); } //解析abstract属性 if (ele.hasAttribute("abstract")) { bd.setAbstract("true".equals(ele.getAttribute("abstract"))); } //解析lazy-init属性 String lazyInit = ele.getAttribute("lazy-init"); if ("default".equals(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } //若没有设置或设置成其他字符都会被设置为false bd.setLazyInit("true".equals(lazyInit)); //解析autowire属性 String autowire = ele.getAttribute("autowire"); bd.setAutowireMode(getAutowireMode(autowire)); //解析dependency-check属性 String dependencyCheck = ele.getAttribute("dependency-check"); bd.setDependencyCheck(getDependencyCheck(dependencyCheck)); if (ele.hasAttribute("depends-on")) { String dependsOn = ele.getAttribute("depends-on"); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, ",; ")); } //解析autowire-candidate属性 String autowireCandidate = ele.getAttribute("autowire-candidate"); if ("".equals(autowireCandidate) || "default".equals(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern != null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate("true".equals(autowireCandidate)); } //解析primary属性 if (ele.hasAttribute("primary")) { bd.setPrimary("true".equals(ele.getAttribute("primary"))); } //解析init-method属性 if (ele.hasAttribute("init-method")) { String initMethodName = ele.getAttribute("init-method"); if (!"".equals(initMethodName)) { bd.setInitMethodName(initMethodName); } } else { if (this.defaults.getInitMethod() != null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } } //解析解析destory-method属性 if (ele.hasAttribute("destroy-method")) { String destroyMethodName = ele.getAttribute("destroy-method"); bd.setDestroyMethodName(destroyMethodName); } else { if (this.defaults.getDestroyMethod() != null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } } //解析factory-method属性 if (ele.hasAttribute("factory-method")) { bd.setFactoryMethodName(ele.getAttribute("factory-method")); } //解析factory-bean属性 if (ele.hasAttribute("factory-bean")) { bd.setFactoryBeanName(ele.getAttribute("factory-bean")); } return bd; }
我们可以清楚的看到Spring完成了对所有bean属性的解析,这些属性中有很多是我们经常使用,同时我相信也一定会有或多或少的属性是读者不熟悉或者是没有使用过的,有兴趣的读者可以查阅相关资料进一步了解每个属性。
3. 解析子元素meta
在开始解析元数据的分析前,我们先回顾一下元数据meta属性的使用。
<bean id="myTestBean" class="com.spring_101_200.test_101_110.test108_mytestbean.MyTestBean"> <meta key="testStr" value="aaaaaaaaaaa"/> </bean>
测试:
@Test public void test() { XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring_101_200/config_101_110/spring108_mytestbean.xml")); MyTestBean bean = (MyTestBean) bf.getBean("myTestBean"); BeanDefinition beanDefinition = bf.getBeanDefinition("myTestBean"); Object a = beanDefinition.getAttribute("testStr"); System.out.println(a ); System.out.println(bean.getTestStr()); }
结果输出:
aaaaaaaaaaa
testStr
这段代码并不会体现在MyTestBean的属性当中,而是一个额外的声明,当需要使用里面的时候可以通过BeanDefinition的getAttributes(key)方法进行获取。
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { //获取当前节点下的所有子元素 NodeList nl = ele.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //提取meta if (isCandidateElement(node) && nodeNameEquals(node, "meta")) { Element metaElement = (Element) node; String key = metaElement.getAttribute("key"); String value = metaElement.getAttribute("value"); //使用key,value构造BeanMetadataAttribute BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); attribute.setSource(extractSource(metaElement)); //记录信息 attributeAccessor.addMetadataAttribute(attribute); } } }
解析子元素lookup-method
在文章的开头,己经对lookup-method举例了,接下来,我们来分析一下源码
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //仅当在Spring默认bean元素下且为 <lookup-method >时有效 if (isCandidateElement(node) && nodeNameEquals(node, "lookup-method")) { Element ele = (Element) node; //获取要修饰的方法 String methodName = ele.getAttribute("name"); //获取配置返回的bean String beanRef = ele.getAttribute("bean"); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } }
上面的代码己经很熟悉了,似乎与parseMetaElemnts的代码大同小异,最大区别就是在if判断中节点名称这里被修改为lookup-method,还有,在数据存储上面通过使用LookupOverride类型的实例类来进行数据承载并记录在AbstractBeanDefinition中的methodOverrides属性中。
发现解析出来的methodName和beanRef 最终以LookupOverride的形式储存到了MethodOverrides的overrides属性中。继续跟进代码
我们在这个类中,根据类中的方法,觉得最可能用到MethodOverride这个方法:
public MethodOverride getOverride(Method method) { MethodOverride match = null; for (MethodOverride candidate : this.overrides) { if (candidate.matches(method)) { match = candidate; } } return match; }
打开方法引用
发现与Cglib类相关联了,再来看看bean的实例化过程。
SimpleInstantiationStrategy.java
// 使用了初始化策略实例化的Bean对象
// 通过下面的代码可以知道,如果Bean被覆盖了,则使用 CGlib进行实例化,否则使用JDK的反射机制来进行实例化
@Override
public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// 如果Bean定义中没有方法覆盖,就不需要CGLib父类方法
// 如果有需要覆盖或者动态替换的方法则当然需要使用 cglib 进行动态代理,因为可以在创建代理的同时将动态方法织入 类中,但是如果没有需要动态
// 但是如果没有需要动态改变得方法,为了方便直接反射就可以了,
// 看了上面两个函数后我似乎我们已经感觉到了Spring 的良苦用心以及为了能更加方便的使用 Spring 而做了大量的工作,程序中,首先判断
// 如果 beanDefinition.getMethodOverrides()为空也就是用户没有使用 replace 或者 lookup 配置方法,那么直接使用反射的方式,简单
// 快捷,但是如果使用了这两个特性,在直接使用反射的方式创建实例就不妥了,因为需要将这两个配置提供的功能切进去才可以保证在调用方法
// 的时候会被相应的拦截器增强,返回值为包含拦截器代理的实例
if (bd.getMethodOverrides().isEmpty()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
// 获取对象的构造方法或者工厂方法
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
// 如果没有构造方法或者工厂方法,判断要实例化的bean是不是有接口
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
// 这里是一个匿名的内部类,使用反射机制获取 Bean的构造方法
constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor<?>>() {
@Override
public Constructor<?> run() throws Exception {
return clazz.getDeclaredConstructor((Class[]) null);
}
});
}
else {
constructorToUse = clazz.getDeclaredConstructor((Class[]) null);
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Exception ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
//使用BeanUtils进行实例化,通过反射机制调用构造方法.newInstance(args)来进行实例化
return BeanUtils.instantiateClass(constructorToUse);
}else {
// Must generate CGLIB subclass.
// 使用Cglib来实例化对象
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
CglibSubclassingInstantiationStrategy.java
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner) {
return instantiateWithMethodInjection(bd, beanName, owner, null);
}
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) {
// Must generate CGLIB subclass...
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}
我们不得不来看一下CglibSubclassCreator这个类的实现
CglibSubclassCreator.java
private static class CglibSubclassCreator {
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
private final RootBeanDefinition beanDefinition;
private final BeanFactory owner;
CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) {
this.beanDefinition = beanDefinition;
this.owner = owner;
}
/**
* 使用CGlib进行Bean的实例化
*/
public Object instantiate(Constructor<?> ctor, Object... args) {
// 创建代理子类
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
Object instance;
if (ctor == null) {
instance = BeanUtils.instantiate(subclass);
}
else {
try {
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
instance = enhancedSubclassConstructor.newInstance(args);
}
catch (Exception ex) {
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
"Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
}
}
// SPR-10785: set callbacks directly on the instance instead of in the
// enhanced class (via the Enhancer) in order to avoid memory leaks.
Factory factory = (Factory) instance;
factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
return instance;
}
/**
* CGLib 是一个常用的字节码生成器的类库,它提供了一系列的API 实现java字节码的生成和转换功能,我们学习了JDK的动态代理学过
* ,JDK动态代理只能针对接口,如果一个类没有实例化任何接口,只要对其进行动态代理 ,只能使用CGLIb了
*/
private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {
// CGLib中的类
Enhancer enhancer = new Enhancer();
// 将Bean本身作为基类
enhancer.setSuperclass(beanDefinition.getBeanClass());
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition));
enhancer.setCallbackTypes(CALLBACK_TYPES);
// 使用Cglib的createClass()方法生成实例对象
return enhancer.createClass();
}
}
这个时候就返回了一个代理类实例对象
我们发现,GetBeanTest 的 getBean通过MethodOverrideCallbackFilter返回的是LOOKUP_OVERRIDE常量,这个常量值是1 ,因此
private static final Class<?>[] CALLBACK_TYPES = new Class<?>[]
{NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class};
这个值的index=1,对应的拦截器类是LookupOverrideMethodInterceptor类,
private static class MethodOverrideCallbackFilter extends CglibIdentitySupport implements CallbackFilter { private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class); public MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) { super(beanDefinition); } @Override public int accept(Method method) { MethodOverride methodOverride = getBeanDefinition().getMethodOverrides().getOverride(method); if (logger.isTraceEnabled()) { logger.trace("Override for '" + method.getName() + "' is [" + methodOverride + "]"); } if (methodOverride == null) { return PASSTHROUGH; } else if (methodOverride instanceof LookupOverride) { return LOOKUP_OVERRIDE; } else if (methodOverride instanceof ReplaceOverride) { return METHOD_REPLACER; } throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " + methodOverride.getClass().getName()); } }
因为lookup-method标签并没有指定showMe()方法,因此showMe()方法没有被代理,而getBeanTest对应的beanDefinition中是有getBean方法对应的LookupOverride,因此getBean()方法的调用会被LookupOverrideMethodInterceptor拦截器intercept()方法拦截。
private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
private final BeanFactory owner;
public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition);
this.owner = owner;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all
// 如果<lookup-method/>标签设置了bean属性,那么通过bean的名称找到实体来调用,
if (StringUtils.hasText(lo.getBeanName())) {
return this.owner.getBean(lo.getBeanName(), argsToUse);
}
// 否则根据getBean方法返回值来确定实例,如下图所示
else {
return this.owner.getBean(method.getReturnType(), argsToUse);
}
}
}
因为返回GetBeanTest类是一个代理对象,所以在调用getBean方法时,
在getBean时,会根据lookup-method标签配置的beanName去容器中查对应的bean对象,再调用bean对象的showMe()方法,因此,当我们lookup-method标签中配置name=teacher时,调用的是teacher对象的showMe()方法,当lookup-method标签中配置name=student时,调用的是student对象的showMe()方法,可能细心的读者发现Spring在此处用到的CGLIB代理和我们平常使用的GCLIB代理有点不一样,可以看我的另一篇博客https://blog.csdn.net/quyixiao/article/details/108382321
到这里,我们己经分析完了<lookup-method/>标签的使用及Spring是如何实现的。
5.解析子元素replace-method
这个方法主要对bean中replace-method子元素的提取,在开始提取分析之前我们还是预先介绍这个元素的用法。
方法替换,可以在运行时用新的方法替换再有的方法,与之前的look-up不同的是replaced-method不但可以动态地替换返回实体bean,而且还能动态地更改原有的方法的逻辑,我们来看看使用示例。
1.在changeMe中完成某个业务逻辑。
public class TestChangeMethod { public void changeMe() { System.out.println("change me"); } }
2.在运营一段时间后需要改变原有的业务逻辑
public class TestMethodReplacer implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { System.out.println("我替换了原有的方法"); return null; } }
3.使替换后的类生效
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="testChangeMethod" class="com.spring_1_100.test_41_50.test42.TestChangeMethod"> <replaced-method name="changeMe" replacer="replacer"></replaced-method> </bean> <bean id="replacer" class="com.spring_1_100.test_41_50.test42.TestMethodReplacer"></bean> </beans>
4.测试
public static void main(String[] args) { ApplicationContext bf = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_41_50/spring42.xml"); TestChangeMethod testChangeMethod =(TestChangeMethod) bf.getBean("testChangeMethod"); testChangeMethod.changeMe(); }
【执行结果】:
我替换了原有的方法
好了,运行测试类就可以看到预期的结果了,控制台成功打印出"我替换了原有的方法"也就是说我们做到了动态替换原有方法,知道了这个元素的用法,我们再来看元素的提取过程:
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //仅当在Spring默认bean的子元素下且为 replace-method时有效 if (isCandidateElement(node) && nodeNameEquals(node, "replaced-method")) { Element replacedMethodEle = (Element) node; String name = replacedMethodEle.getAttribute("name"); String callback = replacedMethodEle.getAttribute("replacer"); ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, "arg-type"); for (Element argTypeEle : argTypeEles) { String match = argTypeEle.getAttribute("match"); match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); if (StringUtils.hasText(match)) { replaceOverride.addTypeIdentifier(match); } } replaceOverride.setSource(extractSource(replacedMethodEle)); overrides.addOverride(replaceOverride); } } }
我们可以看到无论是lookup-method还是replaced-method都构造了一个MethodOverride,并最终记录在AbstractBeanDefinition中的methodOverrides属性中,而这个属性是如何使用以完成它所提供的功能呢?
replaced-method的实现的lookup-method实现一样,都是使用cglib代理来实现的,我们进入代理类看看。
private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor { private final BeanFactory owner; public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { super(beanDefinition); this.owner = owner; } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method); //获取到<replaced-method标签配置的bean【replace】,并调用其reimplement方法 MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class); return mr.reimplement(obj, method, args); } }
从上述源码中,我们得知replacer标签配置的bean一定要实现MethodReplacer接口,并且一定要实现其reimplement方法,不然程序会报错。
从lookup-method和replaced-method标签实现分析得出结论,无论是lookup-method还是replaced-method都构造了一个MethodOverride对象,并最终记录在AbstractBeanDefinition中的methodOverrides属性中,当bean对象创建时,发现methodOverrides属性不为空,则创建一个bean的cglib代理对象,当bean方法调用时,必然经过MethodOverrideCallbackFilter拦截,当方法没有对应的MethodOverride对象时,不做任何拦截处理,当方法对应的MethodOverride是LookupOverride类型,则被LookupOverrideMethodInterceptor拦截器拦截并处理,当方法对应的MethodOverride对象是ReplaceOverride类型时,则被ReplaceOverrideMethodInterceptor拦截器拦截并处理,而拦截器的内部实现一目了然。
6.解析子元素constructor-arg
对构造函数的解析是非常常用的,同时也是非常复杂的,也相信大家对构造函数的配置不陌生,举个例子。
<bean id="user" class="com.spring_1_100.test_31_40.test40.User"> <constructor-arg value="zhangsan"></constructor-arg> <constructor-arg value="1"></constructor-arg> </bean>
上面配置的是Spring构造函数中最基础的配置,实现的功能就是对HelloBean自动寻找对应的构造函数,并在初始化的时候将设置的参数传递进去,那让我们来看看具体的XML解析过程。
对于constructor-arg子元素的解析,Spring是骑过parseConstructorArgElements函数来实现的,具体的代码如下:
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //解析constructor-arg if (isCandidateElement(node) && nodeNameEquals(node, "constructor-arg")) { parseConstructorArgElement((Element) node, bd); } } }
这个结构似乎我们可以想象得到,遍历所有 的子元素,也就是提取所有的constructor-arg,然后坦然解析,但是具体的解析却被放置在了另外一个函数parseConstructorArgElement中,具体代码如下所示。
public void parseConstructorArgElement(Element ele, BeanDefinition bd) { //获取index属性 String indexAttr = ele.getAttribute("index"); //获取type属性 String typeAttr = ele.getAttribute("type"); //获取name属性 String nameAttr = ele.getAttribute("name"); if (StringUtils.hasLength(indexAttr)) { try { int index = Integer.parseInt(indexAttr); if (index < 0) { error("'index' cannot be lower than 0", ele); } else { try { this.parseState.push(new ConstructorArgumentEntry(index)); //解析ele对应的属性元素 Object value = parsePropertyValue(ele, bd, null); ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); if (StringUtils.hasLength(typeAttr)) { valueHolder.setType(typeAttr); } if (StringUtils.hasLength(nameAttr)) { valueHolder.setName(nameAttr); } valueHolder.setSource(extractSource(ele)); //不允许指定相同的元素 if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { error("Ambiguous constructor-arg entries for index " + index, ele); } else { //将解析到的对象存储于indexedArgumentValues集合中 bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); } } finally { this.parseState.pop(); } } } catch (NumberFormatException ex) { error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); } } else { //没有index元素,自动去寻找 try { this.parseState.push(new ConstructorArgumentEntry()); Object value = parsePropertyValue(ele, bd, null); ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); if (StringUtils.hasLength(typeAttr)) { valueHolder.setType(typeAttr); } if (StringUtils.hasLength(nameAttr)) { valueHolder.setName(nameAttr); } valueHolder.setSource(extractSource(ele)); //将解析到的对象存储于genericArgumentValues集合中,可能内部有value合并的过程 bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); } finally { this.parseState.pop(); } } }
上面一段看似复杂的代码让很多人失去耐心,但是,涉及到的逻辑其实并不复杂,首先提取constructor-arg上必要的属性(index,type,name)。
- 如果配置中指定的index属性,那么操作步骤如下:
- 解析Constructor-arg的子元素
- 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。
- 将type ,name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前的BeanDefinition的constructorArgumentValues的indexArgumentValues属性中。
- 如果没有指定index属性,那么操作步骤如下.
- 解析constructor-arg的子元素
- 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。
- 将type,name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型并添加至当前的BeanDefinition的constructorArgumentValues的genericArgumentValues属性中。
可以看到,对于是否制定index属性来讲,Spring的下得流程是不同的,关键在于属性的信息被保存的位置。
那么了解整个流程后,我们尝试着进一步了解解析构造函数配置中的子元素的过程,进入parsePropertyValue:
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { String elementName = (propertyName != null) ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element"; //一个属性只能对应一种类型:ref,value,list等 NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); //对description或者meta不处理 if (node instanceof Element && !nodeNameEquals(node, "description") && !nodeNameEquals(node, "meta")) { // Child element is what we're looking for. if (subElement != null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } //解析constructor-arg上的ref属性 boolean hasRefAttribute = ele.hasAttribute("ref"); //解析constructor-arg上的value属性 boolean hasValueAttribute = ele.hasAttribute("value"); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { //在constructor-arg上不存在: //1.同时既有ref属性又有value属性 //2.存在ref属性或者value属性且又有子元素 error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); } if (hasRefAttribute) { //ref属性的处理,使用RuntimeBeanReference封装对应的ref名称 String refName = ele.getAttribute("ref"); if (!StringUtils.hasText(refName)) { error(elementName + " contains empty 'ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } else if (hasValueAttribute) { //value属性的处理,使用TypedStringValue封装 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute("value")); valueHolder.setSource(extractSource(ele)); return valueHolder; } else if (subElement != null) { //解析子元素 return parsePropertySubElement(subElement, bd); } else { //既没有ref也没有value,也没有子元素,Spring蒙圈了 error(elementName + " must specify a ref or value", ele); return null; } }
从代码上来看,对构造函数中属性元素的解析,经历过以下的几个过程。
- 略过description或者meta。
- 提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则在constructor-arg上不存在以下的情况。
- 同时既有ref属性又有value属性。
- 存在ref属性或者value属性且又有子元素。
- ref属性的处理使用RuntimeBeanReference封装对应的名称,如
<constructor-arg ref=“a”/> - value属性的处理。使用TypeStringValue封装,如:
<constructor-arg value=“a”/> - 子元素的处理,如
<constructor-arg>
<map>
<entry key=“key” value=“value”/>
</map>
</constructor-arg>
而对于子元素的处理,例如这里提到的在构造函数中嵌入了子元素map是怎样实现的呢?parsePropertySubElement中实现了对各种子元素的分类处理。
public Object parsePropertySubElement(Element ele, BeanDefinition bd) { return parsePropertySubElement(ele, bd, null); } public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { if (!isDefaultNamespace(ele)) { return parseNestedCustomElement(ele, bd); } else if (nodeNameEquals(ele, "bean")) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd != null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (nodeNameEquals(ele, "ref")) { // A generic reference to any name of any bean. String refName = ele.getAttribute("bean"); boolean toParent = false; if (!StringUtils.hasLength(refName)) { //解析local refName = ele.getAttribute("local"); if (!StringUtils.hasLength(refName)) { //解析parent refName = ele.getAttribute("parent"); toParent = true; if (!StringUtils.hasLength(refName)) { error("'bean', 'local' or 'parent' is required for <ref> element", ele); return null; } } } if (!StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } //解析idref元素的解析 else if (nodeNameEquals(ele, "idref")) { return parseIdRefElement(ele); } //对value子元素的解析 else if (nodeNameEquals(ele, "value")) { return parseValueElement(ele, defaultValueType); } //对null子元素的解析 else if (nodeNameEquals(ele, "null")) { TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (nodeNameEquals(ele, "array")) { //解析array子元素 return parseArrayElement(ele, bd); } else if (nodeNameEquals(ele, "list")) { //解析list子元素 return parseListElement(ele, bd); } else if (nodeNameEquals(ele, "set")) { //解析set子元素 return parseSetElement(ele, bd); } else if (nodeNameEquals(ele, "map")) { //解析map子元素 return parseMapElement(ele, bd); } else if (nodeNameEquals(ele, "props")) { //解析props子元素 return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
可以看到,在上面的函数中实现了所有可支持的子类的分类处理,到这里,我们己经大致理清构造函数的解析流程,至于更深入的解析读者有兴趣可以自己去探索。
7.解析子元素property
parsePropertyElement函数完成对property属性的提取,property使用方式如下:
<bean id="car" class="com.spring_1_100.test_11_20.test18_property_null.Car"> <property name="brand"><value></value></property> <property name="color"><null></null></property> </bean>
或者
<bean id="boss" class="com.spring_1_100.test_11_20.test20_list_attr.Boss"> <property name="favorites"> <list> <value>年报</value> <value>赛车</value> <value>高尔夫</value> </list> </property> <property name="favoriteList1" ref="favoriteList1"></property> </bean>
而具体解析过程如下:
public void parsePropertyElements(Element beanEle, BeanDefinition bd) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { parsePropertyElement((Element) node, bd); } } }
有了之前分析构造函数的经验,这个函数我们并不难理解,无非是提取所有的property的子元素,然后调用parsePropertyElement处理,parsePropertyElement代码如下:
public void parsePropertyElement(Element ele, BeanDefinition bd) { //获取配置元素中的name的值 String propertyName = ele.getAttribute("name"); if (!StringUtils.hasLength(propertyName)) { error("Tag 'property' must have a 'name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { if (bd.getPropertyValues().contains(propertyName)) { error("Multiple 'property' definitions for property '" + propertyName + "'", ele); return; } Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } }
可以看到上面的函数与构造函数注入方式不同的是将返回值使用PropertyValue进行封装,并记录在BeanDefinition中的propertyValues属性中。
8.解析子元素qualifier
对qualifier元素的获取,我们接触更多的是注解的形式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选Bean的数目必需有且仅有一个,当找不到一个匹配的bean是,Spring容器将抛出BeanCreationException异常,并指出必需至少拥有一个匹配的bean。
Spring允许我们通过Qualifier指定注入的Bean的名称,这样歧义就消除了,而对于配置方式使用如:
public class MysqlDriveManagerDataSource implements DataSource { public void connection() { System.out.println("mysql database connecting..."); } } public class OracleDriveManagerDataSource implements DataSource{ public void connection() { System.out.println("oracle database connecting..."); } } public class TestBean { private DataSource dataSource; @Autowired @Qualifier(value="oracleDataSource") public void initDataSource(DataSource dataSource){ this.dataSource = dataSource; } /* @Autowired public void initDataSource(@Qualifier("oracleDataSource") DataSource dataSource) { this.dataSource = dataSource; } */ public DataSource getDataSource() { return dataSource; } } <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <context:component-scan base-package="com.spring_101_200.test_101_110.test108_mytestbean"></context:component-scan> <bean id="testBean" class="com.spring_101_200.test_101_110.test108_mytestbean.TestBean"/> <bean id="mysqlDataSourceBean" class="com.spring_101_200.test_101_110.test108_mytestbean.MysqlDriveManagerDataSource"> <qualifier value="mysqlDataSource"/> </bean> <bean id="oracleDataSourceBean" class="com.spring_101_200.test_101_110.test108_mytestbean.OracleDriveManagerDataSource"> <qualifier value="oracleDataSource"/> </bean> </beans> // 1)、根据基于XML配置中的<qualifier>标签指定的名字进行注入,使用如下方式指定名称: @Test public void autowiredTest(){ ApplicationContext ctx = new ClassPathXmlApplicationContext("spring_101_200/config_101_110/spring108_mytestbean1.xml"); TestBean bean = ctx.getBean("testBean", TestBean.class); DataSource dataSource = bean.getDataSource(); if(dataSource instanceof MysqlDriveManagerDataSource){ System.out.println("mysql"); }else if(dataSource instanceof OracleDriveManagerDataSource){ System.out.println("oracle"); } dataSource.connection(); }
结果输出:
oracle
oracle database connecting…
其解析过程大同小异,这里就不再说了。
3.1.2 AbstractBeanDefinition属性
至此我们便完成了对XML文档到GenericBeanDefinition的转换,也就是说到这里,XML中所有的配置都可以在GenericBeanDefinition这个实例中找到对应的配置。
GenericBeanDefinition只是子类实现,而大部分是通过属性都保存在AbstractBeanDefinition中,那么我们再次通过AbstractBeanDefinition的属性来回顾一下我们都解析了哪些对应的配置。
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable { //此处省略静态变量以及final常量 public static final String SCOPE_DEFAULT = ""; ... public static final String INFER_METHOD = "(inferred)"; private volatile Object beanClass; //是否是单例,来自bean属性scope private String scope = SCOPE_DEFAULT; //是否是抽象,对应bean属性abstract private boolean abstractFlag = false; //是否是延迟加载,对应bean属性lazy-init private boolean lazyInit = false; //自动注入模式,对应bean属性autowire private int autowireMode = AUTOWIRE_NO; //依赖检查,Spring 3.0后弃用这个属性 private int dependencyCheck = DEPENDENCY_CHECK_NONE; //用来表示一个bean的实例化依靠另一个bean先实例化,对应bean的属性depend-on private String[] dependsOn; //autowire-candidate属性设置为false,这样容器在查找 自动装配对象将不考虑该bean, //即使它不会被考虑作为其他bean自动装配的候选者,但是该bean本身还是可以使用自动装配来注入其他bean的, //对应bean属性autowire-candidate private boolean autowireCandidate = true; //自动装配时当出现多个bean候选者时,将作为首先者,对应bean属性primary private boolean primary = false; //用于记录Qualifier,对应子元素qulifier private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<String, AutowireCandidateQualifier>(0); //允许方法非公开的构造器和方法,程序设置 private boolean nonPublicAccessAllowed = true; //是否以一种宽松的模式解析构造函数,默认为true, //如果为false,则在如下情况 //interface ITest{} //class ITestImpl implements ITest{} ; //class Main{ // Main(ITest i){} // Main(ITestImpl i) {} //} //抛出异常,因为Spring无法准确的定位哪个构造函数 //程序设置 private boolean lenientConstructorResolution = true; //记录构造函数注入属性,对应bean属性constructor-arg private ConstructorArgumentValues constructorArgumentValues; //普通属性集合 private MutablePropertyValues propertyValues; //方法重写的持有者,记录lookup-method,replaced-method元素 private MethodOverrides methodOverrides = new MethodOverrides(); //对应bean属性factory-bean,用法 //<bean id="instanceFactoryBean" class="example.chapter3.InstanceFactoryBean" /> //<bean id="currentTime" factory-bean="instanceFactoryBean" factory-method = "createTime" /> private String factoryBeanName; //对于bean属性factory-method private String factoryMethodName; //初始化方法,对应bean属性init-method private String initMethodName; //销毁方法,对应bean属性destory-method private String destroyMethodName; //是否执行init-method,程序设置 private boolean enforceInitMethod = true; //是否执行destory-method,程序设置 private boolean enforceDestroyMethod = true; //是否是用户定义的而不是应用程序本身定义的,创建AOP时候为true,程序设置 private boolean synthetic = false; //定义这个bean的应用,APPLICATION,INFRASTRUCTURE,完全内部使用,与用户无关 //SUPPORT某些复杂配置的一部分程序设置 private int role = BeanDefinition.ROLE_APPLICATION; //bean的描述信息 private String description; //这个bean定义的资源 private Resource resource; ... }
3.1.3 解析默认标签中的自定义标签元素
到这里我们己经完成了分析默认标签的解析与提取过程,或许涉及的内容太多,我们己经忘了从哪个函数开始的了,再次回顾一下默认标签解析函数的起始函数:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
我们己经用了大量的篇幅分析了BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele)这句代码,接下来,要进行 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
代码进行分析,首先大致了解 下这句代码的作用,其实我们可以从语义上分析,如果需要的话就对beanDefinition进行装饰,那这句代码到底有什么功能呢?其实这句代码用于这样的场景,如:
- 添加Spring.handlers
http://www.lexueba.com/schema/user=com.spring_101_200.test_111_120.test_115_custom_label.MyNamespaceHandler - 添加handler
public class MyNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } } @Data public class User { private String userName; private String email; } public class UserBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { // 从 Elment 中找到相应的类 protected Class getBeanClass(Element element){ return User.class; } // 从 element 中解析并提取出对应的元素 @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); //将提取的数据放到 BeanDefinitionBuilder 中,待完成所有的 bean 注册后,统一注册到 beanFactory 中 if(StringUtils.isNotBlank(userName)){ builder.addPropertyValue("userName", userName); } if(StringUtils.isNotBlank(email)){ builder.addPropertyValue("email",email); } } }
- 配置xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:myname="http://www.lexueba.com/schema/user" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user.xsd"> <myname:user id="testBean" userName="aaa" email="bbb"></myname:user> </beans>
- 测试
public class Test115 { public static void main(String[] args) { ApplicationContext bf = new ClassPathXmlApplicationContext("spring_101_200/config_111_120/spring115_custom_label/spring115.xml"); User user = (User) bf.getBean("testBean"); System.out.println(JSON.toJSONString(user)); } }
执行结果:
{“email”:“bbb”,“userName”:“aaa”}
当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时,这句代码便会起作用了,可能有人会有疑问,之前读过,对bean的解析分为两种类型,一种是默认的类型的解析,另一种是自定义类型的解析,这不正是自定义类型的解析吗?为什么会在默认类型解析中单独添加一个方法处理呢?确实,这个问题很让人迷惑,但是,不知道聪明的读者是否有发现,这个自定义类型并不是以Bean的形式出现呢?之前讲过的两种类型的不同处理只是针对 Bean的,这里我们看到,这个自定义类型其实是属性,好了,我们继续分析下这段代码的逻辑。
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) { return decorateBeanDefinitionIfRequired(ele, definitionHolder, null); }
这里将函数中第三个参数设置为空,那么第三个参数做什么用的呢?什么情况下不为空呢?其实这第三个参数是父类bean,当对某个嵌套配置进行分析时,这里需要传递父类beanDefinition,分析源码得知这里传递的参数其实是为了使用父类的scope属性,以备子类若没有设置scope时默认使用父类的属性,这里分析的顶层配置,所以传递null,将第三个参数高雷为空后进一步跟踪函数:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired( Element ele, BeanDefinitionHolder definitionHolder, BeanDefinition containingBd) { BeanDefinitionHolder finalDefinition = definitionHolder; //遍历所有的属性,看看是否适用于修饰的属性 NamedNodeMap attributes = ele.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } //遍历所有的子节点,看看是否有适用于修饰的子元素 NodeList children = ele.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); } } return finalDefinition; }
上面的代码,我们看到函数分别对元素的所有属性以及子节点进行了decorateIfRequired函数的调用,我们继续跟踪代码。
public BeanDefinitionHolder decorateIfRequired( Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) { //获取自定义标签的命名空间 String namespaceUri = getNamespaceURI(node); //对非默认标签进行修饰 if (!isDefaultNamespace(namespaceUri)) { //根据命名空间找到对应的处理器 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler != null) { return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); } else if (namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node); } else { // A custom namespace, not to be handled by Spring - maybe "xml:...". if (logger.isDebugEnabled()) { logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]"); } } } return originalDef; }
程序走到这里,条理其实己经非常清楚了,首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找到自定义类型所对应的NamespaceHandler并进行进一步解析,在自定义标签解析的章节我们会重点讲解,这里暂时先略过。
总结下decorateBeanDefinitionIfRequired方法的作用,在decorateBeanDefinitionIfRequired中,我们可以看到对于程序默认标签的处理其实是直接略过的,因为默认的标签到这里己经被处理完了,这里只对自定义标签或者说bean的自定义属性感兴趣,在方法中实现了寻找自定义标签并根据自定义标签寻找命名空间处理器,并进行进一步的解析。
3.1.4 注解解析的BeanDefinition
对于配置文件,解析己经解析完了,装饰也装饰完了,对于得到的beanDefinition己经可以满足后续的使用要求了,唯一剩下的工作就是注册了,也就是processBeanDefinition函数中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())代码的解析了。
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { //使用beanName作唯一标识 String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); //注册所有的别名 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
从上面的代码可以看出,解析beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中,而对于beanDefinition的注册分成两个部分,通过beanName的注册以及通过别名的注册。
1. 通过beanName注册BeanDefinition
对beanDefinition的注册,或许很多人认为方式就是将beanDefinition直接放入map中就好了,使用beanName作为key,确实,Spring就是这么做的,只不过除此之外,它还做了点别的事情。
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 { //注册前的最后一次检验,这里的校验不同之前的XML文件校验 //主要是对于AbstractBeanDefinition属性中的methodOverrides //校验methodOverrides是否与工厂方法并存或者methodOverrides对应的方法根本不存在 ((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); //处理注册己经注册的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 (!beanDefinition.equals(oldBeanDefinition)) { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } else { //记录beanName this.beanDefinitionNames.add(beanName); this.manualSingletonNames.remove(beanName); this.frozenBeanDefinitionNames = null; } //注册beanDefinition this.beanDefinitionMap.put(beanName, beanDefinition); if (oldBeanDefinition != null || containsSingleton(beanName)) { //重置所有的beanName对应的缓存 resetBeanDefinition(beanName); } }
上面的代码中我们己经看到,对于bean的注册处理方式上,主要进行了几个步骤。
- 对AbstractBeanDefinition的校验,在解析XML文件的时候我们提过校验,但是此校验非彼校验,之前的校验时针对于XML格式的校验,而此时的校验时针对于AbstractBeanDefinition的methodOverrides属性的。
- 对beanName己经注册的情况的处理,如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖。
- 加入map缓存。
- 清除解析之前留下的对应的beanName的缓存。
2. 通过别名注册BeanDefinition
在理解 了注册bean的原理后,理解注册别名的原理就容易多了。
public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); //如果beanName与alias相同的话不记录alias,并删除对应的alias if (alias.equals(name)) { this.aliasMap.remove(alias); } else { String registeredName = this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { return; } //如果alias不允许被覆盖,则抛出异常 if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } } checkForAliasCircle(name, alias); this.aliasMap.put(alias, name); } }
由以上代码中可以得知,注册alias的步骤如下:
- alias与beanName相同情况处理,若alias与beanName并名称相同则不需要处理并删除掉原有的alias。
- alias覆盖处理,若aliasName己经使用并己经指向了另一个beanName,则需要用户设置进行进行处理。
- alias 循环检查,当 B -> C ,C -> A 存在时,若再次出现 A -> B 时候,则抛出异常
- 注册alias
public class Test1 { public final static Map<String, String> aliasMap = new ConcurrentHashMap<String, String>(16); public static void main(String[] args) { aliasMap.put("D", "C"); aliasMap.put("C", "B"); aliasMap.put("B", "A"); boolean hasAlias = hasAlias("A", "D"); if (hasAlias) { throw new IllegalStateException(" already"); } } public static boolean hasAlias(String name, String alias) { System.out.println("00000000000 name = " + name + ",alias=" + alias); for (Map.Entry<String, String> entry : aliasMap.entrySet()) { String registeredName = entry.getValue(); System.out.println("11111111111 registeredName = " + registeredName + ",name=" + name); if (registeredName.equals(name)) { String registeredAlias = entry.getKey(); System.out.println("222222222222 registeredAlias = " + registeredAlias + ",alias=" + alias); return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)); } } return false; } }
执行结果:
00000000000 name = A,alias=D
11111111111 registeredName = A,name=A
222222222222 registeredAlias = B,alias=D
00000000000 name = B,alias=D
11111111111 registeredName = A,name=B
11111111111 registeredName = B,name=B
222222222222 registeredAlias = C,alias=D
00000000000 name = C,alias=D
11111111111 registeredName = A,name=C
11111111111 registeredName = B,name=C
11111111111 registeredName = C,name=C
222222222222 registeredAlias = D,alias=D
Exception in thread “main” java.lang.IllegalStateException: already
at com.spring_1_100.test_1_10.test3.Test1.main(Test1.java:15)
3.1.5 通知监听器解析及注册完成。
通过代码getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此工作,这里的实现只为扩展,当程序开发人员需要对注册的BeanDefinition事件进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前的Spring中并没有对此事做任何逻辑处理。
3.2 alias标签的解析
通过上面较长的篇幅我们终于分析完成了默认标签中对bean标签的处理,那么我们之前提到过,对配置文件的解析包括对import标签,alias标签,bean标签的处理,现在我们己经完成了最的重要也是最核心的功能,其他的解析步骤也都是围绕第3个解析而进行的,在分析了第3个解析步骤后,再回头来看看对alias标签的解析。
在对bean进行定义时,除了使用id属性来指定名称之外,为了提供多个名称,可以使用alias标签来指定,而所有的这些名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用的每一个组件都能更容易地对公共组件进行引用。
然而,在定义bean时就指定所有的别名并不是总是恰当的,有时候我们期望能在当前位置为那些在别处定义的bean引入别名,在xml配置文件中,可用单独的<alias />元素来完成bean别名的定义,如配置文件中定义一个JavaBean:
bean id=“testBean” class=“com.test” />
要给这个JavaBean增加别名,以方便不同对象来调用,我们就可以直接使用bean标签中的name属性。
<bean id =“testBean” name=“terstBean,testBean2” class=“com.test” />
同样,Spring还有另外一种声明别名的方式:
<bean id=“testBean” class=“com.test” />
<alias name=“testBean” alias=“testBean,testBean2” />
考虑一个题库具体的例子,组件 A在XML配置文件中定义了一个名为componentA的DataSouce类型的bean,但是组件 B却想在其XML文件中以componentB命名来引用此bean,而且在主程序MyApp的XML配置文件中,希望以myApp的名字引用此bean,最后容器加载3个XML文件来生成最终的ApplicationContext,在此种情况下,可以通过在配置文件中添加如下列alias元素来实现:
<alias name=“componentA” alias=“componentB” />
<alias name=“componentA” alias=“myApp” />
这样一来,每个组件及主程序就可以通过唯一的名字来引用同一个数据源而不互相干扰了。
在之前的章节己经读过对于bean中name元素的解析,那么我们现在再来深入分析下对alias标签的解析过程。
protected void processAliasRegistration(Element ele) { String name = ele.getAttribute("name"); String alias = ele.getAttribute("alias"); boolean valid = true; if (!StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele); valid = false; } if (!StringUtils.hasText(alias)) { getReaderContext().error("Alias must not be empty", ele); valid = false; } if (valid) { try { //注册alias getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex); } //别名注册后通知监听器做相应的处理 getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } }
可以发现,跟之前讲过的bean中的alias解析大同小异,都是将别名与beanName组成一对注册registry中,这里不再赘述。
3.3 import标签解析
对Spring配置文件的编写,我想,经历过庞大项目的人,都有那种恐惧的心理,大多数配置文件了,不过,分模块是大多数人能想到的办法,但是怎样分模块,那就不好说了,使用import是个好办法,例如我们可以构造这样的Spring配置文件。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <import resource="classpath:spring_1_100/config_21_30/spring30_import_resource/spring30_parent.xml"></import> <bean id="boss" class="com.spring_1_100.test_21_30.test30_import_resource.Boss" p:car-ref="car"></bean> </beans>
applicationContext.xml文件中使用import的方式导入有模块的配置文件,以后若有新的模块加入,那就可以简单的修改这个文件了,这样大大的简化了配置后期的维护的复杂度,并使配置模块化,易于管理,我们来看看Spring是如何解析import配置文件的呢?
protected void importBeanDefinitionResource(Element ele) { //获取resource属性 String location = ele.getAttribute("resource"); //如果不存在resource属性则不做任何处理 if (!StringUtils.hasText(location)) { getReaderContext().error("Resource location must not be empty", ele); return; } // 解析系统属性,格式如: "${user.dir}" location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); Set<Resource> actualResources = new LinkedHashSet<Resource>(4); //判定location是绝对URI还是相对URI boolean absoluteLocation = false; try { absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } catch (URISyntaxException ex) { // cannot convert to an URI, considering the location relative // unless it is the well-known Spring prefix "classpath*:" } //如果是绝对URI,则直接根据地址加载对应的配置文件 if (absoluteLocation) { try { int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); } } catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from URL location [" + location + "]", ele, ex); } } else { //如果是相对地址,则根据相对地址计算出绝对地址 try { int importCount; //Resource存在多个实现类,如VfsResource, //FileSystemResource等 //而每个resource的createRelative方式实现都不一样,所以这里行使用子类的方法尝试解析 Resource relativeResource = getReaderContext().getResource().createRelative(location); if (relativeResource.exists()) { importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); actualResources.add(relativeResource); } else { //如果解析不成功,则使用默认的解析器ResourcePatternResolver进行解析 String baseLocation = getReaderContext().getResource().getURL().toString(); importCount = getReaderContext().getReader().loadBeanDefinitions( StringUtils.applyRelativePath(baseLocation, location), actualResources); } if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]"); } } catch (IOException ex) { getReaderContext().error("Failed to resolve current resource location", ele, ex); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, ex); } } //解析后进行监听器的激活处理 Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]); getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); }
上面的代码不难,相信配置注释会很好理解,总结一下大致的流程便于读者更好的梳理,在解析import标签时,Spring进行解析的步骤大致如下:
- 获取resource属性所表示的路径
- 解析路径中的系统属性,格式如"${user.dir}"
- 判定location是绝对路径还是相对路径。
- 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析。
- 如果是相对路径则计算出绝对路径并进行解析。
对于嵌入式bean标签,相信大家使用过或者至少接触过,非常类似于import标签所提供的功能,使用如下
<beans></beans>
对于嵌入式beans标签来讲,并没有太多的可讲的,与单独的配置文件并没有太大的差别,无非是递归调用beans的解析过程,相信读者根据之前讲解过的内容很快的理解其中的奥秘了。
本文github地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_41_50