一直心心念的想寫一篇關於AOP切面實例的博文,拖更了許久之后,今天終於着手下筆將其完成。
基礎概念
1、切面(Aspect)
首先要理解‘切’字,需要把對象想象成一個立方體,傳統的面向對象變成思維,類定義完成之后(封裝)。每次實例化一個對象,對類定義中的成員變量賦值,就相當於對這個立方體進行了一個定義,定義完成之后,那個對象就在那里,不卑不亢,不悲不喜,等着被使用,等着被回收。
面向切面編程則是指,對於一個我們已經封裝好的類,我們可以在編譯期間或在運行期間,對其進行切割,把立方體切開,在原有的方法里面添加(織入)一些新的代碼,對原有的方法代碼進行一次增強處理。而那些增強部分的代碼,就被稱之為切面,如下面代碼實例中的通用日志處理代碼,常見的還有事務處理、權限認證等等。
2、切入點(PointCut)
要對哪些類中的哪些方法進行增強,進行切割,指的是被增強的方法。即要切哪些東西。
3、連接點(JoinPoint)
我們知道了要切哪些方法后,剩下的就是什么時候切,在原方法的哪一個執行階段加入增加代碼,這個就是連接點。如方法調用前,方法調用后,發生異常時等等。
4、通知(Advice)
通知被織入方法,改如何被增強。定義切面的具體實現。那么這里面就涉及到一個問題,空間(切哪里)和時間(什么時候切,在何時加入增加代碼),空間我們已經知道了就是切入點中定義的方法,而什么時候切,則是連接點的概念,如下面實例中,通用日志處理(切面),@Pointcut規則中指明的方法即為切入點,@Before、@After是連接點,而下面的代碼就是對應通知。
@Before("cutMethod()")
public void begin() {
System.out.println("==@Before== lingyejun blog logger : begin");
}
5、目標對象(Target Object)
被一個或多個切面所通知的對象,即為目標對象。
6、AOP代理對象(AOP Proxy Object)
AOP代理是AOP框架所生成的對象,該對象是目標對象的代理對象。代理對象能夠在目標對象的基礎上,在相應的連接點上調用通知。
7、織入(Weaving)
將切面切入到目標方法之中,使目標方法得到增強的過程被稱之為織入。
實例代碼
相關依賴包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.8.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
定義和實現日志切面
package com.lingyejun.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Author: Lingye
* @Date: 2018/11/11
* @Describe:
* 定義日志切面
* @Lazy 注解:容器一般都會在啟動的時候實例化所有單實例 bean,如果我們想要 Spring 在啟動的時候延遲加載 bean,需要用到這個注解
* value為true、false 默認為true,即延遲加載,@Lazy(false)表示對象會在初始化的時候創建
*
* @Modified By:
*/
@Aspect
@Component
@Lazy(false)
public class LoggerAspect {
/**
* 定義切入點:對要攔截的方法進行定義與限制,如包、類
*
* 1、execution(public * *(..)) 任意的公共方法
* 2、execution(* set*(..)) 以set開頭的所有的方法
* 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply這個類里的所有的方法
* 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的類的所有的方法
* 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的類的所有的方法
* 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的類的有三個參數,第一個參數為String類型,第二個參數為任意類型,第三個參數為Long類型的方法
* 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
*/
@Pointcut("@annotation(com.lingyejun.annotation.Lingyejun)")
private void cutMethod() {
}
/**
* 前置通知:在目標方法執行前調用
*/
@Before("cutMethod()")
public void begin() {
System.out.println("==@Before== lingyejun blog logger : begin");
}
/**
* 后置通知:在目標方法執行后調用,若目標方法出現異常,則不執行
*/
@AfterReturning("cutMethod()")
public void afterReturning() {
System.out.println("==@AfterReturning== lingyejun blog logger : after returning");
}
/**
* 后置/最終通知:無論目標方法在執行過程中出現一場都會在它之后調用
*/
@After("cutMethod()")
public void after() {
System.out.println("==@After== lingyejun blog logger : finally returning");
}
/**
* 異常通知:目標方法拋出異常時執行
*/
@AfterThrowing("cutMethod()")
public void afterThrowing() {
System.out.println("==@AfterThrowing== lingyejun blog logger : after throwing");
}
/**
* 環繞通知:靈活自由的在目標方法中切入代碼
*/
@Around("cutMethod()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲取目標方法的名稱
String methodName = joinPoint.getSignature().getName();
// 獲取方法傳入參數
Object[] params = joinPoint.getArgs();
Lingyejun lingyejun = getDeclaredAnnotation(joinPoint);
System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
// 執行源方法
joinPoint.proceed();
// 模擬進行驗證
if (params != null && params.length > 0 && params[0].equals("Blog Home")) {
System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth success");
} else {
System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth failed");
}
}
/**
* 獲取方法中聲明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
public Lingyejun getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
// 獲取方法名
String methodName = joinPoint.getSignature().getName();
// 反射獲取目標類
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法對應的參數類型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根據類、方法、參數類型(重載)獲取到方法的具體信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定義的注解信息
Lingyejun annotation = objMethod.getDeclaredAnnotation(Lingyejun.class);
// 返回
return annotation;
}
}
自定義一個注解
package com.lingyejun.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: Lingye
* @Date: 2018/11/11
* @Describe:
* @Modified By:
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lingyejun {
/**
* 何種場景下的通用日志打印
*
* @return
*/
String module();
}
調用切面類
package com.lingyejun.annotation;
import org.springframework.stereotype.Component;
/**
* @Author: Lingye
* @Date: 2018/11/11
* @Describe:
* @Modified By:
*/
@Component
public class LoggerApply {
@Lingyejun(module = "http://www.cnblogs.com/lingyejun/")
public void lingLogger(String event) throws Exception {
System.out.println("lingLogger(String event) : lingyejun will auth by blog address");
throw new Exception();
}
}
測試代碼
package com.lingyejun.annotation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class AnnotationTest {
@Autowired
private LoggerApply loggerApply;
@Test
public void testAnnotationLogger() {
try {
loggerApply.lingLogger("Blog Home");
} catch (Exception e) {
System.out.println("a exception be there");
}
}
}
效果展示

