1. 程式人生 > >【Java】模擬Sping,實現其IOC和AOP核心(一)

【Java】模擬Sping,實現其IOC和AOP核心(一)

在這裡我要實現的是Spring的IOC和AOP的核心,而且有關IOC的實現,註解+XML能混合使用!

參考資料:
IOC:控制反轉(Inversion of Control,縮寫為IoC),是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。
AOP:Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。
(以上是度娘給出的說法,可以看到這樣的說法很不容易讓人理解,過於官方化,下面是我的理解)

IOC:我們平時在寫Java程式時,總要通過new的方式來產生一個物件,物件的生死存亡是與我們直接掛鉤的,我們需要它時,就new一個,不需要他時就通過GC幫我們回收;控制反轉的意思就是將物件的生死權交給第三方,由第三方來生成和銷燬物件,而我們只在需要時從第三方手中取獲取,其餘不管,這樣,物件的控制權就在第三方手裡,我們只有使用權!這就是所謂的控制反轉!
在Spring中,提供了XML檔案配置和註解的方式來向第三方表明我們需要第三方幫我們建立什麼物件,Spring就是這個第三方!它負責通過XML檔案的解析或者包掃描的方式,找到我們給出的對映關係,利用反射機制,在其內部幫我們“new”出物件,再儲存起來,供我們使用!

AOP :就是動態代理的體現,在Spring中就是利用JDKProxy或者CGLibProxy技術,對方法進行攔截!
比如說有一個叫做fun()的方法,我們在其執行前後對其攔截:

就像這樣,fun看成是縱向的線,那麼就相當於用平面把這條線截斷了!

 

有了上面的鋪墊,我們可以大概知道,Sping的IOC和AOP可以幫我們建立並管理物件,可以對物件的方法進行攔截,那麼這兩個技術合在一起,就可以達到自動幫我們建立、管理、並對物件進行攔截!

下面先給出我簡化的SpringIOC容器框圖:
模擬的IOC框圖

這是我簡化後的IOC框圖,實際上的SpringIOC是非常龐大的,裡面包含了許多介面,以及繼承關係,它把要處理的事務區分的非常細膩,將問題由大化小,層層遞減,把面向介面,高內聚低耦合體現的淋漓盡致。
Spring提供了註解和Xml方式的注入,所以後面會有兩個分支,分別處理註解和XML檔案的配置!

BeanFactory:
在別的地方說法是一個最底層容器,其實不要被其“誤導”,在我這它僅僅只是一個介面,只提供了最基礎的一些方法,而方法的具體實現就需要真正的高階容器了!程式碼如下:

複製程式碼

 1 public interface BeanFactory {
 2     String FACTORY_BEAN_PREFIX = "&";
 3     Object getBean(String var1) throws BeansException;
 4     <T> T getBean(String var1, Class<T> var2) throws BeansException;
 5     <T> T getBean(Class<T> var1) throws BeansException;
 6     Object getBean(String var1, Object... var2) throws BeansException;
 7     <T> T getBean(Class<T> var1, Object... var2) throws BeansException;
 8     boolean containsBean(String var1);
 9     boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
10     boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
11     boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
12     Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
13     String[] getAliases(String var1);
14 }

複製程式碼

(這裡我直接挪用了Spring的原始碼,由於是模擬實現,所以後面只實現了其getBean的方法)

 

ApplicationContext:
在別的地方的說法是一個高階容器,其實,它還是一個介面,只不過在原始碼中其繼承了許多介面(核心還是BeanFactory),是一個集大成者,提供了遠比BeanFactory更多的功能。但目前所要實現的核心暫時用不上它,所以暫時留一個空介面吧...

1 public interface ApplicationContext extends BeanFactory {
2     // TODO
3 }

說到這裡,就不能往下繼續了,因為在上面我們看到的,所謂的“容器”,僅僅是定義了介面,完全不能裝東西啊,還有,所謂的容器裡又要裝什麼?這裡就要引入Bean!

