經過struts2源碼分析-IOC容器的實現機制(上篇),能夠從中瞭解了不少關於struts2-IOC容器初始化的東西,若是容器託管對象是什麼,<bean>
節點中爲何有了type
屬性還要有name
屬性,ContainerBuilder
構建Container
原理等。主要是講解了IOC容器的初始化過程,而對從容器中獲取容器託管對象以及注入原理一筆帶過了。該篇文章將詳細講解如何從容器中獲取容器託管對象以及注入原理。java
講解以前有些結論是必需要知道的:緩存
什麼是容器託管對象?安全
按struts2/XWork的配置元素節點可分爲兩類,一類是<bean>
節點和<constant>
節點,這兩個節點爲「容器配置元素」,另外一類是package
節點,這個節點下的全部配置定義爲"事件映射關係"。其中<bean>
節點普遍用於定義框架級別的內置對象和自定義對象,而constant節點和Properties文件中的配置選項則被用於定義系統級別的運行參數。<bean>
節點,<constant>
節點加上Propeties文件中的配置選項統稱爲"容器配置元素",就是由於它們所定義的對象的生命週期都是由容器管理的,因此這些對象就是容器託管對象。session
Container
中的容器託管對象並非直接存儲在容器中的,實際上容器中存儲的是對象的工廠,有了對象工廠就能夠隨時在運行期 得到對象的實例,存儲工廠有個好處就是能夠控制對象的產生,例如是要返回一個新的對象呢,仍是返回一個單例對象,或者說返 回一個線程安全的對象等。依賴注入也就是調用Container
中的inject
方法,先看一下該方法聲明框架
void inject(Object o); <T> T inject(Class<T> implementation);
方法有兩個,第一個是傳入一個Object
,對該對象進行注入,注意,這裏的對象能夠是任意對象,不必定要是容器託管對象。第二個方法傳入一個Class
對象,完成注入並返回一個注入好了的對象實例。ide
下面是@Injector
註解的源碼:源碼分析
@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER}) @Retention(RUNTIME) public @interface Inject { String value() default DEFAULT_NAME; boolean required() default true; }
從其聲明能夠看到,該註釋可標於方法,構造器,字段,參數列表中,這也對應了進行注入時可能用到的各類類型的注入器。其value
屬性爲查找InternalFactory
(容器託管對象的工廠)的name
屬性值,默認值爲default
。ui
Container
爲一個接口,其實現類爲ContainerImpl
,當咱們要進行注入時,其實調用的就是ContainImpl
類的inject方法。以下ContainerImpl
中 void inject(Object o)
的實現:this
public void inject(final Object o) { callInContext(new ContextualCallable<Void>() { public Void call(InternalContext context) { inject(o, context); return null; } }); }
在該實現方法當中調用的是callInContext
方法,這是一個模版方法,在後面能夠看到,從容器中獲取對象的方法getInstance
內部調用的也是該方法,其目的是將全部接口操做進行規範化定義,做爲一個統一操做的入口,並將它們歸入一個線程安全的上下文執行執行環境中。而具體的執行邏輯則被封裝於ContextualCallable
接口的回調實現以內。對於不一樣的接口實現,它們會擁有不一樣的邏輯,而這些邏輯則由ContextualCallable
接口實現類來完成,正由於這樣纔有可能inject
方法與getInstance
方法內部調用的都是同一個callInContext
方法。這就是模版方法的使用,模版方法只是規定了程序的執行流程,而程序執行的具體邏輯能夠由各調用者本身指定。這裏public Void call(InternalContext context)
便是那個回調方法。線程
以下是callInContext
方法的源碼:
<T> T callInContext(ContextualCallable<T> callable) { Object[] reference = localContext.get(); if (reference[0] == null) { reference[0] = new InternalContext(this); try { return callable.call((InternalContext)reference[0]); } finally { // Only remove the context if this call created it. reference[0] = null; } } else { // Someone else will clean up this context. return callable.call((InternalContext)reference[0]); } }
在該方法當中就是調用了前面註冊的回調方法call
,其他的操做都是在獲取一個正確的執行上下文。在回調方法call
中調用了void inject(Object o, InternalContext context)
方法,咱們進行該方法的內部看看:
void inject(Object o, InternalContext context) { List<Injector> injectors = this.injectors.get(o.getClass()); for (Injector injector : injectors) { injector.inject(context, o); } }
該方法其中的邏輯很簡單,首先獲取該對象所須要的注入器(Injector
),再循環迭代第一個注入器,調用注入器的inject
方法完成注入。
這裏須要講的是這句List<Injector> injectors = this.injectors.get(o.getClass());
這是從一個緩存對象(Map
)中獲取所需注入器的操做,這是ContainerImpl
中injectors
的聲明:
final Map<Class<?>, List<Injector>> injectors = new ReferenceCache<Class<?>, List<Injector>>() { @Override protected List<Injector> create(Class<?> key) { List<Injector> injectors = new ArrayList<Injector>(); addInjectors(key, injectors); return injectors; } };
其實你們能夠看到,其實該對象就是個Map
,只不過增長了緩存的功能,其key
爲一Class
對象,就是被注入對象的Class
對象,其value爲該對象注入所需注入器,當第一次爲某一對象進行注入時,獲取的注入器確定爲null
,這時就會去查找該對象注入所須要的注入器。
你們能夠看到獲取注入器用的是ReferenceCache
的get
方法,這是get
方法的源代碼:
@SuppressWarnings("unchecked") @Override public V get(final Object key) { V value = super.get(key); return (value == null) ? internalCreate((K) key) : value; }
若是說沒有找到指定key
的值,該方法則會建立一個新的值放入Map
中並返回,這個新的值其實就是注入器列表。
這是internalCreate
方法中的兩句源碼:
FutureTask<V> futureTask = new FutureTask<V>( new CallableCreate(key)); //中間省略... futureTask.run();
這裏新建了一個CallableCreate
對象封裝到了futureTask
對象中,後面又調用了futureTask.run();
在run
方法內容調用的又是一個Sync
對象的innerRun()
方法,在innerRun
方法中調用了CallableCreate
對象的call
方法,而該call
方法中調用的正是injectors
聲明中註冊的create(key)
回調方法。
好了,如今能夠將注意力集中到該create(key)
方法上了來,該中調用了addInjectors(key, injectors);
方法,該方法源碼爲:
void addInjectors(Class clazz, List<Injector> injectors) { if (clazz == Object.class) { return; } // Add injectors for superclass first. addInjectors(clazz.getSuperclass(), injectors); // TODO (crazybob): Filter out overridden members. addInjectorsForFields(clazz.getDeclaredFields(), false, injectors); addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors); }
該方法是一個遞歸方法,首先查找父類注入器,先爲父類實施注入,其次是本身。這裏addInjectorsForFields
和addInjectorsForMethods
調用的都是一個名爲addInjectorsForMembers
的方法,下面是 addInjectorsForMembers
源碼:
<M extends Member & AnnotatedElement> void addInjectorsForMembers( List<M> members, boolean statics, List<Injector> injectors, InjectorFactory<M> injectorFactory) { for (M member : members) { if (isStatic(member) == statics) { Inject inject = member.getAnnotation(Inject.class); if (inject != null) { try { injectors.add(injectorFactory.create(this, member, inject.value())); } catch (MissingDependencyException e) { if (inject.required()) { throw new DependencyException(e); } } } } } }
該方法只是調用InjectorFactory
的create
方法返回一個Injector
並放入到注入器列表當中,create
方法呢又是一個回調方法,這裏以FieldInjector
爲例,這是addInjectorsForFields
方法的源碼:
void addInjectorsForFields(Field[] fields, boolean statics, List<Injector> injectors) { addInjectorsForMembers(Arrays.asList(fields), statics, injectors, new InjectorFactory<Field>() { public Injector create(ContainerImpl container, Field field, String name) throws MissingDependencyException { return new FieldInjector(container, field, name); } }); }
create
方法中傳入容器對象,Field
, name
返回了一個字段注入器,方法注入器原理也是如些,這裏就不說了。
在FieldInjector
構造方法內問,根據field
類型與name
參數做爲key
在容器中查找對應容器託管對象的InternalFactory
,下面是FieldInjector
類inject
方法的實現:
public void inject(InternalContext context, Object o) { ExternalContext<?> previous = context.getExternalContext(); context.setExternalContext(externalContext); try { field.set(o, factory.create(context)); } catch (IllegalAccessException e) { throw new AssertionError(e); } finally { context.setExternalContext(previous); } }
你們能夠看到最終調用的就是經過反射技術調用field.set()
方法,要設置的值就是相應的InternalFactory
的create
方法返回的值。到這裏你們對struts2
中的注入原理應該有必定的瞭解了吧。
這是在Container
接口中的聲明:
<T> T getInstance(Class<T> type, String name); <T> T getInstance(Class<T> type);
其實這兩個方法的實質是同樣的,第二個其實就是一個簡單方法,至關於getInstance(type,DEFAULT_NAME);
就是以type和name做爲聯合主鍵進行查找。
再看看ContainerImpl
的具體實現:
public <T> T getInstance(final Class<T> type, final String name) { return callInContext(new ContextualCallable<T>() { public T call(InternalContext context) { return getInstance(type, name, context); } }); }
這裏調用的方法如同上面所說,都是callInContext
這個模版方法,其真正邏輯實現是getInstance(type,name,context)
方法,其源碼爲:
@SuppressWarnings("unchecked") <T> T getInstance(Class<T> type, String name, InternalContext context) { ExternalContext<?> previous = context.getExternalContext(); Key<T> key = Key.newInstance(type, name); context.setExternalContext(ExternalContext.newInstance(null, key, this)); try { InternalFactory o = getFactory(key); if (o != null) { return getFactory(key).create(context); } else { return null; } } finally { context.setExternalContext(previous); } }
該方法代碼也很簡單,首先根據type
,name
構造一個Key
對象,其實Key
就是一個用於存儲type
與name
的一個POJO,用它做爲key
去容器中查找相應的InternalFactory
對象,而後調用InternalFactory
對象的create
方法返回該值。
這裏你們可能會問:struts2中bean不是能夠聲明變量範圍嗎,如singleton,request,session,thread的嗎,這裏只是直接調用了InternalFactory
的create
方法,這樣能達到目的嗎,前面也已經說了容器中存儲這些工廠其好處就是能夠控制對象的產生,這些功能邏輯都已經封裝在InternalFactory
當中的,具體的請參看struts2源碼分析-IOC容器的實現機制(上篇),裏面有很詳細的說明。
struts2中呢大量應用了模版方法,回調方法,剛看可能會不習慣,多看幾遍應該沒有問題的。但願對你們有所幫助。
-------------------------------- END -------------------------------
及時獲取更多精彩文章,請關注公衆號《Java精講》。