Struts2的IoC解析
對于 IoC來說,常見的就是Spring框架的了。并且在目前Java EE開發中,使用SSH框架時,也主要依賴于Spring框架所提供的IoC功能。但Struts2框架本身也提供了IoC的功能。本人對于Spring 框架的IoC功能的實現沒怎么做了解,所以也不對此發表什么見解。這里主要是對Struts2框架的IoC的使用方法和實現原理進行分析。純屬個人學習興趣。。。。
在這其中參照了博文《Struts2源碼分析-IoC容器的實現機制》,鏈接地址為http://blog.csdn.net/fcbayernmunchen/article/details/7686385中的相關內容。
[IoC功能的使用] 首先如何理解IoC的含義呢,現在也常稱為依賴注入(Dependency Injection,DI。各種稱呼,對此略有點糊涂)。核心的意思就是把對象之間的耦合關系,交由外部去管理,一般就是一個容器(Container)對象。那容器,又是如何去獲得關于對象的信息的呢,這就是配置文件的作用了。在Spring中,有applicationContext.xml配置文件。在Struts2中,有struts.xml配置文件。在這些配置文件中配置好對象信息,在加載的時候,這些配置文件會被解析,然后當對象被使用的時候,就通過配置文件找到相應的類,調用相應的方法等。。。。
大致是這么個思路,直接說有點抽象,下面用一個實際的例子來說明這種用法。
struts.xml中的配置項如下所示
< bean name ="entity" type ="lynn.entity.Entity" class ="lynn.entity.Entity" scope="Scope.SINGLETON"/>
配置了一個bean,名稱是entity,類型是lynn.entity.Entity,實例化對象時,具體生成的是lynn.entity.Entity這個類的對象。在這個例子中type和class的值被設置成一樣的。一般來說class描述的類應該是type類的子類,或是type描述的接口的實現類。 scope指出了這個對象的生存范圍,這里這個對象被設置成了一個單例形式的對象。也就是,所有通過Struts2的IoC獲取的對象,都是同一個對象。
下面來看lynn.entity.Entity這個類的代碼,如下所示
package lynn.entity;
public class Entity {
private String name ;
private int value ;
public Entity(){
name= "Lynn";
value=23;
}
public void setName(String name){
this.name =name;
}
public void setValue(int value){
this.value =value;
}
public String getName(){
return name ;
}
public int getValue(){
return value ;
}
@Override
public String toString(){
return "Entity info:<" +name +"," +value +">" ;
}
}
lynn.entity.Entity這個類沒有什么特別之處,就是一個一般的Java Bean,只不過重寫了toString方法,這樣就可以直接將Entity對象的相關信息打印出來。
還有一個測試inject方法的類,叫做InjectTest類,這個類的代碼如下所示
package lynn.inject;
import lynn.entity.Entity;
import com.opensymphony.xwork2.inject.Inject;
public class InjectTest {
@Inject( "entity")
private Entity entity ;
@Inject
public void injectTest(@Inject("entity")Entity entity){
System. out.println(entity);
}
public void normalMethod(){
System. out.println("normal method" );
}
}
主要注意這個類中加入的@Inject注釋。這里指定的名稱是“entity”,也就是前面在struts.xml中配置的那個java bean。
在測試項目中與lynn.entity.Entity和IoC相關的部分的代碼如下所示
Container container=ServletActionContext.getContext().getContainer();
InjectTest inject= new InjectTest();
Entity entity=container.getInstance(Entity. class, "entity" );
System. out.println("first mark: " +entity);
entity.setName( "yanlinwang");
entity.setValue(50);
container.inject(inject);
Entity entity2=container.getInstance(Entity. class,"entity" );
System. out.println("second mark:" +entity2);
在這段代碼中,首先通過ServletActionContext.getContext().getContainer()獲得用來管理對象關系的容器(Container)對象container,然后與IoC功能相關部分的代碼是container.getInstance(Entity. class , "entity" );和container.inject(inject); 這里對getInstance對象調用了兩次,并且在中間還對獲取過的對象進行過修改,通過對兩次獲取到的對象的打印結果,來確定是不是像前面所說的那樣,這個對象是一個單例對象。
項目運行后,與此部分相關的顯示結果如下圖所示