Bean:
其實就是IOC容器裡存放的東西!前面我說過,Spring會根據我們給出的對映關係,幫我們建立物件並存儲起來,那麼是否這個物件就是Bean?是!但也不是!如果說Spring僅僅只是幫我們管理物件,那麼它的功能也太單一了,那麼,現在不得不再次提到前面說過的AOP!
前面說到Spring中的AOP使用了JDKProxy和CGLibProxy這兩種代理技術,這兩種技術的目的都是產生代理物件,對方法進行攔截。那麼,是否這個代理物件就是我們的Bean?不完全是!Bean其實是原物件+代理物件!先不急,看到後面就會明白!

下面介紹兩種動態代理技術:
JDKProxy
使用JDK代理,其所代理的類,必須要實現某一個介面:

複製程式碼

 1 @SuppressWarnings("unchecked")
 2 public <E> E getJDKProxy(Class<?> klass, Object tagObject) {
 3     return (E) Proxy.newProxyInstance(klass.getClassLoader(), 
 4             klass.getInterfaces(),
 5             new InvocationHandler() {
 6                 @Override
 7                 public Object invoke(Object proxy, Method method, Object[] args) 
 8                         throws Throwable {
 9                     // TODO 置前攔截,可對引數args進行判斷
10                     Object result = null;
11                     try {
12                         result = method.invoke(tagObject, args);
13                     } catch (Exception e) {
14                         // TODO 對異常進行攔截
15                         throw e;
16                     }
17                     // TODO 置後攔截,可對方法返回值進行修改
18                     return result;
19                 }
20             });
21 }

複製程式碼

使用JDK代理的話就不得不傳入一個原始物件,所以如果不考慮CGLib代理,那麼Bean就是原始物件+代理物件!

 

CGLibProxy:
使用CGLib代理,是讓被代理的類作為代理物件的父類,故原類不能被final修飾,也不能對final修飾的方法攔截!

以下是網上絕大多數人給出的用法:

複製程式碼

 1 @SuppressWarnings("unchecked")
 2 public <E> E getCGLibProxy(Class<?> klass) {
 3     Enhancer enhancer = new Enhancer();
 4     enhancer.setSuperclass(klass); // 從這裡可以明顯看到,讓被代理的類作為了代理物件的父類
 5     enhancer.setCallback(new MethodInterceptor() {
 6         @Override
 7         public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) 
 8                 throws Throwable {
 9             // TODO 置前攔截,可對引數args進行判斷
10             Object result = null;
11             try {
12                 result = methodProxy.invokeSuper(proxyObject, args);
13             } catch (Exception e) {
14                 // TODO 對異常進行攔截
15                 throw e;
16             }
17             // TODO 置後攔截,可對方法返回值進行修改
18             return result;
19         }
20     });
21     return (E) enhancer.create();
22 }

複製程式碼

這種方式是沒錯,但是不適用於後面要做的,至於原因,後面分析到了就會明白!
所以使用如下方式:

複製程式碼

 1 @SuppressWarnings("unchecked")
 2 public <E> E getCGLibProxy(Class<?> klass, Object tagObject) {
 3     Enhancer enhancer = new Enhancer();
 4     enhancer.setSuperclass(klass);
 5     enhancer.setCallback(new MethodInterceptor() {
 6         @Override
 7         public Object intercept(Object proxyObject, Method method, Object[] args, MethodProxy methodProxy) 
 8                 throws Throwable {
 9             // TODO 置前攔截,可對引數args進行判斷
10             Object result = null;
11             try {
12                 result = method.invoke(tagObject, args);
13             } catch (Exception e) {
14                 // TODO 對異常進行攔截
15                 throw e;
16             }
17             // TODO 置後攔截,可對方法返回值進行修改
18             return result;
19         }
20     });
21     return (E) enhancer.create();
22 }

複製程式碼

由於是模擬實現,後面就全部採用CGLib代理!
可以看到以上面這種方式進行CGLib代理就需要原始物件,那麼前面說到的Bean就必須是原物件+代理物件!
當然我知道以invokeSuper那種方式是不需要原始物件,但是原因不是因為Bean,還在後面!

綜上,Bean的定義如下:

1 public interface BeanElement {
2     <E> E getProxy();
3     Object getObject();
4     boolean isDI(); // 用於以後判斷是否完成注入
5 }

在這裡我把BeanElement定義為了一個介面,後面會產生兩個分支,會產生兩種不同處理方式的Bean,用介面更靈活,耦合也低!

