@Around注解可以用來在調(diào)用一個具體方法前和調(diào)用后來完成一些具體的任務(wù)。
比如我們想在執(zhí)行controller中方法前打印出請求參數(shù),并在方法執(zhí)行結(jié)束后來打印出響應(yīng)值,這個時候,我們就可以借助于@Around注解來實現(xiàn);
再比如我們想在執(zhí)行方法時動態(tài)修改參數(shù)值等
類似功能的注解還有@Before等等,用到了Spring AOP切面思想,Spring AOP常用于攔截器、事務(wù)、日志、權(quán)限驗證等方面。
完整演示代碼如下:
需要說明的是,在以下例子中,我們即可以只用@Around注解,并設(shè)置條件,見方法run1();也可以用@Pointcut和@Around聯(lián)合注解,見方法pointCut2()和run2(),這2種用法是等價的。如果我們還想利用其進(jìn)行參數(shù)的修改,則調(diào)用時必須用joinPoint.proceed(Object[] args)方法,將修改后的參數(shù)進(jìn)行回傳。如果用joinPoint.proceed()方法,則修改后的參數(shù)并不會真正被使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.persistence.EntityManager; /** * 控制器切面 * * @author lichuang */ @Component @Aspect public class ControllerAspect { private static final Logger logger = LoggerFactory.getLogger(ControllerAspect. class ); @Autowired private EntityManager entityManager; /** * 調(diào)用controller包下的任意類的任意方法時均會調(diào)用此方法 */ @Around ( "execution(* com.company.controller.*.*(..))" ) public Object run1(ProceedingJoinPoint joinPoint) throws Throwable { //獲取方法參數(shù)值數(shù)組 Object[] args = joinPoint.getArgs(); //得到其方法簽名 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //獲取方法參數(shù)類型數(shù)組 Class[] paramTypeArray = methodSignature.getParameterTypes(); if (EntityManager. class .isAssignableFrom(paramTypeArray[paramTypeArray.length - 1 ])) { //如果方法的參數(shù)列表最后一個參數(shù)是entityManager類型,則給其賦值 args[args.length - 1 ] = entityManager; } logger.info( "請求參數(shù)為{}" ,args); //動態(tài)修改其參數(shù) //注意,如果調(diào)用joinPoint.proceed()方法,則修改的參數(shù)值不會生效,必須調(diào)用joinPoint.proceed(Object[] args) Object result = joinPoint.proceed(args); logger.info( "響應(yīng)結(jié)果為{}" ,result); //如果這里不返回result,則目標(biāo)對象實際返回值會被置為null return result; } @Pointcut ( "execution(* com.company.controller.*.*(..))" ) public void pointCut2() {} @Around ( "pointCut2()" ) public Object run2(ProceedingJoinPoint joinPoint) throws Throwable { //獲取方法參數(shù)值數(shù)組 Object[] args = joinPoint.getArgs(); //得到其方法簽名 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //獲取方法參數(shù)類型數(shù)組 Class[] paramTypeArray = methodSignature.getParameterTypes(); if (EntityManager. class .isAssignableFrom(paramTypeArray[paramTypeArray.length - 1 ])) { //如果方法的參數(shù)列表最后一個參數(shù)是entityManager類型,則給其賦值 args[args.length - 1 ] = entityManager; } logger.info( "請求參數(shù)為{}" ,args); //動態(tài)修改其參數(shù) //注意,如果調(diào)用joinPoint.proceed()方法,則修改的參數(shù)值不會生效,必須調(diào)用joinPoint.proceed(Object[] args) Object result = joinPoint.proceed(args); logger.info( "響應(yīng)結(jié)果為{}" ,result); //如果這里不返回result,則目標(biāo)對象實際返回值會被置為null return result; } } |
補充:Spring Aop實例(AOP 如此簡單)@Aspect、@Around 注解方式配置
IoC相關(guān)的基本內(nèi)容告一段落,本次介紹Spring的第二個特性,AOP,面向切面編程,術(shù)語聽起來比較不容易理解,沒關(guān)系,一切盡在實例中,讓我們看一個簡單的實例,就能明白。
實例
項目工程目錄結(jié)構(gòu)和代碼獲取地址
獲取地址(版本Log將會注明每一個版本對應(yīng)的課程)
https://github.com/laiyijie/SpringLearning
目錄結(jié)構(gòu)
運行工程
運行具有Main函數(shù)的 App.java
得到如下輸出
1
2
3
|
method start time: 1480223298250 userHello method end time: 1480223299250 |
項目詳解
從App.java入手
App.java
1
2
3
4
5
6
7
8
9
10
11
|
package me.laiyijie.demo; import org.springframework.context.support.ClassPathXmlApplicationContext; import me.laiyijie.demo.service.HelloInterface; public class App { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( "root-context.xml" ); HelloInterface userService = context.getBean(HelloInterface. class ); userService.sayHello(); context.close(); } } |
調(diào)用的是HelloInterface的sayHello方法
HelloInterface.java
1
2
3
4
5
6
|
package me.laiyijie.demo.service; public interface HelloInterface{ void sayHello(); } |
其實現(xiàn)類為UserServiceImpl.java
UserServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package me.laiyijie.demo.service; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements HelloInterface { public void sayHello() { try { Thread.sleep( 1000 ); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println( "userHello" ); } } |
誒?情況跟我們看到的代碼有出入?
sayHello 應(yīng)該只輸出 userHello,前后兩行輸出從何出現(xiàn)?
在Main函數(shù)中找不到一點兒線索!
這就是AOP的一個強大特性:
無侵入性,不改變原有的代碼,卻能增加功能!
那么究竟是如何增加功能的呢?
讓我們看看TimeMonitor.java
TimeMonitor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package me.laiyijie.demo.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Service; @Service @Aspect public class TimeMonitor { @Around ( "execution(* me.laiyijie.demo.service.UserServiceImpl.sayHello(..))" ) public void monitorAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println( "method start time:" + System.currentTimeMillis()); Object re = pjp.proceed(); System.out.println( "method end time:" + System.currentTimeMillis()); } } |
終于看到了 method start time:1480223298250 和 method end time:1480223299250這兩行輸出是從哪兒出現(xiàn)的了!
讓我們來仔細(xì)解讀一下這個類
類有兩個注釋,分別是@Service和@Aspect,第一個注解是使得TimeMonitor受Spring托管并實例化。@Aspect就是使得這個類具有AOP功能(你可以這樣理解)兩個注解缺一不可
類里面只有一個方法,名字叫做monitorAroud,其實就是為了檢測函數(shù)執(zhí)行時間的!
那么關(guān)鍵點來了,兩個輸出語句是怎么插入到sayHello方法的前后的呢!
看這個注解:
1
|
@Around ( "execution(* me.laiyijie.demo.service.UserServiceImpl.sayHello(..))" ) |
@Around表示包圍一個函數(shù),也就是可以在函數(shù)執(zhí)行前做一些事情,也可以在函數(shù)執(zhí)行后做一些事情
1
|
execution(* me.laiyijie.demo.service.UserServiceImpl.sayHello(..)) |
這個比較好理解,就是使用表達(dá)式的方式指定了要對哪個函數(shù)進(jìn)行包圍!(除了execution以外還有很多,可以搜索AspectJ語法來學(xué)習(xí))
也就是說,這個注解完整的說明了,應(yīng)該在函數(shù)的什么位置插入變化,也就是所謂的切點
之后是函數(shù)的定義:
1
|
public Object monitorAround(ProceedingJoinPoint pjp) |
這里引入了ProceedingJoinPoint,在使用了@Around之后可以帶入這個參數(shù),代表的其實就是sayHello這個函數(shù),不過做了一些封裝
而 Object re = pjp.proceed(); 就是相當(dāng)于執(zhí)行了 sayHello方法!
剩下的代碼就不用過多解釋了,就是在執(zhí)行這個函數(shù)的前后分別進(jìn)行了系統(tǒng)時間的獲取。
我們把這個函數(shù)體,也就是定義了要做那些事情的代碼,稱作增強
而包含切點和增強結(jié)合起來就稱作切面
面向切面由此而來!
Spring AOP 開啟需要的配置
需要配置兩項
1、pom.xml增加依賴(因為要用到AOP還需要不同的JAR包)
2、root-context.xml中增加切面相關(guān)配置
root-context.xml
1
2
3
4
5
6
7
8
9
10
11
12
|
<? 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:context = "http://www.springframework.org/schema/context" xmlns:aop = "http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> < aop:aspectj-autoproxy ></ aop:aspectj-autoproxy > < context:component-scan base-package = "me.laiyijie.demo" ></ context:component-scan > </ beans > |
root-context.xml 增加了兩行
1、xmlns:aop="http://www.springframework.org/schema/aop"
代表加入命名空間
2、<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
使用1中引入的aop命名空間開起自動代理(自動代理具體含義后續(xù)慢慢解釋,簡單的理解就是AOP的實現(xiàn)是依靠自動代理實現(xiàn)的)
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
< project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion >4.0.0</ modelVersion > < groupId >me.laiyijie</ groupId > < artifactId >demo</ artifactId > < version >0.0.1-SNAPSHOT</ version > < packaging >jar</ packaging > < dependencies > <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-context</ artifactId > < version >4.3.2.RELEASE</ version > </ dependency > <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> < dependency > < groupId >org.aspectj</ groupId > < artifactId >aspectjweaver</ artifactId > < version >1.8.9</ version > </ dependency > </ dependencies > </ project > |
增加了一個依賴
AspectJ 一個強大的AOP框架,也就是@Aspect和@Around以及ProceedingJoinPoint這些注解和方法的提供者
小結(jié)
增強:定義了應(yīng)該怎么把額外的動作加入到指定函數(shù)中
切點:定義了你應(yīng)該把增強插入到哪個函數(shù)的什么位置
切面:切點和增強組合起來的稱呼
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。如有錯誤或未考慮完全的地方,望不吝賜教。
原文鏈接:https://blog.csdn.net/lichuangcsdn/article/details/87741811