javascript
自己动手实现的 Spring IOC 和 AOP - 上篇
1. 背景
我在大四實(shí)習(xí)的時(shí)候開始接觸 J2EE 方面的開發(fā)工作,也是在同時(shí)期接觸并學(xué)習(xí) Spring 框架,到現(xiàn)在也有快有兩年的時(shí)間了。不過之前沒有仿寫過 Spring IOC 和 AOP,只是宏觀上對 Spring IOC 和 AOP 原理有一定的認(rèn)識(shí)。所以為了更進(jìn)一步理解 Spring IOC 和 AOP 原理。在工作之余,參考了一些資料和代碼,動(dòng)手實(shí)現(xiàn)了一個(gè)簡單的 IOC 和 AOP,并實(shí)現(xiàn)了如下功能:
在實(shí)現(xiàn)自己的 IOC 和 AOP 前,我的想法比較簡單,就是實(shí)現(xiàn)一個(gè)非常簡單的 IOC 和 AOP,哪怕是幾十行代碼實(shí)現(xiàn)的都行。后來實(shí)現(xiàn)后,感覺還很有意思的。不過那個(gè)實(shí)現(xiàn)太過于簡單,和 Spring IOC,AOP 相去甚遠(yuǎn)。后來想了一下,不能僅滿足那個(gè)簡單的實(shí)現(xiàn),于是就有了這個(gè)仿寫項(xiàng)目。相對來說仿寫的代碼要復(fù)雜了一些,功能也多了一點(diǎn),看起來也有點(diǎn)樣子的。盡管仿寫出的項(xiàng)目仍然是玩具級,不過寫仿寫的過程中,還是學(xué)到了一些東西??傮w上來說,收獲還是很大的。在接下來文章中,我也將從易到難,實(shí)現(xiàn)不同版本的 IOC 和 AOP。好了,不多說了,開始干活。
?2. 簡單的 IOC 和 AOP 實(shí)現(xiàn)
?2.1 簡單的 IOC
先從簡單的 IOC 容器實(shí)現(xiàn)開始,最簡單的 IOC 容器只需4步即可實(shí)現(xiàn),如下:
如上所示,僅需4步即可,是不是覺得很簡單。好了,Talk is cheap, Show me the code. 接下來要上代碼了。不過客官別急,上代碼前,容我對代碼結(jié)構(gòu)做一下簡單介紹:
| 1 2 3 4 5 | SimpleIOC // IOC 的實(shí)現(xiàn)類,實(shí)現(xiàn)了上面所說的4個(gè)步驟 SimpleIOCTest // IOC 的測試類 Car // IOC 測試使用的 bean Wheel // 同上 ioc.xml // bean 配置文件 |
容器實(shí)現(xiàn)類 SimpleIOC 的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | public class SimpleIOC {private Map<String, Object> beanMap = new HashMap<>();public SimpleIOC(String location) throws Exception {loadBeans(location);}public Object getBean(String name) {Object bean = beanMap.get(name);if (bean == null) {throw new IllegalArgumentException("there is no bean with name " + name);}return bean;}private void loadBeans(String location) throws Exception {// 加載 xml 配置文件InputStream inputStream = new FileInputStream(location);DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder docBuilder = factory.newDocumentBuilder();Document doc = docBuilder.parse(inputStream);Element root = doc.getDocumentElement();NodeList nodes = root.getChildNodes();// 遍歷 <bean> 標(biāo)簽for (int i = 0; i < nodes.getLength(); i++) {Node node = nodes.item(i);if (node instanceof Element) {Element ele = (Element) node;String id = ele.getAttribute("id");String className = ele.getAttribute("class");// 加載 beanClassClass beanClass = null;try {beanClass = Class.forName(className);} catch (ClassNotFoundException e) {e.printStackTrace();return;}// 創(chuàng)建 beanObject bean = beanClass.newInstance();// 遍歷 <property> 標(biāo)簽NodeList propertyNodes = ele.getElementsByTagName("property");for (int j = 0; j < propertyNodes.getLength(); j++) {Node propertyNode = propertyNodes.item(j);if (propertyNode instanceof Element) {Element propertyElement = (Element) propertyNode;String name = propertyElement.getAttribute("name");String value = propertyElement.getAttribute("value");// 利用反射將 bean 相關(guān)字段訪問權(quán)限設(shè)為可訪問Field declaredField = bean.getClass().getDeclaredField(name);declaredField.setAccessible(true);if (value != null && value.length() > 0) {// 將屬性值填充到相關(guān)字段中declaredField.set(bean, value);} else {String ref = propertyElement.getAttribute("ref");if (ref == null || ref.length() == 0) {throw new IllegalArgumentException("ref config error");}// 將引用填充到相關(guān)字段中declaredField.set(bean, getBean(ref));}// 將 bean 注冊到 bean 容器中registerBean(id, bean);}}}}}private void registerBean(String id, Object bean) {beanMap.put(id, bean);} } |
容器測試使用的 bean 代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class Car {private String name;private String length;private String width;private String height;private Wheel wheel;// 省略其他不重要代碼 }public class Wheel {private String brand;private String specification ;// 省略其他不重要代碼 } |
bean 配置文件 ioc.xml 內(nèi)容:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <beans><bean id="wheel" class="com.titizz.simulation.toyspring.Wheel"><property name="brand" value="Michelin" /><property name="specification" value="265/60 R18" /></bean><bean id="car" class="com.titizz.simulation.toyspring.Car"><property name="name" value="Mercedes Benz G 500"/><property name="length" value="4717mm"/><property name="width" value="1855mm"/><property name="height" value="1949mm"/><property name="wheel" ref="wheel"/></bean> </beans> |
IOC 測試類 SimpleIOCTest:
| 1 2 3 4 5 6 7 8 9 10 11 | public class SimpleIOCTest {@Testpublic void getBean() throws Exception {String location = SimpleIOC.class.getClassLoader().getResource("spring-test.xml").getFile();SimpleIOC bf = new SimpleIOC(location);Wheel wheel = (Wheel) bf.getBean("wheel");System.out.println(wheel);Car car = (Car) bf.getBean("car");System.out.println(car);} } |
測試結(jié)果:
以上是簡單 IOC 實(shí)現(xiàn)的全部內(nèi)容,難度不大,代碼也不難看懂,這里不再多說了。下面說說簡單 AOP 的實(shí)現(xiàn)。
?2.2 簡單的 AOP 實(shí)現(xiàn)
AOP 的實(shí)現(xiàn)是基于代理模式的,這一點(diǎn)相信大家應(yīng)該都知道。代理模式是AOP實(shí)現(xiàn)的基礎(chǔ),代理模式不難理解,這里就不花篇幅介紹了。在介紹 AOP 的實(shí)現(xiàn)步驟之前,先引入 Spring AOP 中的一些概念,接下來我們會(huì)用到這些概念。
通知(Advice)
| 1 2 3 4 5 6 7 | 通知定義了要織入目標(biāo)對象的邏輯,以及執(zhí)行時(shí)機(jī)。 Spring 中對應(yīng)了 5 種不同類型的通知: · 前置通知(Before):在目標(biāo)方法執(zhí)行前,執(zhí)行通知 · 后置通知(After):在目標(biāo)方法執(zhí)行后,執(zhí)行通知,此時(shí)不關(guān)系目標(biāo)方法返回的結(jié)果是什么 · 返回通知(After-returning):在目標(biāo)方法執(zhí)行后,執(zhí)行通知 · 異常通知(After-throwing):在目標(biāo)方法拋出異常后執(zhí)行通知 · 環(huán)繞通知(Around): 目標(biāo)方法被通知包裹,通知在目標(biāo)方法執(zhí)行前和執(zhí)行后都被會(huì)調(diào)用 |
切點(diǎn)(Pointcut)
| 1 2 | 如果說通知定義了在何時(shí)執(zhí)行通知,那么切點(diǎn)就定義了在何處執(zhí)行通知。所以切點(diǎn)的作用就是 通過匹配規(guī)則查找合適的連接點(diǎn)(Joinpoint),AOP 會(huì)在這些連接點(diǎn)上織入通知。 |
切面(Aspect)
| 1 | 切面包含了通知和切點(diǎn),通知和切點(diǎn)共同定義了切面是什么,在何時(shí),何處執(zhí)行切面邏輯。 |
說完概念,接下來我們來說說簡單 AOP 實(shí)現(xiàn)的步驟。這里 AOP 是基于 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)的,只需3步即可完成:
上面步驟比較簡單,不過在實(shí)現(xiàn)過程中,還是有一些難度的,這里要引入一些輔助接口才能實(shí)現(xiàn)。接下來就來介紹一下簡單 AOP 的代碼結(jié)構(gòu):
| 1 2 3 4 5 6 7 | MethodInvocation 接口 // 實(shí)現(xiàn)類包含了切面邏輯,如上面的 logMethodInvocation Advice 接口 // 繼承了 InvocationHandler 接口 BeforeAdvice 類 // 實(shí)現(xiàn)了 Advice 接口,是一個(gè)前置通知 SimpleAOP 類 // 生成代理類 SimpleAOPTest // SimpleAOP 從測試類 HelloService 接口 // 目標(biāo)對象接口 HelloServiceImpl // 目標(biāo)對象 |
MethodInvocation 接口代碼:
| 1 2 3 | public interface MethodInvocation {void invoke(); } |
Advice 接口代碼:
| 1 | public interface Advice extends InvocationHandler {} |
BeforeAdvice 實(shí)現(xiàn)代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class BeforeAdvice implements Advice {private Object bean;private MethodInvocation methodInvocation;public BeforeAdvice(Object bean, MethodInvocation methodInvocation) {this.bean = bean;this.methodInvocation = methodInvocation;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在目標(biāo)方法執(zhí)行前調(diào)用通知methodInvocation.invoke();return method.invoke(bean, args);} } |
SimpleAOP 實(shí)現(xiàn)代碼:
| 1 2 3 4 5 6 | public class SimpleAOP {public static Object getProxy(Object bean, Advice advice) {return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader(), bean.getClass().getInterfaces(), advice);} } |
HelloService 接口,及其實(shí)現(xiàn)類代碼:
| 1 2 3 4 5 6 7 8 9 10 | public interface HelloService {void sayHelloWorld(); }public class HelloServiceImpl implements HelloService {@Overridepublic void sayHelloWorld() {System.out.println("hello world!");} } |
SimpleAOPTest 代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class SimpleAOPTest {@Testpublic void getProxy() throws Exception {// 1. 創(chuàng)建一個(gè) MethodInvocation 實(shí)現(xiàn)類MethodInvocation logTask = () -> System.out.println("log task start");HelloServiceImpl helloServiceImpl = new HelloServiceImpl();// 2. 創(chuàng)建一個(gè) AdviceAdvice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask);// 3. 為目標(biāo)對象生成代理HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl,beforeAdvice);helloServiceImplProxy.sayHelloWorld();} } |
輸出結(jié)果:
以上實(shí)現(xiàn)了簡單的 IOC 和 AOP,不過實(shí)現(xiàn)的 IOC 和 AOP 還很簡單,且只能獨(dú)立運(yùn)行。在下一篇文章中,我將實(shí)現(xiàn)一個(gè)較為復(fù)雜的 IOC 和 AOP,大家如果有興趣可以去看看。好了,本篇文章到此結(jié)束。
?附錄:Spring 源碼分析文章列表
?Ⅰ. IOC
| 2018-05-30 | Spring IOC 容器源碼分析系列文章導(dǎo)讀 |
| 2018-06-01 | Spring IOC 容器源碼分析 - 獲取單例 bean |
| 2018-06-04 | Spring IOC 容器源碼分析 - 創(chuàng)建單例 bean 的過程 |
| 2018-06-06 | Spring IOC 容器源碼分析 - 創(chuàng)建原始 bean 對象 |
| 2018-06-08 | Spring IOC 容器源碼分析 - 循環(huán)依賴的解決辦法 |
| 2018-06-11 | Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象 |
| 2018-06-11 | Spring IOC 容器源碼分析 - 余下的初始化工作 |
?Ⅱ. AOP
| 2018-06-17 | Spring AOP 源碼分析系列文章導(dǎo)讀 |
| 2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
| 2018-06-20 | Spring AOP 源碼分析 - 創(chuàng)建代理對象 |
| 2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執(zhí)行過程 |
?Ⅲ. MVC
| 2018-06-29 | Spring MVC 原理探秘 - 一個(gè)請求的旅行過程 |
| 2018-06-30 | Spring MVC 原理探秘 - 容器的創(chuàng)建過程 |
- 本文鏈接:?https://www.tianxiaobo.com/2018/01/18/自己動(dòng)手實(shí)現(xiàn)的-Spring-IOC-和-AOP-上篇/
from:http://www.tianxiaobo.com/2018/01/18/%E8%87%AA%E5%B7%B1%E5%8A%A8%E6%89%8B%E5%AE%9E%E7%8E%B0%E7%9A%84-Spring-IOC-%E5%92%8C-AOP-%E4%B8%8A%E7%AF%87/?
總結(jié)
以上是生活随笔為你收集整理的自己动手实现的 Spring IOC 和 AOP - 上篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己动手实现一个简单的JSON解析器
- 下一篇: 自己动手实现的 Spring IOC 和