現在知道了Bean到底是什麼,我們就可以往下繼續進行:


AbstractApplicationContext:
ApplicationContext的具體實現類,但它是一個抽象類,只能實現部分功能,往後在它的基礎上還要分化成兩支,那麼,把所有的Bean存在這裡面再合適不過了!

複製程式碼

 1 public abstract class AbstractApplicationContext implements ApplicationContext {
 2     protected static final Map<String, String> beanNameMap;  // key : id      value : className
 3     protected static final Map<String, BeanElement> beanMap; // key : className value : Bean
 4     protected AopFactory aopFactory; // Aop工廠,生產代理物件
 5     
 6     static {
 7         beanNameMap = new HashMap<>();
 8         beanMap = new HashMap<>();
 9     }
10     
11     protected AbstractApplicationContext() {
12         aopFactory = new AopFactory();
13         // 設定切面
14         aopFactory.setAdvice(
15                 (new AdviceHander())
16                 .setIntercepterFactory(new IntercepterLoader()));
17     }
18     
19     protected void add(String id, String className, BeanElement bean) throws BeansException {
20         if (beanMap.containsKey(className)) {
21             // TODO 可以拋異常!
22             return;
23         }
24         beanMap.put(className, bean);
25         if (id.length() <= 0) return;
26         if (beanNameMap.containsKey(id)) {
27             throw new BeansException("bean:" + id + "已定義!");
28         }
29         beanNameMap.put(id, className);
30     }
31 }

複製程式碼

其中的aopFactory是代理工廠,負責生產代理,會在後面給出,先不急。
可以看到,AbstractApplicationContext這個類持有了兩張靜態的map,第一組是用來存取Bean的別名(id),第二組用來存放真正的Bean,這就是我們真正意義上的容器,由於其map都是static修飾的,在類載入的時候就存在了,所以往後有別的類繼承它時,這兩個map是共享的!只增加了一個add方法,只要是繼承自它的子類,都會通過這種方式新增Bean!並且這個類是protected的,不對外提供使用!

 

我們先進行左邊的分支,用註解的方式完成IOC
這裡說的註解都是自定義註解,屬於RUNTIME,就必須通過反射機制來獲取,反射機制就要得到類或者類名稱,那麼就先得到符合要求的類,這裡就要用到包掃描,我在前面的部落格中有介紹過:【Java】包、jar包的掃描
首先是對類的註解:
@Component

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.TYPE)
3 public @interface Component {
4     String id() default "";
5 }

其中的id相當於類名稱的別名,具有唯一性,如果不設定則不處理!通過這個註解我們就可以判斷哪個類是需要進行操作的,就應該自動地為這個類生成一個物件和代理物件,將其新增到beanMap中,就是bean的標誌!
如果說用過Spring的XML配置,這其實就相當於一個bean標籤:

1 <bean id="XXX" class="XXX">
2     ......
3 </bean>

註解中的id就相當於id屬性,我們是通過反射機制得到註解的,當然能得到類名稱,那就相當於有了class屬性!

但是僅僅有這個註解是完全不夠的,我們只能通過反射機制產生一個物件,但它的成員都沒賦值,僅僅是一具軀殼,所以就需要對成員添加註解,將我們需要的值注入進來;當然也可以給方法添加註解,通過setter方法給成員賦值,Spring就是使用的這種方式!
這是對成員的註解:
@Autowired

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
3 public @interface Autowired {
4     boolean requisite() default true;
5 }

這裡直接使用的spring原始碼,在原始碼中,這個註解可以成員、方法以及構造方法新增。其中的requisite是是否必須注入,這是Spring提供的更為細膩的操作,我的實現暫時不考慮它。

如果說這個註解是給成員新增的,那麼標誌著它需要賦值!使用這個註解的前提是它本身是一個複雜型別,不是基本型別,它的賦值,是將我們beanMap中的符合要求的Bean注入進去!至於基本型別後面有別的解決辦法。
用Component 和Autowired註解其實就相當於如下XML的配置:

1 <bean id="XXX" class="XXX">
2     <property name="XXX" ref="XXX">
3 </bean>

我們還是通過反射機制得到的Autowired註解,那麼一定可以得到成員名稱,和成員型別,成員名稱就相當於name屬性,通過成員型別就可以得到型別名稱,就相當於ref屬性!

如果說是給構造方法新增,那麼就規定了我們在反射機制執行時需要呼叫哪個構造方法,相當於如下:

1 <bean id="XXX" class="XXX">
2     <constructor-arg index="0" ref="XXX">
3     <constructor-arg index="1" ref="XXX">
4 </bean>

對於構造方法的處理我覺得使用註解的方式比XML配置要更好,註解可以直接定位到某一個構造方法,但是XML檔案的方式就需要遍歷比較,找出符合要求的,而且關於這個符合要求這一說還有更為複雜的問題,我會在後面用XML的方式詳細介紹!

還有一種就是對方法新增,實際上就是提供給setter方法使用的,通過執行setter方法給成員賦值,可能看到這裡會覺得這樣做是不是多次一舉,其實不是,因為這樣做會避免我後面會談到的迴圈依賴的問題!所以給方法新增,和對成員新增等效:

1 <bean id="XXX" class="XXX">
2     <property name="XXX" ref="XXX">
3 </bean>


上面我說過使用Autowired 是不處理基本型別的,它是將bean注入的,基本型別完全沒有必要作為bean,那麼,我們就可以給出一個註解,直接賦值:

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target({ElementType.FIELD, ElementType.PARAMETER})
3 public @interface Value {
4     String value();
5 }

其中value就是我們要注入的值,但是它是String型別的,可能需要強制型別轉換
演示如下:

複製程式碼

 1 @Component
 2 public class TestA {
 3     @Autowired
 4     private TestB testB;
 5     @Value(value="直接賦值")
 6     private String member;
 7     
 8     public TestA() {
 9     }
10 
11 }
12 
13 @Component(id = "testB")
14 public class TestB {
15     private int a;
16     private TestA testA;
17     
18     @Autowired
19     public TestB(@Value(value="10")int a, TestA testA) {
20         this.a = a;
21         this.testA = testA;
22     }
23     
24 }

複製程式碼

就相當於:

複製程式碼

1 <bean class="xxx.xxx.TestA">
2     <property name="testB" ref="xxx.xxx.TestB">
3     <property name="member" value="直接賦值">
4 </bean>
5 <bean id="testB" class="xxx.xxx.TestB">
6     <constructor-arg index="0" value="10"></constructor-arg>
7     <constructor-arg index="1" ref="xxx.xxx.TestA"></constructor-arg>
8 </bean>

複製程式碼

為了簡單處理,Autowired註解我在後面就只處理成員的。

有了上面的兩個註解是否夠呢?當然不夠。仔細想一想,如果說我們需要Spring幫我們建立的物件,其對應的類又被打成了jar包,那麼我們完全沒有辦法對已經形成jar包的程式碼添加註解;又或者說是我們需要建立的物件不是通過反射機制就能產生的,它是一個工廠物件,需要走工廠模式那一套來建立,上面的兩個註解就遠遠不能滿足我們的要求了!

 

@Bean

1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.METHOD)
3 public @interface Bean {
4     String id() default "";
5 }

可以看出這是對方法的註解,因為我們可以通過反射機制執行方法,將方法的返回值作為Bean的原始物件,再產生一個代理物件,這樣就能解決上面的所說的問題!

複製程式碼

 1 @Component
 2 public class Action {
 3     
 4     @Bean(id="getDocumentBuilderFactory")
 5     public DocumentBuilderFactory getDocumnet() throws Exception {
 6         return DocumentBuilderFactory.newInstance();
 7     }
 8     
 9     @Bean
10     public DocumentBuilder getDocumentBuilder(DocumentBuilderFactory factory) throws Exception {
11         return factory.newDocumentBuilder();
12     }
13     
14 }

複製程式碼

就相當於:

1 <bean class="xxx.xxx.Action "></bean>
2 <bean class="javax.xml.parsers.DocumentBuilderFactory" id="getDocumentBuilderFactory" 
3     factory-method="newInstance"></bean>
4 <bean class="javax.xml.parsers.DocumentBuilderFactory" 
5     factory-bean="getDocumentBuilderFactory" factory-method="newDocumentBuilder">
6 </bean>

有了這些註解,我們就能對包進行掃描,可以繼續向下進行!

