AOP(Aspect Oriented Programming)稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日志,事務,權限等。其實說起aop大家都對其有一定的概念。今天主要是總結一下他代碼中的實現方式,以方便后邊查看使用。
說起AOP,首先我們需要先了解其中的幾個概念:
1.通知(Advice)
AOP在特定的切入點上執行的增強處理,就是你想要的功能,也就是上面說的日志、事務、權限等。有before(前置),after(后置),afterReturning(最終),afterThrowing(異常),around(環繞),就是在上面這幾中類型所實現的功能。比如在使用redis操作前打印日志。
2.連接點(JoinPoint)
就是spring允許你使用通知的地方,例如每個方法的前,后(兩者都有也行),或拋出異常時,也就是說和方法有關的前后(拋出異常),都是連接點。Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器,如aspectJ。
3.切入點(Pointcut)
就是帶有通知的連接點,在連接點的基礎上來定義切入點,假如一個類里面有5個方法,那就有5個連接點,但是並不想在所有方法上都實現切面的功能,只是想讓其中的幾個方法在調用之前,之后或者拋出異常時干點什么,那么就用切點來定義這幾個方法,讓切點來篩選連接點,選中那幾個你想要的方法。
在程序中主要體現為書寫切入點表達式。
4.切面(Aspect)
通常是一個類,里面可以定義切入點和通知。通知說明了干什么和什么時候干(之前,之后,還是異常),而切入點說明了在哪干(指定到底是哪個方法),這就是一個完整的切面定義。
5.引入(introduction)
在不修改代碼的前提下,引入可以在運行期為類動態地添加一些方法或字段。就是把我們定義的功能用到目標類中。
6.目標(target)
包含連接點的對象。也被稱作被通知或被代理對象。就是上面引入中所提到的目標類。
7.代理(proxy)
AOP框架創建的對象,代理就是目標對象的加強版本。Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基於接口,后者基於子類。
8.織入(weaving)
把切面應用到目標對象來創建新的代理對象的過程。有3種方式,spring采用的是運行時。
下面我們用注解的方式實現下AOP的功能。
增加AOP所需要的依賴包
<!--AOP依賴包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
定義一個切面類。
package com.example.demo.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component //讓spring能夠掃描到 @Aspect //定義這是一個切面類 public class LogAspect { /** * modifier-pattern:用於匹配public、private等訪問修飾符 * ret-type-pattern:用於匹配返回值類型,不可省略 * declaring-type-pattern:用於匹配包類型 * modifier-pattern(param-pattern):用於匹配類中的方法,不可省略 * throws-pattern:用於匹配拋出異常的方法 * * * 多個表達式之間使用連接符匹配多個條件, 如使用||表示“或”,使用 &&表示“且” */ @Pointcut("@annotation(com.example.demo.annotation.LogAop) &&" + "execution(public * com.example.demo.service.impl.AopTestServiceImpl.get*(..))") public void log(){ } /** * 匹配com.example.demo.service.impl包下所有類下的方法名以update結尾、參數類型不限的public方法 */ @Pointcut("execution(public * com.example.demo.service.impl.*.*update(..))") public void say(){ } @Order(2) @Before("log()") public void beforeLog2(){ System.out.println("后執行,增加log()方法..."); } @Order(1) // Order 代表優先級,數字越小優先級越高 @Before("log()||say()") //多個的話用 @Before("log()||say()") public void beforeLog1(){ System.out.println("先執行,增加log()方法..."); } /** * Advice注解一共有五種,分別是: * 1.@Before前置通知 * 前置通知在切入點運行前執行,不會影響切入點的邏輯 * 2.@After后置通知 * 后置通知在切入點正常運行結束后執行,如果切入點拋出異常,則在拋出異常前執行 * 3.@AfterThrowing異常通知 * 異常通知在切入點拋出異常前執行,如果切入點正常運行(未拋出異常),則不執行 * 4.@AfterReturning返回通知 * 返回通知在切入點正常運行結束后執行,如果切入點拋出異常,則不執行 * 5.@Around環繞通知 * 環繞通知是功能最強大的通知,可以在切入點執行前后自定義一些操作。環繞通知需要負責決定是繼續處理join point(調用ProceedingJoinPoint的proceed方法)還是中斷執行 */ @Before("say()") public void beforeSay(){ System.out.println("增加say()方法..."); } }
此類就是一個切面,其中一共定義了兩個切入點,一個是log(),一個是say()。在滿足切入點要求的方法前會做三中類型的通知,也就是beforeLog21(),beforeLog2(),beforeSay()。為什么是方法前呢,因為用的注解是@Before。其他具體的信息,可以看代碼里面的注釋。里面用到了一個我們自定義的注解,其代碼如下:
package com.example.demo.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.CLASS) public @interface LogAop { /** * 使用@annotation、@within、@target、@args匹配注解 * //匹配標注有LogAop注解的方法 * @Pointcut("@annotation(com.example.demo.annotation.LogAop)") * public void matchAnno() {} * * //匹配標注有LogAop的類底下的方法,要求annotation的Retention級別為CLASS * @Pointcut("@within(com.example.demo.annotation.LogAop)") * public void matchWithin() {} * * //匹配標注有LogAop的類底下的方法,要求annotation的Retention級別為RUNTIME * @Pointcut("@target(com.example.demo.annotation.LogAop)") * public void matchTarget() {} * * //匹配傳入的參數類標注有LogAop注解的方法 * @Pointcut("@args(com.example.demo.annotation.LogAop)") * public void matchArgs() {} */ }
然后就是我們的業務處理代碼,也就是目標對象。
package com.example.demo.service.impl; import com.example.demo.annotation.LogAop; import com.example.demo.model.Student; import com.example.demo.service.GetStudentService; import org.springframework.stereotype.Service; /** * @Description: AOP測試 * @Author: haoqiangwang3 * @CreateDate: 2020/1/13 */ @Service public class AopTestServiceImpl implements GetStudentService { @LogAop @Override public Student getStudentInfo() { System.out.println("調用業務處理中的get()方法..."); return null; } @Override public int update() { System.out.println("調用業務處理中的update()方法..."); return 0; } }
再就是程序的入口,controlle方法
package com.example.demo.controller; import com.example.demo.service.impl.AopTestServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Description: AOP測試controller * @Author: haoqiangwang3 * @CreateDate: 2020/1/13 */ @RestController public class AopController { @Autowired private AopTestServiceImpl aopTestServiceImpl; @RequestMapping("/boot/getAop") public String aopGet(){ aopTestServiceImpl.getStudentInfo(); return "success"; } @RequestMapping("/boot/updateAop") public String aopUpdate(){ aopTestServiceImpl.update(); return "success"; } }
上面的代碼是在我之前學習spring boot程序的基礎上增加的。以上完成后,就可以測試我們的aop功能了,測試結果就不粘貼了,親測是可以生效的。