摘要:不過(guò)那個(gè)實(shí)現(xiàn)太過(guò)于簡(jiǎn)單,和,相去甚遠(yuǎn)。在接下來(lái)文章中,我也將從易到難,實(shí)現(xiàn)不同版本的和。切面切面包含了通知和切點(diǎn),通知和切點(diǎn)共同定義了切面是什么,在何時(shí),何處執(zhí)行切面邏輯。
1. 背景
我在大四實(shí)習(xí)的時(shí)候開(kāi)始接觸 J2EE 方面的開(kāi)發(fā)工作,也是在同時(shí)期接觸并學(xué)習(xí) Spring 框架,到現(xiàn)在也有快有兩年的時(shí)間了。不過(guò)之前沒(méi)有仿寫(xiě)過(guò) Spring IOC 和 AOP,只是宏觀上對(duì) Spring IOC 和 AOP 原理有一定的認(rèn)識(shí)。所以為了更進(jìn)一步理解 Spring IOC 和 AOP 原理。在工作之余,參考了一些資料和代碼,動(dòng)手實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 IOC 和 AOP,并實(shí)現(xiàn)了如下功能:
根據(jù) xml 配置文件加載相關(guān) bean
對(duì) BeanPostProcessor 類型的 bean 提供支持
對(duì) BeanFactoryAware 類型的 bean 提供支持
實(shí)現(xiàn)了基于 JDK 動(dòng)態(tài)代理的 AOP
整合了 IOC 和 AOP,使得二者可很好的協(xié)同工作
在實(shí)現(xiàn)自己的 IOC 和 AOP 前,我的想法比較簡(jiǎn)單,就是實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的 IOC 和 AOP,哪怕是幾十行代碼實(shí)現(xiàn)的都行。后來(lái)實(shí)現(xiàn)后,感覺(jué)還很有意思的。不過(guò)那個(gè)實(shí)現(xiàn)太過(guò)于簡(jiǎn)單,和 Spring IOC,AOP 相去甚遠(yuǎn)。后來(lái)想了一下,不能僅滿足那個(gè)簡(jiǎn)單的實(shí)現(xiàn),于是就有了這個(gè)仿寫(xiě)項(xiàng)目。相對(duì)來(lái)說(shuō)仿寫(xiě)的代碼要復(fù)雜了一些,功能也多了一點(diǎn),看起來(lái)也有點(diǎn)樣子的。盡管仿寫(xiě)出的項(xiàng)目仍然是玩具級(jí),不過(guò)寫(xiě)仿寫(xiě)的過(guò)程中,還是學(xué)到了一些東西??傮w上來(lái)說(shuō),收獲還是很大的。在接下來(lái)文章中,我也將從易到難,實(shí)現(xiàn)不同版本的 IOC 和 AOP。好了,不多說(shuō)了,開(kāi)始干活。
2. 簡(jiǎn)單的 IOC 和 AOP 實(shí)現(xiàn) 2.1 簡(jiǎn)單的 IOC先從簡(jiǎn)單的 IOC 容器實(shí)現(xiàn)開(kāi)始,最簡(jiǎn)單的 IOC 容器只需4步即可實(shí)現(xiàn),如下:
加載 xml 配置文件,遍歷其中的
獲取
遍歷
將 bean 注冊(cè)到 bean 容器中
如上所示,僅需4步即可,是不是覺(jué)得很簡(jiǎn)單。好了,Talk is cheap, Show me the code. 接下來(lái)要上代碼了。不過(guò)客官別急,上代碼前,容我對(duì)代碼結(jié)構(gòu)做一下簡(jiǎn)單介紹:
SimpleIOC // IOC 的實(shí)現(xiàn)類,實(shí)現(xiàn)了上面所說(shuō)的4個(gè)步驟 SimpleIOCTest // IOC 的測(cè)試類 Car // IOC 測(cè)試使用的 bean Wheel // 同上 ioc.xml // bean 配置文件
容器實(shí)現(xiàn)類 SimpleIOC 的代碼:
public class SimpleIOC { private MapbeanMap = 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(); // 遍歷 標(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"); // 加載 beanClass Class beanClass = null; try { beanClass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } // 創(chuàng)建 bean Object bean = beanClass.newInstance(); // 遍歷 標(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)字段訪問(wèn)權(quán)限設(shè)為可訪問(wèn) 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 注冊(cè)到 bean 容器中 registerBean(id, bean); } } } } } private void registerBean(String id, Object bean) { beanMap.put(id, bean); } }
容器測(cè)試使用的 bean 代碼:
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)容:
IOC 測(cè)試類 SimpleIOCTest:
public class SimpleIOCTest { @Test public 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); } }
測(cè)試結(jié)果:
以上是簡(jiǎn)單 IOC 實(shí)現(xiàn)的全部?jī)?nèi)容,難度不大,代碼也不難看懂,這里不再多說(shuō)了。下面說(shuō)說(shuō)簡(jiǎn)單 AOP 的實(shí)現(xiàn)。
2.2 簡(jiǎn)單的 AOP 實(shí)現(xiàn)AOP 的實(shí)現(xiàn)是基于代理模式的,這一點(diǎn)相信大家應(yīng)該都知道。代理模式是AOP實(shí)現(xiàn)的基礎(chǔ),代理模式不難理解,這里就不花篇幅介紹了。在介紹 AOP 的實(shí)現(xiàn)步驟之前,先引入 Spring AOP 中的一些概念,接下來(lái)我們會(huì)用到這些概念。
通知(Advice)
通知定義了要織入目標(biāo)對(duì)象的邏輯,以及執(zhí)行時(shí)機(jī)。 Spring 中對(duì)應(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)
如果說(shuō)通知定義了在何時(shí)執(zhí)行通知,那么切點(diǎn)就定義了在何處執(zhí)行通知。所以切點(diǎn)的作用就是 通過(guò)匹配規(guī)則查找合適的連接點(diǎn)(Joinpoint),AOP 會(huì)在這些連接點(diǎn)上織入通知。
切面(Aspect)
切面包含了通知和切點(diǎn),通知和切點(diǎn)共同定義了切面是什么,在何時(shí),何處執(zhí)行切面邏輯。
說(shuō)完概念,接下來(lái)我們來(lái)說(shuō)說(shuō)簡(jiǎn)單 AOP 實(shí)現(xiàn)的步驟。這里 AOP 是基于 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)的,只需3步即可完成:
定義一個(gè)包含切面邏輯的對(duì)象,這里假設(shè)叫 logMethodInvocation
定義一個(gè) Advice 對(duì)象(實(shí)現(xiàn)了 InvocationHandler 接口),并將上面的 logMethodInvocation 和 目標(biāo)對(duì)象傳入
將上面的 Adivce 對(duì)象和目標(biāo)對(duì)象傳給 JDK 動(dòng)態(tài)代理方法,為目標(biāo)對(duì)象生成代理
上面步驟比較簡(jiǎn)單,不過(guò)在實(shí)現(xiàn)過(guò)程中,還是有一些難度的,這里要引入一些輔助接口才能實(shí)現(xiàn)。接下來(lái)就來(lái)介紹一下簡(jiǎn)單 AOP 的代碼結(jié)構(gòu):
MethodInvocation 接口 // 實(shí)現(xiàn)類包含了切面邏輯,如上面的 logMethodInvocation Advice 接口 // 繼承了 InvocationHandler 接口 BeforeAdvice 類 // 實(shí)現(xiàn)了 Advice 接口,是一個(gè)前置通知 SimpleAOP 類 // 生成代理類 SimpleAOPTest // SimpleAOP 從測(cè)試類 HelloService 接口 // 目標(biāo)對(duì)象接口 HelloServiceImpl // 目標(biāo)對(duì)象
MethodInvocation 接口代碼:
public interface MethodInvocation { void invoke(); }
Advice 接口代碼:
public interface Advice extends InvocationHandler {}
BeforeAdvice 實(shí)現(xiàn)代碼:
public class BeforeAdvice implements Advice { private Object bean; private MethodInvocation methodInvocation; public BeforeAdvice(Object bean, MethodInvocation methodInvocation) { this.bean = bean; this.methodInvocation = methodInvocation; } @Override public 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)代碼:
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)類代碼:
public interface HelloService { void sayHelloWorld(); } public class HelloServiceImpl implements HelloService { @Override public void sayHelloWorld() { System.out.println("hello world!"); } }
SimpleAOPTest 代碼:
public class SimpleAOPTest { @Test public 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è) Advice Advice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask); // 3. 為目標(biāo)對(duì)象生成代理 HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl,beforeAdvice); helloServiceImplProxy.sayHelloWorld(); } }
輸出結(jié)果:
以上實(shí)現(xiàn)了簡(jiǎn)單的 IOC 和 AOP,不過(guò)實(shí)現(xiàn)的 IOC 和 AOP 還很簡(jiǎn)單,且只能獨(dú)立運(yùn)行。在下一篇文章中,我將實(shí)現(xiàn)一個(gè)較為復(fù)雜的 IOC 和 AOP,大家如果有興趣可以去看看。好了,本篇文章到此結(jié)束。
本文在知識(shí)共享許可協(xié)議 4.0 下發(fā)布,轉(zhuǎn)載請(qǐng)注明出處
作者:coolblog
為了獲得更好的分類閱讀體驗(yàn),
請(qǐng)移步至本人的個(gè)人博客:http://www.coolblog.xyz
本作品采用知識(shí)共享署名-非商業(yè)性使用-禁止演繹 4.0 國(guó)際許可協(xié)議進(jìn)行許可。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/70200.html
摘要:在上文中,我實(shí)現(xiàn)了一個(gè)很簡(jiǎn)單的和容器。比如,我們所熟悉的就是在這里將切面邏輯織入相關(guān)中的。初始化的工作算是結(jié)束了,此時(shí)處于就緒狀態(tài),等待外部程序的調(diào)用。其中動(dòng)態(tài)代理只能代理實(shí)現(xiàn)了接口的對(duì)象,而動(dòng)態(tài)代理則無(wú)此限制。 1. 背景 本文承接上文,來(lái)繼續(xù)說(shuō)說(shuō) IOC 和 AOP 的仿寫(xiě)。在上文中,我實(shí)現(xiàn)了一個(gè)很簡(jiǎn)單的 IOC 和 AOP 容器。上文實(shí)現(xiàn)的 IOC 和 AOP 功能很單一,且 I...
摘要:本文是容器源碼分析系列文章的第一篇文章,將會(huì)著重介紹的一些使用方法和特性,為后續(xù)的源碼分析文章做鋪墊。我們可以通過(guò)這兩個(gè)別名獲取到這個(gè)實(shí)例,比如下面的測(cè)試代碼測(cè)試結(jié)果如下本小節(jié),我們來(lái)了解一下這個(gè)特性。 1. 簡(jiǎn)介 Spring 是一個(gè)輕量級(jí)的企業(yè)級(jí)應(yīng)用開(kāi)發(fā)框架,于 2004 年由 Rod Johnson 發(fā)布了 1.0 版本。經(jīng)過(guò)十幾年的迭代,現(xiàn)在的 Spring 框架已經(jīng)非常成熟了...
摘要:框架最初是由編寫(xiě)的,并且年月首次在許可下發(fā)布。在一個(gè)方法執(zhí)行之后,只有在方法退出拋出異常時(shí),才能執(zhí)行通知在建議方法調(diào)用之前和之后,執(zhí)行通知。方法執(zhí)行之后,不考慮其結(jié)果,執(zhí)行通知。 導(dǎo)讀: 在上篇文章的結(jié)尾提到了Spring Boot 提供了一系列的框架整合(Starter POMs)幫助我們提升開(kāi)發(fā)效率,但是這并不意味著我們不需要學(xué)習(xí)這些框架,反而更需要去學(xué)習(xí),通過(guò)學(xué)習(xí)這些框架可以使...
摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開(kāi)發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計(jì)模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過(guò)的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛(ài)情萌芽的模樣…… Java 進(jìn)階面試問(wèn)題列表 -...
閱讀 1841·2021-10-13 09:39
閱讀 3250·2021-10-12 10:11
閱讀 659·2021-09-28 09:36
閱讀 2776·2019-08-30 15:55
閱讀 1484·2019-08-30 13:04
閱讀 710·2019-08-29 17:08
閱讀 1995·2019-08-29 14:14
閱讀 3495·2019-08-28 18:23