AnnotationContext:
這個類繼承自AbstractApplicationContext,它也是protected的,不對外提供,我用它來進行有關注解的掃描和解析,但它的功能有限,不能完成全部的注入,這會涉及到注入的順序,以及注入之間的依賴關係:

複製程式碼

  1 /**
  2  * 執行包掃描
  3  * 將符合要求的結果新增到父類AbstractApplicationContext的beanMap中
  4  * 只處理@Component和@Bean註解
  5  * @Autowired註解留給下一級處理
  6  */
  7 public class AnnotationContext extends AbstractApplicationContext {
  8     // method緩衝區,儲存暫時不能執行的方法,其中的MethodBuffer用來封裝method,以及invoke時所需要的內容
  9     private List<MethodBuffer> methodList; 
 10 
 11     protected AnnotationContext() {
 12     }
 13     
 14     protected AnnotationContext(String packageName) {
 15         scanPackage(packageName);
 16     }
 17     
 18     /**
 19      * 通過aopFactory產生代理物件,將代理物件和原始物件封裝成bean新增到父類的map中
 20      */
 21     private void addBean(Class<?> klass, Object object, String id, String className) {
 22         // aopFactory是其父類AbstractApplicationContext的成員,原來產生代理物件
 23         Object proxy = aopFactory.creatCGLibProxy(klass, object);
 24         // AnnoationBean是BeanElement介面的一個實現類
 25         AnnotationBean bean = new AnnotationBean(object, proxy);
 26         // 父類AbstractApplicationContext的add方法
 27         add(id, className, bean);
 28     }
 29     
 30     protected void scanPackage(String packageName) {
 31         new PackageScanner() {
 32             @Override
 33             public void dealClass(Class<?> klass) {
 34                 if (!klass.isAnnotationPresent(Component.class)) return;
 35                 Component component = klass.getAnnotation(Component.class);
 36                 String className = klass.getName();
 37                 String name = component.id();
 38                 try {
 39                     // 這裡簡單起見,不考慮構造方法的過載,預設執行無參構造 
 40                     Object object = klass.newInstance();
 41                     // 產生BeanElement,加入到beanMap中
 42                     addBean(klass, object, name, className);
 43                     // 處理帶有@Bean註解的方法
 44                     dealMethod(klass, object);
 45                 } catch (Exception e) {
 46                     // TODO 
 47                     e.printStackTrace();
 48                 }
 49             }
 50         }.scanPackage(packageName);
 51         if (methodList == null) return;
 52         // 執行快取的所有方法
 53         for (MethodBuffer methodBuffer : methodList) {
 54             // 獲得方法執行所需要的東西
 55             String id = methodBuffer.getId();
 56             Class<?> returnClass = methodBuffer.getReturnClass();
 57             Method method = methodBuffer.getMethod();
 58             Object object = methodBuffer.getObject();
 59             Parameter[] parameters = methodBuffer.getParameters();
 60             
 61             try {
 62                 dealMultiParaMethod(returnClass, method, object, parameters, id);
 63             } catch (Exception e) {
 64                 // TODO 
 65                 e.printStackTrace();
 66             } 
 67         }
 68         methodList.clear();
 69     }
 70     
 71     private void dealMultiParaMethod(Class<?> returnClass, Method method, 
 72             Object object, Parameter[] parameters, String id) 
 73                     throws BeansException, IllegalAccessException, IllegalArgumentException, 
 74                     InvocationTargetException, ValueOnlyPrimitiveType {
 75         int count = parameters.length;
 76         Object[] result = new Object[count];
 77         
 78         for (int index = 0; index < count; index++) {
 79             Parameter para = parameters[index];
 80             // 判斷引數是否帶有@Value註解
 81             if (para.isAnnotationPresent(Value.class)) { 
 82                 Class<?> type = para.getType();
 83                 // 判斷@Value註解標識的引數是否是基本型別(八大型別和String)
 84                 if (!type.isPrimitive() && !type.equals(String.class)) {
 85                     throw new ValueOnlyPrimitiveType("Value只能用基本型別!");
 86                 }
 87                 // TypeConversion是我自己的一個工具類,用於將字串轉換成基本型別!
 88                 result[index] = TypeConversion.getValue(para.getAnnotation(Value.class).value(), 
 89                                             para.getType().getSimpleName());
 90             } else {
 91                 // 如果不是基本型別,那麼就需要從beanMap中獲取符合要求的bean
 92                 result[index] = getBean(para.getType());
 93             }
 94         }
 95         // 執行方法,獲得方法返回值
 96         Object returnObject = method.invoke(object, result);
 97         // 為方法執行結果建立bean,新增到beanMap中
 98         addBean(returnClass, returnObject , id, returnClass.getName());
 99     }
100     
101     /**
102      * 遍歷所有方法,處理帶有@Bean註解的方法
103      */
104     private void dealMethod(Class<?> klass, Object object) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
105         Method[] methods = klass.getDeclaredMethods();
106         for (Method method : methods) {
107             if (!method.isAnnotationPresent(Bean.class)) continue; 
108             
109             Class<?> returnType = method.getReturnType();
110             if (returnType.equals(void.class)) {
111                 // TODO 如果沒有返回值,那麼根本沒有必要做
112                 return;
113             }
114             String id= method.getAnnotation(Bean.class).id();
115             Parameter[] parameters = method.getParameters();
116             // 判斷方法是否有引數,沒有引數,直接執行,新增Bean
117             // 有引數就先快取起來,等所有都掃描完成後再執行
118             if (parameters.length <= 0) {
119                 Object returnObject = method.invoke(object);
120                 addBean(returnType, returnObject, id, returnType.getName());
121             } else {
122                 (methodList = methodList == null ? new ArrayList<>() : methodList)
123                     .add(new MethodBuffer(returnType, object, method, parameters, id));
124             }
125         }
126     }
127     
128 }

