【修真院JAVA小課堂】Spring AOP
大家好,我是IT修真院深圳分院第十二期學員,一枚正直純潔善良的JAVA程式設計師。
今天給大家分享一下,修真院官網JAVA任務三的一個知識點:spring AOP
課題:spring AOP
1.背景介紹
2.知識剖析
3.常見問題
4.解決方案
5.編碼實戰
6.擴充套件思考
7.參考文獻
8.更多討論
1.背景介紹:
什麼是aop:
通過前面的學習,我們知道 Spring 的兩大核心是 IOC 和 AOP。這裡我們說得 AOP (全稱是Aspect Oriented Programming),翻譯成中文是面向切面程式設計。它的主要思想是在程式正常執行的某一個點切進去加入特定的邏輯。Spring AOP 在程式中,可以對業務邏輯的各個部分進行隔離,提高隔離的可重用性,從而降低耦合度。
2.知識剖析
2.1.Spring 中的AOP。
個人對AOP思想的理解:
AOP 的核心思想是在動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想。。橫向的抽取機制,規避了傳統面向物件中縱向繼承體系的重複性高耦合程式碼。
其中,我們把分佈於應用中多處的功能稱為橫切關注點,通常這些橫切關注點在概念上是與應用的業務邏輯相分離的,但其程式碼往往直接嵌入在應用的業務邏輯之中。將這些橫切關注點與業務邏輯相分離正是面向切面程式設計(AOP)所要解決的。
AOP具體可以在下面的場景中使用:
許可權、快取、內容傳遞、錯誤處理、懶載入、除錯、日誌管理、效能優化、持久化、資源池、同步、事務
2.2.AOP的核心概念
1、橫切關注點
對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之為橫切關注點
2、切面(aspect)
切面是通知和切入點的結合。
通知說明了擴充套件的功能是什麼和什麼時候擴充套件,而切入點說明了在哪裡進擴充套件(指定到底是哪個方法),這就是一個完整的切面定義。可以看出切面就是表述的一個過程,就是將增強應用到具體方法上的過程。
3、連線點(joinpoint)
能夠應用通知的點 ,是在應用執行過程中能夠插入切面的一個點。在spring中,這些點指的是方法,spring只支援方法級別的連線點,一個類裡有多少方法就有多少Jointpoint。
4、切入點(pointcut)
指的是我們要對哪個連線點進行攔截的定義。
5、通知(advice)
也稱為增強。指的是攔截到Joinpoint後你想要擴充套件的功能,也就是上面說的 安全,事物,日誌等。
有五種型別的通知:
有五種型別的通知:
Before(前置):在方法被呼叫之前呼叫通知;
After(後置):在方法呼叫之後呼叫通知;(不管該方法是否執行成功)
After-returning(返回結果通知):在方法返回結果後;
After-throwing(異常通知):在方法丟擲異常後;
Around(環繞):在方法呼叫之前和之後都會呼叫通知;
6、目標(target)
代理的目標物件(要增強的類)
7、織入(weave)
是把增強應用到目標的過程。
8、引入(introduction)
引入允許我們向現有的類新增新方法或屬性,從而可以在無需修改現有類的情況下,讓它們具有新的行為和狀態。
3.常見問題
3.1.AOP 切割的兩種方式
3.2.AOP 切入點的定位是如何完成的?
3.3Spring AOP無法攔截Controller中的方法
4.解決方案
4.1.AOP 切割的兩種方式:
基於 xml 和基於註解的配置方式
4.2.AOP 切入點的定位是如何完成的?
使用表示式來配置切入點,表示式有很多種,但是絕大多數問題都可以用execution表示式來解決。
1.execution(* com.ptteng.controller.controller.listCategory(..))
2.execution(* ccom.ptteng.controller.controller.*(..))
3.execution(* *.*(..))
4.匹配所有save開頭的方法:execution(* save*(..))
4.3.Spring AOP無法攔截Controller中的方法
因為Spring的Bean掃描和Spring-MVC的Bean掃描是分開的, 兩者的Bean位於兩個不同的Application, 而且Spring-MVC的Bean掃描要早於Spring的Bean掃描, 所以當Controller Bean生成完成後, 再執行Spring的Bean掃描,Spring會發現要被AOP代理的Controller Bean已經在容器中存在, 配置AOP就無效了.
同樣這樣的情況也存在於資料庫事務中, 如果Service的Bean掃描配置在spring-mvc.xml中, 而資料庫事務管理器配置在application.xml中, 會導致資料庫事務失效, 原理一樣.
所以這裡 ,我們需要把AOP放置在Controller掃描配置的檔案中.
Spring的配置檔案application.xml包含了 開啟AOP自動代理,Service掃描配置, 現在只包含了service的自動掃描配置。
5.編碼實戰
以下是基於XML 的配置:
aop.xml
<aop:config>
<aop:aspect id ="time" ref="timeHandler">
<aop:pointcut id="addAllMethod" expression="execution(* dao.HelloWorld.*(..))" />
<aop:before method="printTime" pointcut-ref="addAllMethod"/>
<aop:after method="printTime" pointcut-ref="addAllMethod"/>
</aop:aspect>
<aop:aspect id ="printLog" ref="logHandler" order="2">
<aop:pointcut id="printLog" expression="execution(* dao.HelloWorld.*(..))"/>
<aop:before method="LogBefore" pointcut-ref="printLog" />
<aop:after method="LogAfter" pointcut-ref="printLog" />
</aop:aspect>
</aop:config>
HelloWorld.class
public interface HelloWorld {
void printHelloWorld();
void doPrint();
}
HelloWorldImpl1.class
public class HelloWorldImpl1 implements HelloWorld {
@Override
public void printHelloWorld() {
System.out.println("Enter HelloWorldImpl1.printHelloWorld()");
}
@Override
public void doPrint() {
System.out.println("Enter HelloWorldImpl1.doPrint");
// return ;
}
}
HelloWorldImpl2.class
public class HelloWorldImpl2 implements HelloWorld {
@Override
public void printHelloWorld() {
System.out.println("Enter HelloWorldImpl2.printHelloWorld()");
}
@Override
public void doPrint() {
System.out.println("Enter HelloWorldImpl2.doPrint()");
}
}
public class LogHandler {
public void LogBefore(){
System.out.println("Log before method");
}
public void LogAfter(){
System.out.println("Log aafter method ");
}
}
public class TimeHandler {
public void printTime()
{
System.out.println("CurrentTime =" + System.currentTimeMillis());
}
}
public class test01 {
public static void main(String[] args) {
ApplicationContext applicationContext
= new ClassPathXmlApplicationContext("aop.xml");
HelloWorld hw1 = (HelloWorld)applicationContext.getBean("helloWorldImpl1");
HelloWorld hw2 = (HelloWorld)applicationContext.getBean("helloWorldImpl2");
hw1.printHelloWorld();
System.out.println();
hw1.doPrint();
System.out.println();
hw2.printHelloWorld();
System.out.println();
hw2.doPrint();
}
}
以下是基於註解的配置:
@Aspect
@Component
public class TimeInterceptor {
private static Log logger = LogFactory.getLog(TimeInterceptor.class);
@Around("execution(* controller.*.*(..))")
public Object aroundController(ProceedingJoinPoint proceedingJoinPoint){
TimeInterceptorUtil timeInterceptorUtil1 = new TimeInterceptorUtil();
Object obj = timeInterceptorUtil1.timeAround(proceedingJoinPoint);
return obj;
}
@Around("execution(* mapper.*.*(..))")
public Object aroundDao(ProceedingJoinPoint proceedingJoinPoint){
TimeInterceptorUtil timeInterceptorUtil2 = new TimeInterceptorUtil();
Object obj = timeInterceptorUtil2.timeAround(proceedingJoinPoint);
return obj;
}
}
public class TimeInterceptorUtil {
private static Log logger = LogFactory.getLog(TimeInterceptorUtil.class);
/**
* 統計方法執行耗時Around環繞通知
*
* @return
* @param.joinPoint
*/
// @Around(POINT)
public Object timeAround(ProceedingJoinPoint joinPoint) {
// 定義返回物件、得到方法需要的引數
Object obj = null;
//獲取傳入目標方法的引數物件
Object[] args = joinPoint.getArgs();
//打印出開始時間
long startTime = System.currentTimeMillis();
try {
//用引數物件執行目標方法
obj = joinPoint.proceed(args);
} catch (Throwable e) {
//異常通知
logger.error("統計某方法執行耗時環繞通知出錯", e);
}
// 獲取執行的方法名
long endTime = System.currentTimeMillis();
//獲取封裝了署名資訊的物件,在該物件中可以獲取到目標方法名,所屬類的Class等資訊
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//獲取執行的方法名並且打印出來
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
// 列印耗時的資訊
this.printExecTime(methodName, startTime, endTime);
return obj;
}
/**
* 列印方法執行耗時的資訊,如果超過了一定的時間,才打印
*
* @param methodName
* @param startTime
* @param endTime
*/
private void printExecTime(String methodName, long startTime, long endTime) {
long diffTime = endTime - startTime;
logger.warn("-----" + methodName + " 方法執行耗時: " + diffTime + " ms");
System.out.println("-----" + methodName + " 方法執行耗時: " + diffTime + " ms");
}
}
開啟自動註解裝配:
applicationContext.xml
//自動裝配註解
<aop:aspectj-autoproxy/>
<context:annotation-config/>
<context:component-scan base-package="controller"/>
<context:component-scan base-package="service"/>
<!--<context:component-scan base-package="pojo"/>-->
<context:component-scan base-package="aop"/>
spring-MVC.xml
<context:annotation-config/>
<mvc:annotation-driven/>
<context:component-scan base-package="controller"/>
<mvc:default-servlet-handler/>
<aop:aspectj-autoproxy/>
6.擴充套件思考
基於註解與基於配置檔案的優缺點:
1、註解可以充分利用 Java 的反射機制獲取類結構資訊,這些資訊可以有效減少配置的工作,註釋和 Java 程式碼位於一個檔案中,而XML 配置採用獨立的配置檔案,大多數配置資訊在程式開發完成後都不會調整,如果配置資訊和 Java 程式碼放在一起,有助於增強程式的內聚性。而採用獨立的 XML 配置檔案,程式設計師在編寫一個功能時,往往需要在程式檔案和配置檔案中不停切換,這種思維上的不連貫會降低開發效率。
2、個人認為如果開發的程式比較複雜的話還是多用xml配置,這樣以後方便維護,反之則使用註解,
7.參考文獻
https://blog.csdn.net/loongshawn/article/details/72303040
https://my.oschina.net/GinkGo/blog/1577574
https://blog.csdn.net/yujin753/article/details/46688105
8.更多討論
1.spring aop 攔截controller怎麼傳遞request
在到達controller之前攔截,有兩種方法,一種AOP和一種Filter。都可以在到達Controller之前攔截。
2.Spring AOP 實現方式
Spring有兩種實現AOP的方式:Java動態代理 和 Cglib. 預設使用動態代理,當目標物件沒有實現介面時,
就會使用後者. 我們這裡使用Java的動態代理實現一個簡單的AOP, 可以更深刻的理解Spring AOP
3.怎麼開啟JDK代理或CGLib代理?
在Spring配置檔案的切面驅動中加入proxy-target-class="true"欄位,"true"表示開啟CGLib代理,
"false"表示開啟JDK代理,若不加入這一欄位,則預設啟用JDK代理
今天的分享就到這裡啦,歡迎大家點贊、轉發、留言、拍磚~
技能樹.IT修真院
“我們相信人人都可以成為一個工程師,現在開始,找個師兄,帶你入門,掌控自己學習的節奏,學習的路上不再迷茫”。
這裡是技能樹.IT修真院,成千上萬的師兄在這裡找到了自己的學習路線,學習透明化,成長可見化,師兄1對1免費指導。
快來與我一起學習吧~http://www.jnshu.com/login/1/12744596