java開發AOP面向切面編程入門
引言
在實際應用場景中,我們封裝一個學生的類,這個類用於封裝學生的日常行為,如:上學、吃飯、上課等。然而,在疫情期間,學生上學時入校、吃飯時進入餐廳,需要測溫查驗證件等行為,拿到這樣的需求我們怎麼辦?
不好的解決方案
面向過程的解決方案
遇到問題解決問題,在上學、吃飯方法中加上測溫、查驗證件方法,或者在學生類中提煉一個測溫查驗證件私有的方法,在需要時調用。
代碼如下:
public class Student { public void toSchool(){ check(); System.out.println("高高興興去上學!"); } public void toEat(){ check(); System.out.println("一號餐廳去吃飯!"); } public void toClass(){ System.out.println("排排坐,聽講座!"); } public void getUp(){ System.out.println("起床啦!"); } private void check(){ System.out.println("查驗證件、測體溫"); } }
這種方式存在問題隻是頭痛醫頭、腳痛醫腳,代碼硬拷貝,現在教師進校門也要查驗證件測溫,如何辦?將check代碼復制到教師類中,再者,如果check()中業務規則發生變化,則需要到處改代碼,顯然是一種非常蹩腳的解決方案。
使用繼承解決方案
創建一個man的基類,將check()方法放到該類中,教師、學生均繼承此類,子類調用這個方法即可,
代碼如下:
public class Man { protected void check(){ System.out.println("查驗證件、測體溫"); } }
public class Student extends Man { public void toSchool(){ check(); System.out.println("高高興興去上學!"); } public void toEat(){ check(); System.out.println("一號餐廳去吃飯!"); } } public class Teacher extends Man { public void toSchool(){ check(); System.out.println("高高興興去上班!"); } public void toEat(){ check(); System.out.println("教工餐廳去吃飯!"); } public void toClass(){ System.out.println("排排坐,聽講座!"); } }
這種方式,雖然解決瞭代碼復制問題,但違背瞭面向對象程序設計的單一職責原則,因為查驗證件測溫等均不是學生或教師應該擁有的職責,類的劃分職責不清,增加瞭代碼擴展維護的難度。
使用聚合的解決方案
將查驗證件測溫等行為封裝一個獨立的類,學生和教師的依然隻封裝他們固有的方法,
代碼如下:
public class Checktor { public void takeTemperature(){ System.out.println("查驗證件、測體溫"); } } public class Student { private Checktor checktor = new Checktor(); public void toSchool(){ checktor.takeTemperature(); System.out.println("高高興興去上學!"); } public void toEat(){ checktor.takeTemperature(); System.out.println("一號餐廳去吃飯!"); } }
這種方法很好的解決瞭面向對象程序設計單一職責原則,體現瞭類的封裝性,但是代碼侵入性很強,而且代碼僵化,維護性差。如疫情結束瞭,我們要求取消查驗證件和測溫,就需要改原有的代碼,破壞瞭開閉原則,增加瞭程序員的工作量。有些人寫配置開關變量,在調用時使用ifelse進行判斷是否調用也能遵循開關變量,但是代碼中包含瞭一些可能用不到代碼,不優雅,不是很好解決方案。
面向切面的編程基本概念
面向切面的編程是不破壞原有類封裝性的前提下,動態在其方法前面(before)、後邊(after)及周圍(Around)(前面和後邊)增強功能。
- 連接點(Joinpoint):目標對象中每個方法前面、後面均為連接點。如學生類中toSchool、toEat、toClass、getUp方法前面後面均是切入點;
- 切入點(Point):需要增強功能方法前面或後面,是切入點,如進學校前、吃飯前需要測體溫,則toSchool、toEat兩個方法前是切入點,其餘的不是;
- 切面類(Aspect):也稱通知類,封裝瞭需要增強功能的類,如:Checktor;
- 通知方法(advice):需要增強的功能,如:測溫方法;
- 通知類型:前置通知(Before)、後置通知(After)、環繞通知(around)、異常通知(throwing)、最終通知(after (finally) advice);
- 代理(proxy):將通知應用到目標對象實現機制,是通過創建代理對象完成的
- 織入(Weaving):將切面代碼插入到目標對象上,生成代理對象的過程
基於Spring面向切面程序實現
針對學生上學場景,Spring的使用XML和註解兩設計種方式完成面向切面的編程。
工程依賴項:在maven pom文件中,需要引入以下依賴項
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> <scope>runtime</scope> </dependency>
XML方式:
學生類:
public class Student { public void toSchool(){ System.out.println("高高興興去上學!"); } public void toEat(){ System.out.println("一號餐廳去吃飯!"); } public void toClass(){ System.out.println("排排坐,聽講座!"); } public void getUp(){ System.out.println("起床啦!"); } }
切面類:
public class Checktor { public void takeTemperature(){ System.out.println("測體溫啦!"); } public void washHand(){ System.out.println("洗洗手更健康!"); } }
xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.bjwl.*"/> <!--目標(業務)對象--> <bean id="student" class="com.bjwl.xmltest.Student"/> <bean id="teacher" class="com.bjwl.xmltest.Teacher"/> <!--通知(切面)對象--> <bean id="checktor" class="com.bjwl.xmltest.Checktor"></bean> <aop:config> <!--定義(切面)對象--> <aop:aspect ref="checktor"> <!--定義切點--> <aop:pointcut id="point_test_d" expression="execution(* com.bjwl.xmltest.Student.to*(..))"/> <!--定義通知--> <aop:before method="takeTemperature" pointcut-ref="point_test_d"></aop:before> <aop:after method="washHand" pointcut-ref="point_test_d"></aop:after> </aop:aspect> </aop:config> </beans>
其中:目標對象是student對象,切點是student對象中方法名前兩個字母是to的所有方法,事前執行測溫(takeTemperature)、事後執行洗手(washHand)
測試代碼如下:
public class StudentTest { @Test public void toSchool() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "bean02.xml" ); Student student = (Student)applicationContext.getBean("student"); student.toSchool(); } }
運行結果如下:
如果教師需也需要測溫,編寫教師類,代碼如下:
public class Teacher { public void toSchool(){ System.out.println("高高興興去上班!"); } public void toEat(){ System.out.println("教工餐廳去吃飯!"); } public void toClass(){ System.out.println("排排坐,聽講座!"); } }
xml文件更改如下:
<aop:config> <!--定義(切面)對象--> <aop:aspect ref="checktor"> <!--定義切點--> <aop:pointcut id="point_test_d" expression="execution(* com.bjwl.xmltest.*.to*(..))"/> <!--定義通知--> <aop:before method="takeTemperature" pointcut-ref="point_test_d"></aop:before> <aop:after method="washHand" pointcut-ref="point_test_d"></aop:after> </aop:aspect> </aop:config>
則表示com.bjwl.xmltest中的所有類的對象均為目標對象,前兩個字母帶to的方法前後均為切點。
註解方式:
學生類代碼:
切面類的代碼
xml文件:
運行結果等同,XML文件。
這樣做的好處,學生類、教師類、切面類具有職責單一性,提現業務邏輯封裝性,動態增強符合開閉原則。
小結
以上簡單的介紹瞭面向切面編程的基本概念和基本用法。用好面向切面的編程技術很不易,因為切點隻能設置到需要增強功能前面或後面,對程序設計人員要求很高,要求能夠根據編程經驗和技巧設計出合理的切點,需要提現類的層次性。如:執行SQL語句前,在控制臺輸出完整的SQL語句的切點,切點就不能設置到你自己定義的Dao中,而是設置在PrepareStatement的exectuce的方法前。不過也不用擔心,使用各種框架時,常用的日志等,已經給我們設置瞭開關變量,我們直接使用即可,非常簡單。
以上就是java開發AOP面向切面編程入門的詳細內容,更多關於java面向切面編程的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 詳解Java SpringAOP切面類
- Spring為singleton bean註入prototype bean
- Spring中的AOP操作你瞭解嗎
- Java SpringAOP技術之註解方式詳解
- Spring IOC容器Bean管理XML註入集合類型屬性