複製程式碼

這個類只負責掃描包,為帶有@Component註解的類通過反射機制生成物件,再通過代理工廠將其加工成代理物件,然後封裝再AnnotationBean中作為bean,將其新增到BeanMap中!
其次還處理了帶有@Bean註解的的方法,如果說是方法帶有引數,那麼就像將這個方法的執行延後,等所有東西都掃描完成後再執行;而對於無參的方法,則可以直接執行,併為執行結果產生的物件建立代理,生成AnnotationBean,新增進beanMap中。至於@Autowired註解這個類暫不處理,留給下一級處理!
AnnotationBean 類如下:

複製程式碼

 1 /**
 2  * 註解分支中BeanElement的具體實現類
 3  */
 4 public class AnnotationBean implements BeanElement {
 5     private boolean DI; // 判斷是否完成注入
 6     private Object object; // 原始物件
 7     private Object proxy; // 代理物件
 8     
 9     AnnotationBean() {
10         this(false, null, null);
11     }
12     
13     AnnotationBean(Object object, Object proxy) {
14         this(false, object, proxy);
15     }
16     
17     AnnotationBean(boolean dI, Object object, Object proxy) {
18         DI = dI;
19         setObject(object);
20         setProxy(proxy);
21     }
22     
23     @Override
24     public Object getObject() {
25         return object;
26     }
27     
28     AnnotationBean setObject(Object object) {
29         this.object = object;
30         return this;
31     }
32 
33     AnnotationBean setProxy(Object proxy) {
34         this.proxy = proxy;
35          return this;
36     }
37 
38     void setDI(boolean DI) {
39         this.DI = DI;
40     }
41     
42     @Override
43     public boolean isDI() {
44         return DI;
45     }
46 
47     @Override
48     @SuppressWarnings("unchecked")
49     public <E> E getProxy() {
50         return (E) proxy;
51     }
52     
53 }

複製程式碼

MethodBuffer 類如下:

複製程式碼

 1 /**
 2  *    帶有@Bean註解的方法封裝類
 3  */
 4 public class MethodBuffer {
 5     private String id; // bean的id
 6     private Class<?> returnType; // 方法返回值型別
 7     private Object object; // 方法執行所需物件
 8     private Method method; // 方法本身
 9     private Parameter[] parameters; // 方法所需引數
10     
11     MethodBuffer() {
12     }
13 
14     MethodBuffer(Class<?> returnType, Object object, Method method, Parameter[] parameters, String id) {
15         this.returnType = returnType;
16         this.object = object;
17         this.method = method;
18         this.parameters = parameters;
19         this.id = id;
20     }
21 
22     String getId() {
23         return id;
24     }
25     
26     Object getObject() {
27         return object;
28     }
29 
30     Class<?> getReturnType() {
31         return returnType;
32     }
33 
34     Method getMethod() {
35         return method;
36     }
37 
38     Parameter[] getParameters() {
39         return parameters;
40     }
41     
42 }