從運行結果來看,總共對這個對象打印了三次。出了在上面標出的"first mark"和"second mark"外,還進行了一次打印動作。是在InjectTest類的injectTest函數中。第一次的打印結果是<Lynn,23>,這表明是調用了默認構造函數。在后續對這個entity對象的屬性值進行了設定,然后進行 container.inject(inject);時injectTest函數被調用,使得這個對象又被打印一次。這里顯示的信息就是設置后的新值。然后再次通過容器獲取這個對象,并且打印。這里打印出來的值與設置的值一樣,表明這里獲取的還是之前修改過的對象,所以,從這三次打印的結果來看,三次操作的都是同一個entity對象。
通過上面這個例子,也給出了IoC功能的一個形象說明。下面部分,將對Struts2的IoC的實現,進行解析。學習所致,不保證正確性!
[IoC功能的接口]
前面的例子中給出的對Struts2的IoC功能的使用,是通過一個容器(Container)對象來實現的。Container是一個接口類,它的源代碼如下所示
public interface Container extends Serializable {
/**
* Default dependency name.
*/
String DEFAULT_NAME = "default";
/**
* Injects dependencies into the fields and methods of an existing object.
*/
void inject(Object o);
/**
* Creates and injects a new instance of type {@code implementation}.
*/
<T> T inject(Class<T> implementation);
/**
* Gets an instance of the given dependency which was declared in
* {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
*/
<T> T getInstance(Class<T> type, String name);
/**
* Convenience method. Equivalent to {@code getInstance(type,
* DEFAULT_NAME)}.
*/
<T> T getInstance(Class<T> type);
/**
* Gets a set of all registered names for the given type
* @param type The instance type
* @return A set of registered names or empty set if no instances are registered for that type
*/
Set<String> getInstanceNames(Class<?> type);
/**
* Sets the scope strategy for the current thread.
*/
void setScopeStrategy(Scope.Strategy scopeStrategy);
/**
* Removes the scope strategy for the current thread.
*/
void removeScopeStrategy();
}
由上面的代碼結合前面的例子,可以看出獲取對象和注入相關的接口是getInstance和inject的重載方法。這就是IoC功能的相關外部接口。那么,這個過程是如何實現的呢?
[IoC功能相關的數據結構----容器實現類ContainerImpl的成員變量]
要了解Struts2的IoC的實現,就需要從上面相關容器接口方法的具體實現來看。在Struts2中,實現這些相關方法的類是ContainerImpl,它實現了一個具體的容器的功能。通過對這個容器實現類進行解析,就可以大致了解IoC的實現原理。
在對相關接口的實現函數進行源代碼分析之前,首先說明下ContainerImpl類的一些與此相關的成員變量。
1、factories變量
final Map<Key<?>, InternalFactory<?>> factories;
factories是在生成容器對象時傳遞進來的參數,在之前的博文《Struts2的Builder模式》中介紹了參數的收集過程,并且對參數的作用作了一定的介紹。這里再簡要說明下,構造容器對象時傳遞進來的參數是<Key,InternalFactory>的鍵值對。Key代表的是一個對象的類型、類型的名稱,InternalFactory代表的是創建一個具體對象的工廠對象,在需要的時候,調用factory方法就可以生成這個對象。
factories是在ContainerImpl類的構造函數中被賦值的,就是參數傳遞進來的值。
2、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;
}
};
injectors 實際上是一個ReferenceCache對象,就是在之前的博文《Struts2緩存解析》中分析的Struts2的一個緩存實現。這里是緩存了類和其注入器之間的關系。對ReferenceCache類的create函數的重寫,主要是調用了ContainImpl類的addInjectors函數。這是實現緩存延遲加載原理中真正加載緩存對象的地方,在緩存中暫時找不到所要的對象時,就通過addInjectors來加載所需的對象。
addInjectors的代碼如下
void addInjectors( Class clazz, List<Injector> injectors ) {
if (clazz == Object.class) {
return;
}
addInjectors(clazz.getSuperclass(), injectors);//遞歸向上,為其父類實現注入,直到遇到Object為止
//變量注入
addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
//方法注入
addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
}
簡單說來addInjectors的作用就是遞歸向上依次實現注入的動作,實現變量注入和方法注入。
這里看不到真正執行注入的動作,繼續分析。來看addInjectorsForFields的代碼
void addInjectorsForFields( Field[] fields, boolean statics,List<Injector> injectors ) {
addInjectorsForMembers(Arrays. asList(fields), statics, injectors,
//匿名內部類,注意這個InjectorFactory對象
new InjectorFactory<Field>() {
@Override
public Injector create( ContainerImpl container, Field field,String name )
throws MissingDependencyException {
return new FieldInjector(container, field, name);//注意這里
}
});
}
從代碼上看,addInjectorsForFields也不是執行注入動作的地方,而是通過調用addInjectorsForMembers來實現。與此類似,對addInjectorsForMethods,也是通過調用addInjectorsInjectorsForMembers來執行注入的動作。也就是二者最終都匯集到了對一個方法的調用上,當主要的區別在于所傳遞的參數的不同之處。而影響最終注入動作的就是最后一個參數,InjectorFacotry對象。
區別點:
在addInjectorsForField中,這個InjectorFactory對象的create方法調用,返回的是一個FieldInjector對象;
在addInjectorsForMethods中,傳遞的InjectorFactory對象的create方法調用,返回的是一個MethodInjector對象。
下面來研究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 {
//inject.value()!!
injectors.add(injectorFactory.create( this, member, inject.value()));
} catch ( MissingDependencyException e ) {
if (inject.required()) {
throw new DependencyException(e);
}
}
}
}
}
}
注意在addInjectors方法中調用addInjectorsForFields和addInjectorsForMethods時,傳遞了一個false參數,傳遞到 addInjectorsForMembers時,就是這個statics參數為false。就是為其中非靜態的實例變量和實例方法來實現注入。對于靜態的類變量和類方法,這里不執行注入動作。關于靜態的情況,這里就不做討論了。。。
在上面的addInjectorForMembers方法中,主要看這兩條語句
Inject inject = member.getAnnotation( Inject. class );首先獲取這個成員(變量、方法)的注釋情況,如果對這個成員有“@Inject”的注釋,那么就執行這條語句injectors.add(injectorFactory.create( this , member, inject.value()));生成這個成員的注入器中,然后保存起來。
保存起來,保存到了何處?看整個調用過程,addInjectors(key, injectors);->
addInjectorsForFields(clazz.getDeclaredFields(), false , injectors);->
addInjectorsForMembers(...,injectors,...);
其實是保存在了由傳進來的參數指定的List<Injector>中,也就是設置的緩存變量ReferenceCache類型的injectors中。再回過頭來看
這個ReferenceCache類型的injectors緩存,它是ReferenceCache<Class<?>,List<Injector>>,也就是通過類類型,來獲取這個類的所有的注入器。
根據之前對緩存的講解,當找不到緩存值時,就會實現加載,也就是調用create。將調用create的值返回,也就是那些生成的注入器了。具體說來,就是當通過 injectors.get(XXX.class);針對這個類類型加載的注入器會被返回。也就是前面所說的保存的地方了!
[IoC的具體實現]
與Struts2的IoC相關的數據結構介紹完之后,現在來說下接口函數的實現,同時這里以對成員變量的注入為例,通過FieldInjector來看,注入操作到底做了什么。
首先來看 getInstance的實現。
其它的重載函數不予說明,這里給出的是真正執行動作的那個getInstance的實現。其它的重載函數最終都是通過對這個函數的調用來實現的。
<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);
}
}
類容不長,真正要注意的地方就是這句話return getFactory(key).create(context);通過制定的類型和名稱,來獲取能夠生成所需實例的工廠對象,其它的語句是對內外上下文的一些設置操作(內外上下文的具體差異,我也比較模糊)。
getFactory方法的方法體中,只有一句話
<T> InternalFactory<? extends T> getFactory( Key<T> key ) {
return (InternalFactory<T>) factories .get(key);
}
從上面的代碼來看,對getInstance的實現是非常簡單的,困難的過程是在于構造容器過程中收集參數的過程,在之前的一篇博文《Struts2的Builder模式》中有簡單地介紹。
再來看inject的實現。
也是忽略其它重載函數,給出執行最終動作的inject函數的代碼。inject方法的重載情況稍微復雜一點,有兩條路,這里對與前面的內容相關的那一條路進行介紹。整個的代碼如下
void inject( Object o, InternalContext context ) {
List<Injector> injectors = this .injectors .get(o.getClass());
for ( Injector injector : injectors ) {
injector.inject(context, o);
}
}
就是首先獲取這個對象的所有注入器,然后依次執行注入操作。至于,具體的注入動作是做哪些事情,這里看不出來,要通過對注入器的研究來了解。前面的內容中總共出現過兩類注入器 FieldInjector和MethodInjector。這里以FieldInjector為例來進行分解。對這個類的核心方法進行研究,下面是 FieldInjector類的inject函數的代碼。其它部分的代碼與注入功能的相關性不大,主要是做一些權限上的檢查和設置,這里就不與列出了。
public void inject( InternalContext context, Object o ) {
//change the external context
ExternalContext<?> previous = context.getExternalContext();
context.setExternalContext( externalContext );
try {
/*
* "factory.create()" method will create an instance of the source type,
* indicated by the value of the @Inject annotation
* set the field value
* */
field.set(o, factory.create(context));
} catch ( IllegalAccessException e ) {
throw new AssertionError(e);
} finally {
context.setExternalContext(previous);
}
}
在FieldInjector的構造函數中,需要傳遞三個參數,
public FieldInjector( ContainerImpl container, Field field, String name )
在構造函數中,對factory進行初始化
Key<?> key = Key. newInstance(field.getType(), name);
factory = container.getFactory(key);
注意看addInjectorsForMembers中所傳遞的參數,name參數對應的是inject.value(),也就是 @Inject(value="xxxx")或@Inject("")所指定的名稱。一般對應于struts.xml中配置的bean的名稱。在對 FieldInject例子來說,就是這個成員變量所將要真正對應的那個bean。
結合上面的代碼來看,FieldInjector的inject方法的主要動作就是field.set(o, factory.create(context));設置指定對象的某個成員變量的值。這個對象o就是通過inject入口傳進來的對象了。
所以,對FieldInjector的inject函數做個小結,就是設置某一對象的標有“@Inject”注釋的成員變量的值。
類似地,對MethodInjector的inject函數做個小結,就是調用某一對象的表用“@Inject”注釋的成員函數。
那么在進行對象注入,也就是調用inject方法的時候,是設置該對象的加標注的成員變量的值,以及調用加標注的成員函數。
來自:http://blog.csdn.net/yanlinwang/article/details/8944632
來自:http://blog.csdn.net/yanlinwang/article/details/8944632
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!