在這裡我要實現的是Spring的IOC和AOP的核心,而且有關IOC的實現,註解+XML能混合使用! 參考資料: IOC:控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴註入(Depend ...
在這裡我要實現的是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,那麼,我們就可以給出一個註解,直接賦值:
@Value
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