複製程式碼

*在處理@Bean註解時,就能發現我前面提出的問題,用CGLibProxy產生的代理為什麼還需要原始物件?
我們處理@Bean註解,是為了得到方法的返回值,如果直接對返回值所對應的類進行代理,產生代理物件,那麼,在方法執行時,如果原始物件的成員被賦值了,那麼代理物件是不會知道的,那麼產生的代理是不完整的!使用methodProxy.invokeSuper(proxyObjet, args)方法是不可行的了!所以一定要儲存原始物件,使用method.invoke(object, args)才是合理的!

AnnotationConfigApplicationContext:
這是註解這一支最高階的容器,是最終留給使用者使用的,用來完成最後@Autowired註解標識的成員的注入!
如果說是需要注入的bean都能找的到,且這些bean都完成了注入,那麼其注入過程會非常簡單,但若是,彼此之間的依賴關係比較複雜,你中有我,我中有你,會形成一個環形閉環,陷入死迴圈,這就是迴圈依賴!


迴圈依賴

 

這裡有四個類ClassA、ClassB、ClassC、ClassD,並且都沒有完成注入,如果現在想getBean得到ClassA的Bean,那麼就需要先對ClassA的Bean完成注入,但是其注入又需要ClassB,那麼,就需要先將B注入,但B又要C,那就先注入C,可是C需要D,只能先注入D,但是D確需要A!繞了一圈又回來了,陷入了死迴圈,這就是我們要解決的迴圈依賴!


解決方案:
一、前面我說過@Autowired註解可以給setter方法新增,用來解決迴圈依賴!
如果說我們使用這種方式給ClassA的setClassB方法新增@Autowired,而不是給其ClassB成員新增,那麼這個迴圈自然而然就不會出現!
二、假設自身以完成注入:
在ClassA注入之前,讓它的狀態變為完成注入,然後繼續找B,發現B沒注入,找C,C沒注入,找D,D沒注入,找A,此時A的狀態是完成注入,自然也就不會產生閉環!

所以AnnotationConfigApplicationContext就是為了最後的注入:

複製程式碼

 1 public class AnnotationConfigApplicationContext extends AnnotationContext {
 2     public AnnotationConfigApplicationContext() {
 3     }
 4     // 呼叫父類的構造
 5     public AnnotationConfigApplicationContext(String packageName) {
 6         super(packageName);
 7     }
 8     // Advice是代理的攔截處理,內部使用預設的一種方式,使用者也可以注入一種方式
 9     public AnnotationConfigApplicationContext setAdvice(Advice advice) {
10         aopFactory.setAdvice(advice);
11         return this;
12     }
13     
14     public AnnotationConfigApplicationContext parsePackage(String packageName) {
15         scanPackage(packageName);
16         return this;
17     }
18     
19     @Override
20     public Object getBean(String name) throws BeansException {
21         String className = beanNameMap.get(name);
22         return className == null ? get(name) : get(className);
23     }
24 
25     private <T> T get(String className, Class<?> klass) throws BeansException {
26         BeanElement bean =  beanMap.get(className);
27         if (bean == null) {
28             throw new BeansException("Bean :" + klass + "不存在!");
29         }
30         if (!bean.isDI() && bean instanceof AnnotationBean) {
31             autowired(klass, (AnnotationBean)bean);
32         }
33         return bean.getProxy();
34     }
35     
36     @Override
37     public <T> T getBean(Class<T> klass) throws BeansException {
38         return get(klass.getName());
39     }
40     
41     private void autowired(AnnotationBean bean) throws BeansException {
42         // 一開始令自身完成注入
43         bean.setDI(true);
44         Object object = bean.getObject();
45         Class<?> klass = object.getClass();    
46         Field[] fields = klass.getDeclaredFields();
47         Object arg = null;
48         for (Field field : fields) {
49             if (field.isAnnotationPresent(Value.class)) {
50                 try {
51                     // 注入基本型別的值
52                     arg = injectValue(field);
53                 } catch (ValueOnlyPrimitiveType e) {
54                     e.printStackTrace();
55                 }
56             } else if (field.isAnnotationPresent(Autowired.class)) {
57                 // 注入bean中的Bean
58                 arg = injectBean(field);
59             } else {
60                 continue;
61             }
62             try {
63                 // 成員注入
64                 field.setAccessible(true);
65                 field.set(object, arg);
66             } catch (Exception e) {
67                 throw new BeansException(klass + "依賴關係不正確!");
68             }
69         }
70     }
71     
72     private Object injectValue(Field field) throws ValueOnlyPrimitiveType {
73         Class<?> type = field.getType();
74         if (!type.isPrimitive() && !type.equals(String.class)) {
75             throw new ValueOnlyPrimitiveType("Value只能用於八大基本型別!");
76         }
77         Value value = field.getAnnotation(Value.class);
78         return TypeConversion.getValue(value.value(), type.getSimpleName());
79     }
80     
81     private Object injectBean(Field field) throws BeansException {
82         Class<?> fieldType = field.getType();
83         BeanElement fieldBean =  beanMap.get(fieldType.getName());
84         if (fieldBean == null) { return null;}
85         if (!fieldBean.isDI() && fieldBean instanceof AnnotationBean) {
86             autowired((AnnotationBean)fieldBean);
87         }
88         return fieldBean.getProxy();
89     }
90 }

