1. 程式人生 > >Struts2源碼學習(一)——Struts2中的XWork容器

Struts2源碼學習(一)——Struts2中的XWork容器

sta 註入 解耦 器) equal catch 若有 pojo read

  接下來記錄幾篇學習Struts2源碼的文章,希望能溫故而知新。


目錄:

  1, 為什麽引入容器

  2,容器的定義

  3,對象創建分析

  4,依賴註入分析

  5,對象創建和依賴註入的實現


  首先,了解為什麽框架要引入容器這個概念,容器能為框架做哪些事情。

  面向對象編程中,框架應當要考慮的問題:(1),如何創建需要的對象; (2),如何建立對象與對象之間的依賴關系;

  然後,引入容器之後,容器能為框架做到:(1),對象實例的創建和引用機制;(3),對象與其依賴的的關系的處理;

  引入容器的好處:(1),避免框架中對象的頻繁創建,提高程序執行效率; (2), 將對象的創建與業務代碼解耦; (3),明確對象的職責,更方便針對對象的業務邏輯進行測試。

  因此,框架應該定義其容器為一組一系列對象操作接口,其中包含對象實例的獲取(如何創建對象)已經處理對象之間的依賴(對象與其依賴的的關系的處理)這兩個方面。


  以上是關於容器概念性的一些結論,記住這些結論,可以讓我們帶著問題去探討接下來的問題,以及在面試中吹吹水。。

  認識XWork中的容器,首先看看XWork如何定義容器的:

 1 public interface Container extends Serializable {
 2     String DEFAULT_NAME = "default";
 3 
 4     void inject(Object var1);
5 6 <T> T inject(Class<T> var1); 7 8 <T> T getInstance(Class<T> var1, String var2); 9 10 <T> T getInstance(Class<T> var1); 11 12 Set<String> getInstanceNames(Class<?> var1); 13 14 void setScopeStrategy(Strategy var1); 15 16
void removeScopeStrategy(); 17 }

  可以看到,在Struts源碼的定義中,接口定義的方法證實之間描述的概念性的結論,接口中大致包括了以下幾個方面的方法:

  (1),獲取對象實例的方法——getInstance(用於接受容器托管的具體對象實例), getInstanceNames(對一個接口的多個不同實現類之間的實例獲取的管理)

  (2),處理對象依賴關系的方法——inject()

  (3),處理對象作用範圍的方法—— setScopeStrategy() removeScopeStrategy()

ps:在框架中,容器被設計為在系統初始化時就進行自身的初始化,並且能夠在系統全局任何編程層次中進行訪問的對象,這是容器的在使用方面我們需要記得的地方

  其中, 獲取對象實例的方法(getInstance和getInstanceNames),主要作用於聲明在XML文件中的 <bean> 標簽(包括框架內部bean以及自定義的bean)和 <constant> 標簽等,通過這兩個方法獲取容器管理的(對象依賴已被正確處理)對象實例。

   而處理依賴關系的方法(inject),建立起任意對象到框架元素溝通的橋梁, 其中方法傳入的參數是被註入依賴的對象,該方法會掃描傳入的對象中聲明有 @Inject 註解的字段,方法,構造函數,方法參數,並將它們註入容器托管對象,實現任意對象與容器對象之間的關系。 @Inject 註解的作用域在源碼中能找到,

