在上一篇部落格中提到過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
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csYnRXFGNsdVZxY0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2IzM2IzNyATM2EDNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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