複製程式碼

這裡解決迴圈依賴就使用了我上面給出的第二種方案,利用遞迴來實現!

註解部分的簡單實現已基本完成,雖然有些地方沒有處理或是處理的比較簡陋,但是SpringIOC的核心思想就是如此,只不過Spring實現的更為精緻、細膩!
來看看它的使用:
先給出幾個需要注入的類:

複製程式碼

 1 @Component(id="studentA")
 2 public class StudentA {
 3     @Value(value="我是A")
 4     String name;
 5     @Autowired
 6     private StudentB B;
 7     
 8     public StudentA() {
 9     }
10     
11     @Override
12     public String toString() {
13         return "A:" + name + "->" +  B;
14     }
15     
16 }
17 
18 @Component
19 public class StudentB {
20     @Value(value="我是B")
21     private String name;
22     @Autowired
23     private StudentC C;
24     
25     public StudentB() {
26     }
27 
28     @Override
29     public String toString() {
30         return "B:" + name + "->" + C;
31     }
32     
33 }
34 
35 @Component
36 public class StudentC {
37     @Value(value="我是C")
38     private String name;
39     
40     @Autowired
41     private StudentA A;
42     
43     public StudentC() {
44     }
45 
46     @Override
47     public String toString() {
48         return "C:" + name;
49     }
50     
51 }
52 
53 public class StudentD {
54     private String name;
55     @Autowired
56     private StudentA A;
57     
58     public StudentD(String name) {
59         this.name = name;
60     }
61 
62     @Override
63     public String toString() {
64         return "D:" + name + "->" + A;
65     }
66     
67 }
68 
69 @Component
70 public class MethodAction {
71     public MethodAction() {
72     }
73 
74     @Bean(id="studentD")
75     public StudentD getStudentD(@Value(value="我是D")String name) {
76         return new StudentD(name);
77     }
78 }

複製程式碼

主函式:

複製程式碼

1 public static void main(String[] args) throws BeansException {
2     ApplicationContext applicationContext = 
3                 new AnnotationConfigApplicationContext("com.zc.ioc.demo");
4         StudentD studentD = applicationContext.getBean(StudentD.class);
5         System.out.println(studentD);
6 
7 }

複製程式碼

結果是:

或者這樣使用:

1 public static void main(String[] args) throws BeansException {
2     BeanFactory beanFactory = new AnnotationConfigApplicationContext("com.zc.ioc.demo");
3         StudentD studentD = (StudentD) beanFactory.getBean("studentD");
4         System.out.println(studentD);
5 }

結果:

有關XML方式以及AOP的實現我會在下一篇給出。

鄭州檢查婦科去哪個醫院

鄭州婦科醫院

鄭州男科醫院哪裡好

鄭州婦科醫院