1 @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.PARAMETER})
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface Inject {
4     String value() default "default";
5 
6     boolean required() default true;
7 }

  簡單了解容器的定義以及容器定義中的兩個主要功能方法之後,接下來通過源碼了解容器是如何實現創建對象以及依賴註入功能的。

  (1),對象創建

  容器 Container 被定義成一個借口,通過查看其實現類可以探索容器的內部的數據結構,

 1 class ContainerImpl implements Container {
 2     final Map<Key<?>, InternalFactory<?>> factories;
 3     final Map<Class<?>, Set<String>> factoryNamesByType;
 4     final Map<Class<?>, List<ContainerImpl.Injector>> injectors = new ReferenceCache<Class<?>, List<ContainerImpl.Injector>>() {
 5         protected List<ContainerImpl.Injector> create(Class<?> key) {
 6             List<ContainerImpl.Injector> injectors = new ArrayList();
 7             ContainerImpl.this.addInjectors(key, injectors);
 8             return injectors;
 9         }
10     };
11   ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {
12         this.factories = factories;
13         Map<Class<?>, Set<String>> map = new HashMap();
14 
15         Iterator i$;
16         Key key;
17         Object names;
18         for(i$ = factories.keySet().iterator(); i$.hasNext(); ((Set)names).add(key.getName())) {
19             key = (Key)i$.next();
20             names = (Set)map.get(key.getType());
21             if(names == null) {
22                 names = new HashSet();
23                 map.put(key.getType(), names);
24             }
25         }
26 
27         i$ = map.entrySet().iterator();
28 
29         while(i$.hasNext()) {
30             Entry<Class<?>, Set<String>> entry = (Entry)i$.next();
31             entry.setValue(Collections.unmodifiableSet((Set)entry.getValue()));
32         }
33 
34         this.factoryNamesByType = Collections.unmodifiableMap(map);
35     }
36   。。。省略代碼

  可以看到實現類中包含的兩個主要的數據結構Map, factories 和 factoryNamesByType,而在構造函數中可以發現, factoryNamesByType 是通過遍歷 factories 得到的。

  因此,深入了解 factories 中的數據結構可以知道容器的內部數據結構,可以發現鍵是 Key 類型, 值是 InternalFactory 類型

 1 class Key<T> {
 2     final Class<T> type;
 3     final String name;
 4     final int hashCode;
 5 
 6     private Key(Class<T> type, String name) {
 7         if(type == null) {
 8             throw new NullPointerException("Type is null.");
 9         } else if(name == null) {
10             throw new NullPointerException("Name is null.");
11         } else {
12             this.type = type;
13             this.name = name;
14             this.hashCode = type.hashCode() * 31 + name.hashCode();
15         }
16     }
17 
18     Class<T> getType() {
19         return this.type;
20     }
21 
22     String getName() {
23         return this.name;
24     }
25 
26     public int hashCode() {
27         return this.hashCode;
28     }
29 
30     public boolean equals(Object o) {
31         if(!(o instanceof Key)) {
32             return false;
33         } else if(o == this) {
34             return true;
35         } else {
36             Key other = (Key)o;
37             return this.name.equals(other.name) && this.type.equals(other.type);
38         }
39     }
40 
41     public String toString() {
42         return "[type=" + this.type.getName() + ", name=‘" + this.name + "‘]";
43     }
44 
45     static <T> Key<T> newInstance(Class<T> type, String name) {
46         return new Key(type, name);
47     }
48 }

  通過源碼可以發現, Key 類只是由兩個屬性構成的 pojo 類, 而其中的兩個屬性 name 和 type, 其實就對應著在XML文件中 <bean> 標簽中聲明的 name 和 type 屬性。

  接著查看鍵類型 InternalFactory 類的源碼,

1 interface InternalFactory<T> extends Serializable {
2     /*
3     * @param  context of this injection
4     * 
5     * @return instance to be injected
6     */  
7     T create(InternalContext var1);
8 }

  發現只是定義了一個方法的接口,聲明了對象的創建方法, 得知 factories 中存儲的是對象的實例構建方法。

  (2), 依賴註入

  在容器的實現類 ContainerImpl 中, 有這樣一段代碼,

 1 class ContainerImpl implements Container {
 2     。。。省略代碼
 3     final Map<Class<?>, List<ContainerImpl.Injector>> injectors = new ReferenceCache<Class<?>, List<ContainerImpl.Injector>>() {
 4         protected List<ContainerImpl.Injector> create(Class<?> key) {
 5             List<ContainerImpl.Injector> injectors = new ArrayList();
 6             ContainerImpl.this.addInjectors(key, injectors);
 7             return injectors;
 8         }
 9     };
10 }

  這個 Map 的作用是存儲對象以及對象之間的依賴關系。

  其中 ReferenceCache 是在運行期構建 Map 內容的機制,查看源碼

1 public abstract class ReferenceCache<K, V> extends ReferenceMap<K, V> {
2     。。。省略代碼
3     private static final long serialVersionUID = 0L;
4     transient ConcurrentMap<Object, Future<V>> futures = new ConcurrentHashMap();
5     transient ThreadLocal<Future<V>> localFuture = new ThreadLocal();
6 }

  其中維護的 ConcurrentMap和ThreadLocal處理多線程問題, 在類中聲明的 internalCreate(), get(), readObject() 方法都在操作這兩個維護的變量,即 Map 中的內容。

其工作源碼大致為:查找內部是否有緩存的對象,若有,直接返回,若沒有,調用 create 根據 key 的內容產生對象並緩存。

  回到容器實現類 ContainerImpl 中, 註入器的 ReferenceCache 中,鍵為 Class 對應的對象, 值為根據 Class 對象找到的所有隸屬於 Class 中的註入器(對於這一點,之後的源碼中,會講到掃描類中包含 @Inject 註解的方法或字段,會生成對應的 Injector 註入器), 鍵和值關系的建立是通過 ReferenceCache 中的 create() 方法。

  通過源碼查看 Injector 對象,

  技術分享

  發現 Injector 是容器實現類 ContainerImpl 的內部類,其中只定義了一個 inject 方法。

  然後繼續回到 ContainerImpl 中的 ReferenceCache 中的 injectors 中, 之前說過,ReferenceCache 中的鍵值是通過 create 方法建立聯系的, 而在 create 方法中

1 final Map<Class<?>, List<ContainerImpl.Injector>> injectors = new ReferenceCache<Class<?>, List<ContainerImpl.Injector>>() {
2         protected List<ContainerImpl.Injector> create(Class<?> key) {
3             List<ContainerImpl.Injector> injectors = new ArrayList();
4             ContainerImpl.this.addInjectors(key, injectors);
5             return injectors;
6         }
7     };

  又調用了 ContainerImpl 中的 addInjectors 方法,查看源碼,

 1     void addInjectors(Class clazz, List<ContainerImpl.Injector> injectors) {
 2         if(clazz != Object.class) {
 3             // 遞歸查找父類的註入器
 4             this.addInjectors(clazz.getSuperclass(), injectors);
 5             // 字段的註入器
 6             this.addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
 7             // 方法的註入器
 8             this.addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
 9         }
10     }

  繼續查看調用的方法

 1     void addInjectorsForMethods(Method[] methods, boolean statics, List<ContainerImpl.Injector> injectors) {
 2         this.addInjectorsForMembers(Arrays.asList(methods), statics, injectors, new ContainerImpl.InjectorFactory<Method>() {
 3             public ContainerImpl.Injector create(ContainerImpl container, Method method, String name) throws ContainerImpl.MissingDependencyException {
 4                 return new ContainerImpl.MethodInjector(container, method, name);
 5             }
 6         });
 7     }
 8 
 9     void addInjectorsForFields(Field[] fields, boolean statics, List<ContainerImpl.Injector> injectors) {
10         this.addInjectorsForMembers(Arrays.asList(fields), statics, injectors, new ContainerImpl.InjectorFactory<Field>() {
11             public ContainerImpl.Injector create(ContainerImpl container, Field field, String name) throws ContainerImpl.MissingDependencyException {
12                 return new ContainerImpl.FieldInjector(container, field, name);
13             }
14         });
15     }

  可以看到, 在 addInjectorsForMethods 和 addInjectorsForFields 方法中, 都是對於模板方法的調用, 不同的是聲明了不同的註入器, FieldInjector 和 MethodInjector,

繼續查看模板方法,

 1     <M extends Member & AnnotatedElement> void addInjectorsForMembers(List<M> members, boolean statics, List<ContainerImpl.Injector> injectors, ContainerImpl.InjectorFactory<M> injectorFactory) {
 2         Iterator i$ = members.iterator();
 3 
 4         while(true) {
 5             Member member;
 6             Inject inject;
 7             do {
 8                 do {
 9                     if(!i$.hasNext()) {
10                         return;
11                     }
12 
13                     member = (Member)i$.next();
14                 } while(this.isStatic(member) != statics);
15           // 重點 : 判斷是否具有 @Inject 註解
16                 inject = (Inject)((AnnotatedElement)member).getAnnotation(Inject.class);
17             } while(inject == null);
18 
19             try {    // 調用傳入的不同的 injectorFactory 的 create 方法, 創建對應的註入器
20                 injectors.add(injectorFactory.create(this, member, inject.value()));
21             } catch (ContainerImpl.MissingDependencyException var9) {
22                 if(inject.required()) {
23                     throw new DependencyException(var9);
24                 }
25             }
26         }
27     }

  至此,理清一遍依賴註入的原理, 通過掃描對象中具有 @Inject 註解的字段或方法,通過該標誌初始化相應的註入器,放入到 ContainerImpl 中的 ReferenceCache 類型中的 injectors 中,供之後的對象依賴註入使用。 @Inject 註解在框架內部作為一個標誌,被轉換成不同的 Injectot 對象, 從而實施依賴註入。

  (3), 模板方法, 對象創建以及依賴註入的最終實現

  對於對象創建的實現

  在 ContainerImpl 中, 查看 getInstance 方法

1     public <T> T getInstance(final Class<T> type, final String name) {
2         return this.callInContext(new ContainerImpl.ContextualCallable<T>() {
3             public T call(InternalContext context) {
4                 return ContainerImpl.this.getInstance(type, name, context);
5             }
6         });
7     }

  發現調用的是一個模板方法,

 1     <T> T callInContext(ContainerImpl.ContextualCallable<T> callable) {
 2         Object[] reference = (Object[])this.localContext.get();
 3         if(reference[0] == null) {
 4             reference[0] = new InternalContext(this);
 5 
 6             Object var3;
 7             try {
 8                 var3 = callable.call((InternalContext)reference[0]);
 9             } finally {
10                 reference[0] = null;
11                 this.localContext.remove();
12             }
13 
14             return var3;
15         } else {
16             return callable.call((InternalContext)reference[0]);
17         }
18     }

  模板方法只是提供一個線程安全的上下文執行環境,最終調用的方法還是傳入的 ContextualCallable 接口的實現的 call 方法,回到 getInstance 方法中, call 方法中調用的是

getInstance 方法,

 1     <T> T getInstance(Class<T> type, String name, InternalContext context) {
 2         ExternalContext<?> previous = context.getExternalContext();
 3         Key<T> key = Key.newInstance(type, name);
 4         context.setExternalContext(ExternalContext.newInstance((Member)null, key, this));
 5 
 6         Object var7;
 7         try {
 8             InternalFactory o = this.getFactory(key);
 9             if(o != null) {
10                 var7 = this.getFactory(key).create(context);
11                 return var7;
12             }
13 
14             var7 = null;
15         } finally {
16             context.setExternalContext(previous);
17         }
18 
19         return var7;
20     }

  這段代碼,就是根據 type 和 name 構建 Key, 並在 ContainerImpl 維護的 Map 類型的 factories 變量中尋找對應的 InternalFactory 對象,調用對應的 create 方法創建對象。

  

  對於依賴註入的實現,

  對於依賴註入,在之前的分析中提到過, @Inject 註解作為標誌, 在框架內部最終被轉換成不同的 Injector 對象, FieldInjector 和 MethodInjector, 深入了解這兩個類的源碼

 1    static class FieldInjector implements ContainerImpl.Injector {
 2         final Field field;
 3         final InternalFactory<?> factory;
 4         final ExternalContext<?> externalContext;
 5 
 6         //  在構造器中進行數據準備
 7         public FieldInjector(ContainerImpl container, Field field, String name) throws ContainerImpl.MissingDependencyException {
 8             this.field = field;
 9             if(!field.isAccessible()) {
10                 SecurityManager sm = System.getSecurityManager();
11           
12                 try {
13                     if(sm != null) {
14                         sm.checkPermission(new ReflectPermission("suppressAccessChecks"));
15                     }
16 
17                     field.setAccessible(true);
18                 } catch (AccessControlException var6) {
19                     throw new DependencyException("Security manager in use, could not access field: " + field.getDeclaringClass().getName() + "(" + field.getName() + ")", var6);
20                 }
21             }
22 
23             Key<?> key = Key.newInstance(field.getType(), name);
24             this.factory = container.getFactory(key);
25             if(this.factory == null) {
26                 throw new ContainerImpl.MissingDependencyException("No mapping found for dependency " + key + " in " + field + ".");
27             } else {
28                 this.externalContext = ExternalContext.newInstance(field, key, container);
29             }
30         }
31 
32         // 為屬性賦值
33         public void inject(InternalContext context, Object o) {
34             ExternalContext<?> previous = context.getExternalContext();
35             context.setExternalContext(this.externalContext);
36 
37             try {
38                 this.field.set(o, this.factory.create(context));
39             } catch (IllegalAccessException var8) {
40                 throw new AssertionError(var8);
41             } finally {
42                 context.setExternalContext(previous);
43             }
44 
45         }
46     }
 1     static class MethodInjector implements ContainerImpl.Injector {
 2         final Method method;
 3         final ContainerImpl.ParameterInjector<?>[] parameterInjectors;
 4      // 在構造器中進行數據準備
 5         public MethodInjector(ContainerImpl container, Method method, String name) throws ContainerImpl.MissingDependencyException {
 6             this.method = method;
 7             if(!method.isAccessible()) {
 8                 SecurityManager sm = System.getSecurityManager();
 9 
10                 try {
11                     if(sm != null) {
12                         sm.checkPermission(new ReflectPermission("suppressAccessChecks"));
13                     }
14 
15                     method.setAccessible(true);
16                 } catch (AccessControlException var6) {
17                     throw new DependencyException("Security manager in use, could not access method: " + name + "(" + method.getName() + ")", var6);
18                 }
19             }
20             // 查找方法參數的 Injector
21             Class<?>[] parameterTypes = method.getParameterTypes();
22             if(parameterTypes.length == 0) {
23                 throw new DependencyException(method + " has no parameters to inject.");
24             } else {
25                 this.parameterInjectors = container.getParametersInjectors(method, method.getParameterAnnotations(), parameterTypes, name);
26             }
27         }
28         // 調用方法實施依賴註入
29         public void inject(InternalContext context, Object o) {
30             try {
31                 this.method.invoke(o, ContainerImpl.getParameters(this.method, context, this.parameterInjectors));
32             } catch (Exception var4) {
33                 throw new RuntimeException(var4);
34             }
35         }
36     }

  可以看到 FieldInjector 和 MethodInjector 最終調用的 inject 方法都使用反射的方法實現依賴註入。

  除了 FieldInjector 和 MethodInjector, 容器 ContainerImpl 自身也有一個 inject 方法,

 1     void inject(Object o, InternalContext context) {
 2         List<ContainerImpl.Injector> injectors = (List)this.injectors.get(o.getClass());
 3         Iterator i$ = injectors.iterator();
 4 
 5         while(i$.hasNext()) {
 6             ContainerImpl.Injector injector = (ContainerImpl.Injector)i$.next();
 7             injector.inject(context, o);
 8         }
 9 
10     }

  該方法遍歷了所有的 Injector , 並調用實現類所實現的 inject 方法而已。


  至此,了解了 Struts2 中 XWork 容器的源碼的分析實現流程,從容器的兩大功能,對象創建以及依賴註入,一步步的分析到最後。

  

Struts2源碼學習(一)——Struts2中的XWork容器