本文內容:spring中如何使用注解實現面向切面編程,以及如何使用自定義注解。
一個場景
比如用戶登錄,每個請求發起之前都會判斷用戶是否登錄,如果每個請求都去判斷一次,那就重復地做了很多事情,只要是有重復的地方,就有優化的空間。現在就把重復的地方抽取出來,暫且稱之為 " 攔截器 ",然后每次請求之前就先經過" 攔截器 ",這個編程的思想就可以稱之為面向切面編程。AOP(Aspect Oriented Program)
最典型的應用就是事務管理和權限驗證,還有日志統計,下文中的案例就是接口執行時間的統計。
spring中使用AOP(基於注解)
不得不說注解是個很巧妙的設計,使用很少量的信息描述數據,這類數據稱之為元數據,描述數據的數據。關於注解的理解,這里有個傳送門:http://www.importnew.com/10294.html
下面的案例是在springBoot中進行的,直觀地感受一下如何使用注解完成AOP。
@Service
public class UserService {
public void getUser() {
//To do something
System.out.println("getUser() has been called");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
切面是這樣定義的:
@Component
@Aspect
public class LoggerAspect {
/**
* getUser()執行之前執行
*/
@Before("execution(* com.springboot.demo.service.UserService.getUser(..))")
public void callBefore() {
System.out.println("before call method");
System.out.println("begin........................");
}
/**
* getUser()執行之后執行
*/
@After("execution(* com.springboot.demo.service.UserService.getUser(..))")
public void callAfter() {
System.out.println("after call method");
System.out.println("end..............................");
}
}
來個單元測試驗證一下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void getUserTest() {
userService.getUser();
}
}
具體案例
假如有以下的業務場景: UserService業務類中有個getUser()這個方法,現在想統計一下這個方法的執行時間,可能需要測試這個接口的性能。通常做法是方法開始時獲取系統當前時間,然后方法結束時獲取當前時間,最后 excuteTime=endTime-startTime。
如果現在不僅是這個方法需要統計,還有getUserByName()、getUserById()需要統計,上述的方法明顯很笨了。
使用AOP怎么解決? 抽取公共部分為一個切面,方法執行前記錄時間,然后執行目標方法,最后,目標方法執行完成之后再獲取一次系統時間。
具體實現如下:在LoggerAspect中再寫一個方法,記錄getUser()方法的執行時間。
/**
* 記錄執行時間
* @param point 切點
* @return
* @throws Throwable
*/
@Around("execution(* com.springboot.demo.service.UserService.getUser(..))")
public Object getMethodExecuteTime(ProceedingJoinPoint point) throws Throwable {
System.out.println("---------------getMethodExecuteTime------------------");
long startTime = System.currentTimeMillis();
//調用目標方法
Object result = point.proceed();
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
System.out.println("executeTime=" + executeTime + "------------------");
return result;
}
@Around將目標方法再次封裝,控制了它的調用時機,以此來記錄getUser()的執行時間。但是好像並沒有達到記錄UserService中的多個方法的執行時間的目的。
@Around("execution(* com.springboot.demo.service.UserService.getUser(..))")
其中指定了切點是getUser()這個方法,這里的表達式很豐富,可以設置為:
* com.springboot.demo.service.UserService.*(..)
表示UserService中的每一個方法都是切點,甚至可以是這樣:
* com.springboot.demo.service..(..)
表示service包下的所有類的所有方法都是切點,但是這樣很明顯不夠靈活,如果能自定義地控制就更好了。
自定義一個注解
如果用一個注解標注某個方法需要記錄其執行時間,豈不是更加優雅。
/**
* @Description 標注某個方法需要記錄執行時間
* @Author YaoQi
* @Date 2018/7/6 15:51
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logger {
String value() default "";
}
注解是用來描述數據的,上面的這個注解的意思是:這個注解將作用於方法,並且在運行時有效。但是這樣只是標注了,如何讀取這個標注的信息?
在LoggerAspect中加入這一個方法:
/**
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(com.springboot.demo.annotation.Logger)")
public Object getMethodExecuteTimeForLogger(ProceedingJoinPoint point) throws Throwable {
System.out.println("---------------getMethodExecuteTime------------------");
long startTime = System.currentTimeMillis();
Object result = point.proceed();
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
System.out.println("executeTime=" + executeTime + "------------------");
return result;
}
哪個方法需要記錄執行時間就將@Logger放在對應的方法上:
@Logger
public void getUser() {
System.out.println("getUser() has been called");
}
