最近看到aop,就做一點小小的總結。
一、AOP的基本概念:
1、什么是aop:
- AOP(Aspect Oriented Programming)稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日志,事務,權限等待,Struts2的攔截器設計就是基于AOP的思想,是個比較經典的例子。
- 在不改變原有的邏輯的基礎上,增加一些額外的功能。代理也是這個功能,讀寫分離也能用aop來做。
- AOP可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來建立一種對象層次結構,用于模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關系,但并不適合定義橫向的關系,例如日志功能。日志代碼往往橫向地散布在所有對象層次中,而與它對應的對象的核心功能毫無關系對于其他類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散布在各處的無關的代碼被稱為橫切(cross cutting),在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用。
- AOP技術恰恰相反,它利用一種稱為"橫切"的技術,剖解開封裝的對象內部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便于減少系統的重復代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護性。
- 使用"橫切"技術,AOP把軟件系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如權限認證、日志、事物。AOP的作用在于分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。
2、AOP的相關概念:
(1)橫切關注點:對哪些方法進行攔截,攔截后怎么處理,這些關注點稱之為橫切關注點
(2)Aspect(切面):通常是一個類,里面可以定義切入點和通知
(3)JointPoint(連接點):程序執行過程中明確的點,一般是方法的調用。被攔截到的點,因為Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器
(4)Advice(通知):AOP在特定的切入點上執行的增強處理,有before(前置),after(后置),afterReturning(最終),afterThrowing(異常),around(環繞)
(5)Pointcut(切入點):就是帶有通知的連接點,在程序中主要體現為書寫切入點表達式
(6)weave(織入):將切面應用到目標對象并導致代理對象創建的過程
(7)introduction(引入):在不修改代碼的前提下,引入可以在運行期為類動態地添加一些方法或字段
(8)AOP代理(AOP Proxy):AOP框架創建的對象,代理就是目標對象的加強。Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基于接口,后者基于子類
(9)目標對象(Target Object): 包含連接點的對象。也被稱作被通知或被代理對象。POJO
3、Advice通知類型介紹:
(1)Before:在目標方法被調用之前做增強處理,@Before只需要指定切入點表達式即可
(2)AfterReturning:在目標方法正常完成后做增強,@AfterReturning除了指定切入點表達式后,還可以指定一個返回值形參名returning,代表目標方法的返回值
(3)AfterThrowing:主要用來處理程序中未處理的異常,@AfterThrowing除了指定切入點表達式后,還可以指定一個throwing的返回值形參名,可以通過該形參名
來訪問目標方法中所拋出的異常對象
(4)After:在目標方法完成之后做增強,無論目標方法時候成功完成。@After可以指定一個切入點表達式
(5)Around:環繞通知,在目標方法完成前后做增強處理,環繞通知是最重要的通知類型,像事務,日志等都是環繞通知,注意編程中核心是一個ProceedingJoinPoint
4、AOP使用場景:
Authentication 權限
Caching 緩存
Context passing 內容傳遞
Error handling 錯誤處理
Lazy loading 懶加載
Debugging 調試
logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準
Performance optimization 性能優化
Persistence 持久化
Resource pooling 資源池
Synchronization 同步
Transactions 事務
好,概念方面講清楚了之后,下面說說實現方案:
二、關于jar包依賴:
首先,使用aop依賴包除了Spring提供給開發者的jar包外,還需額外上網下載兩個jar包:
1、aopalliance.jar
2、aspectjweaver.jar
我用的是maven管理jar,具體如下:
pom.xml:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--spring aop + aspectj-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
三、使用AOP的幾種方式:
1.經典的基于代理的AOP
2.@AspectJ注解驅動的切面
3.純POJO切面(純粹通過<aop:fonfig>標簽配置)
4.注入式AspectJ切面
四、具體使用方式:
先把項目結構貼一下,用Java Project+Maven與用Web Project 會有小區別,一會說到。包結構:
1、經典的基于代理的AOP實現,用的是一個helloworld為例:
(1)先定義一個接口,任何可以說“helloworld”的類都可以實現它。
public interface HelloWorld {
void printHelloWorld();
void doPrint();
}
(2)定義兩個接口實現類。
public class HelloWorldImpl1 implements HelloWorld {
public void printHelloWorld() {
System.out.println("------11111------按下HelloWorld1.printHelloWorld()-----11111111-------");
}
public void doPrint() {
System.out.println("------1111111------打印HelloWorldImpl1-----1111111------");
return ;
}
}
public class HelloWorldImpl2 implements HelloWorld {
public void printHelloWorld() {
System.out.println("------222222------按下HelloWorld2.printHelloWorld()------2222222------");
}
public void doPrint() {
System.out.println("-------22222-----打印HelloWorldImpl2------22222-----");
return ;
}
}
(3)HelloWorld的兩個實現類關注的是業務邏輯,但在此之外還需要其他的功能邏輯等,如打印時間、打印日志等等。這里開始就需要AOP替“HelloWorldImpl”完成!解耦!首先需要一個TimeHandler類。因為一個是切入點前執行、一個是切入點之后執行,所以實現對應接口。
橫切關注點,這里是打印時間:
public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("代理----前----CurrentTime = " + System.currentTimeMillis());
}
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("代理----后----CurrentTime = " + System.currentTimeMillis());
}
}
(3)最關鍵的來了,Spring核心配置文件application.xml配置AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<!-- 定義被代理者 -->
<bean id="h1" class="com.lym.aopTest.HelloWorldImpl1"></bean>
<bean id="h2" class="com.lym.aopTest.HelloWorldImpl2"></bean>
<!-- 定義通知內容,也就是切入點執行前后需要做的事情 -->
<bean id="timeHandler" class="com.lym.aopTest.TimeHandler"></bean>
<!-- 定義切入點位置,這里定義到了doPrint方法上 -->
<bean id="timePointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*doPrint"></property>
</bean>
<!-- 使切入點與通知相關聯,完成切面配置 -->
<bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="timeHandler"></property>
<property name="pointcut" ref="timePointcut"></property>
</bean>
<!-- 設置代理 -->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的對象,有打印時間能力 -->
<property name="target" ref="h1"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="timeHandlerAdvisor"></property>
<!-- 代理接口,hw接口 -->
<property name="proxyInterfaces" value="com.lym.aopTest.HelloWorld"></property>
</bean>
<!-- 設置代理 -->
<bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 代理的對象,有打印時間能力 -->
<property name="target" ref="h2"></property>
<!-- 使用切面 -->
<property name="interceptorNames" value="timeHandlerAdvisor"></property>
<!-- 代理接口,hw接口 -->
<property name="proxyInterfaces" value="com.lym.aopTest.HelloWorld"></property>
</bean>
<!--<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>-->
</beans>
(5)測試類,Test,其中,通過AOP代理的方式執行h1、h2,其中doPrint()方法會把執行前、執行后的操作執行,實現了AOP的效果!
public class Test {
public static void main(String[] args){
//@SuppressWarnings("resource")
//如果是web項目,則使用以下代碼加載配置文件,如果是一般的Java項目,則使用注釋的方式
ApplicationContext appCtx = new ClassPathXmlApplicationContext("conf/application.xml");
//ApplicationContext appCtx = new FileSystemXmlApplicationContext("conf/application.xml");
HelloWorld hw1 = (HelloWorld) appCtx.getBean("proxy");
HelloWorld hw2 = (HelloWorld) appCtx.getBean("proxy2");
hw1.printHelloWorld();
System.out.println();
hw1.doPrint();
System.out.println();
hw2.printHelloWorld();
System.out.println();
hw2.doPrint();
}
}
打印結果如下,可以看到,配置在h1、h2的doPrint()前后打印時間的方法都執行了: