Spring面向切面(AOP)
阿新 • • 發佈:2019-04-11
nat 結果 局部變量 形參 ase ram 枚舉 處理程序 tor
- AOP稱為面向切面編程,在程序開發中主要用來解決一些系統層面上的問題,比如日誌、事務、權限等,Struts2的攔截器設計就是基於AOP的思想。
- AOP的基本概念
- Aspect(切面):通常是一個類,裏面可以定義切入點和通知
- JointPoint(連接點):程序執行過程中明確的點,一般是方法的調用。
- Advice(通知):AOP在特定的切入點上執行的增強處理,有before、after、afterReturning、afterThrowing、around
- Pointcut(切入點):AOP框架創建的對象,代理就是目標對象的加強。Spring中的AOP代理可以是JDK動態代理,也可以是CGLIB代理,前者基於接口,後者基於子類。
- Spring AOP
- Spring中的AOP代理離不開Spring的IOC容器,代理的生成,管理及其依賴關系都是有IOC容器負責,Spring默認使用JDK動態代理,在需要代理類而不是代理接口的時候,Spring會自動切換為使用CGLID代理。
- 基於註解的AOP配置方式
- 啟用@Asject支持
- 在applicationContext.xml中配置
<aop:aspectj-autoproxy />
- 在applicationContext.xml中配置
- 通知類型介紹
- Before:在目標方法被調用之前做增強處理,@Before只需要指定切入點表達式即可。
- AfterReturning:在目標方法正常完成後做增強,@AfterReturning除了指定切入點表達式後,還可以指定一個返回值形參名returning,代表目標方法的返回值。
- AfterThrowing:主要用來處理程序中未處理的異常,@AfterThrowing除了指定切入點表達式後,還可以指定一個throwing的返回值形參名,可以用過改形參名來訪問目標方法中所拋出的異常對象。
- After:在目標方法完成之後做增強,無論目標方法什麽時候成功完成。@After可以指定一個切入點表達式。
- Around:環繞通知,在目標方法完成前後做增強處理,環繞通知最重要的通知類型,像事務、日誌等都是環繞通知。
- 啟用@Asject支持
- 測試實例
- Operator.java --> 切面類
@Component @Aspect public class Operator { @Pointcut(
- UserService.java --> 定義一些目標方法
@Service public class UserService { public void add(){ System.out.println("UserService add()"); } public String delete(){ System.out.println("UserService delete()"); return "delete函數返回值"; } public void edit(){ System.out.println("UserService edit()"); int i = 5/0; } }
- applicationContext.xml
<context:component-scan base-package="com.lynn.learning.spring"/> <aop:aspectj-autoproxy />
- UserServiceTest.java
public class UserServiceTest { @Test public void add() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add(); } @Test public void delete() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.delete(); } @Test public void edit() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.edit(); } }
- 註意:做環繞通知的時候,調用ProceedingJoinPoint的proceed()方法才會執行目標方法,同時需要返回值,否則在AfterReturning中無法獲取返回值。
- Operator.java --> 切面類
- 通知執行的優先級
- 進入目標方法時,先織入Around,再織入Before
- 退出目標方法時,織入Around,再織入AfterReturning,最後才織入After
- 切入點的定義和表達式
- 切入點表達式的定義算是整個AOP中的核心,有一套自己的規範
- Spring AOP支持的切入點指示符:
- @Pointcut("execution(* com.lynn.learning.spring.service..*.*(..))")
- 第一個*表示匹配任意的方法返回值,..(兩個點)表示零個或多個,上面的第一個..表示service包及其子包,第二個*表示所有類,第三個*表示所有方法,第二個..表示方法的任意參數個數
- execution:用來匹配執行方法的連接點
- @Pointcut("within(com.lynn.learning.spring.service*)")
- within限定匹配方法的連接點,上面的就是表示匹配service包下的任意連接點
- @Pointcut("this(com.lynn.learning.spring.service.UserService)")
- this用來限定AOP代理必須是指定類型的實例,如上,指定了一個特定的實例,就是UserService
- @Pointcut("bean(userService)")
- bean也是非常常用的,bean可以指定IOC容器中的bean的名稱。
- @Pointcut("execution(* com.lynn.learning.spring.service..*.*(..))")
- Spring實現自定義註解
- 創建自定義註解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface MyLog { String requestUrl(); }
- 解析註解,通過AOP實現
@Component @Aspect public class MyLogAspect { @Pointcut(value = "@annotation(com.lynn.learning.spring.annotation.MyLog)") public void pointcut() {} @Around(value = "pointcut() && @annotation(myLog)") public Object around(ProceedingJoinPoint point, MyLog myLog) { System.out.println("+++++執行了around方法+++++"); String requestUrl = myLog.requestUrl(); Class clazz = point.getTarget().getClass(); Method method = ((MethodSignature) point.getSignature()).getMethod(); System.out.println("執行了類:" + clazz + ", 方法:" + method + " 自定義請求地址:" + requestUrl); try { return point.proceed(); } catch (Throwable throwable) { return throwable.getMessage(); } } @AfterReturning(value = "pointcut() && @annotation(myLog)", returning = "result") public Object afterReturning(JoinPoint joinPoint, MyLog myLog, Object result) { System.out.println("+++++執行了afterReturning方法+++++"); System.out.println("執行結果:" + result); return result; } @AfterThrowing(value = "pointcut() && @annotation(myLog)", throwing = "ex") public void afterThrowing(JoinPoint joinPoint, MyLog myLog, Exception ex) { System.out.println("+++++執行了afterThrowing方法+++++"); System.out.println("請求:" + myLog.requestUrl() + " 出現異常"); } }
- 使用自定義註解
@Service public class UserService { @MyLog(requestUrl = "add") public void add(){ System.out.println("UserService add()"); } public String delete(){ System.out.println("UserService delete()"); return "delete函數返回值"; } public void edit(){ System.out.println("UserService edit()"); int i = 5/0; } }
- 測試
public class UserServiceTest { @Test public void add() { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.add(); } }
- 打印結果
+++++執行了around方法+++++ 執行了類:class com.lynn.learning.spring.service.UserService, 方法:public void com.lynn.learning.spring.service.UserService.add() 自定義請求地址:add AOP Around before... AOP Before Advice... UserService add() AOP Around after... AOP After Advice... AOP AfterReturning Advice:null +++++執行了afterReturning方法+++++ 執行結果:null
- @Retention:元註解,有一個屬性value,是RetentionPolicy類型的,Enum RetentionPolicy是一個枚舉類型。
- CLASS:表示註解的信息被保留在class文件(字節碼文件)。當程序編譯時,不會被虛擬機保存在運行時。默認行為。
- SOURCE:表示註解的信息會被編輯器拋棄,不會保留在class文件中,註解的信息只會保存在源文件中
- RUNTIME:表示註解的信息會被保留在class文件(字節碼文件)中,當程序編譯時,會被虛擬機保存在運行時。所以他們可以使用反射的方式讀取。Retention.RUNTIME可以從JVM中讀取Annotation註解的信息,以便在分析程序的時候使用。
- @Target:用來修飾註解的元註解。屬性ElementType也是一個枚舉類型
- TYPE:用於描述類、接口(包括註解類型)或enum
- FIELD:用於描述域
- METHOD:用於描述方法
- PARAMETER:用於描述參數(參數名)
- CONSTRUCTOR:用於描述構造函數
- LOCAL_VARIABLE:用於描述局部變量
- ANNOTATION_TYPE:用於描述註解
- PACKAGE:用於描述包
- TYPE_PARAMETER:用於描述參數類型
- TYPE_USE:任何位置都可以
- @Documented:表明這個註解應該被javadoc工具記錄。默認情況下javadoc是不包括註解的,但如果聲明註解是指定了@Documented,則他會被javadoc之類的工具處理,所以註解類型信息也會被包括在生成的文檔中。
- @Inherited:指明被註解的類會自動繼承. 更具體地說,如果定義註解時使用了 @Inherited 標記,然後用定義的註解來標註另一個父類, 父類又有一個子類(subclass),則父類的所有屬性將被繼承到它的子類中.
- 創建自定義註解
Spring面向切面(AOP)