| 方式 | 時代 | 核心類/註解 | 特點 | 現狀 |
|---|---|---|---|---|
| 1. 經典代理 API | Spring 1.x | ProxyFactoryBean, MethodBeforeAdvice, Advisor | 程式化配置繁瑣、侵入性強 | 教學/遺留維護 |
| 2. 自動代理創建器 | Spring 2.0 | DefaultAdvisorAutoProxyCreator, BeanNameAutoProxyCreator | 掃描Advisor自動代理、減少XML | 內部機制,少直接用 |
| 3. @AspectJ 註解 | Spring 2.0+ | @Aspect, @Before/@After/@Around, @Pointcut | 代碼即配置、IDE友好、功能最全 | 主流推薦 |
| 4. Schema XML (aop:config) | Spring 2.0+ | <aop:config>, <aop:aspect>, <aop:pointcut> | 純POJO切面、無AspectJ依賴、配置外部化 | 遺留/無註解場景 |
MethodBeforeAdvice (前置)、AfterReturningAdvice (後置)、ThrowsAdvice (異常)、MethodInterceptor (環繞)Pointcut (切點) + Advice = 針對性增強<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目標對象 -->
<bean id="human" class="com.example.Human"/>
<!-- 通知 (Advice) -->
<bean id="beforeAdvice" class="com.example.SleepBeforeAdvice"/>
<bean id="afterAdvice" class="com.example.SleepAfterAdvice"/>
<bean id="aroundAdvice" class="com.example.SleepAroundAdvice"/>
<!-- 顧問:切點 + 通知 -->
<!-- 正則表達式切點 -->
<bean id="sleepAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="beforeAdvice"/>
<property name="pattern" value=".*sleep.*"/>
</bean>
<!-- 方法名匹配切點 -->
<bean id="methodNameAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="afterAdvice"/>
<property name="mappedNames" value="sleep,walk"/>
</bean>
<!-- 代理工廠 -->
<bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="human"/>
<property name="interceptorNames" value="sleepAdvisor,methodNameAdvisor"/>
<!-- proxyInterfaces 可選,默認代理目標實現的所有接口 -->
</bean>
</beans>
ApplicationContext ctx = new ClassPathXmlApplicationContext("classic-aop.xml");
Sleepable human = (Sleepable) ctx.getBean("humanProxy"); // 拿代理
human.sleep(); // 觸發前置/環繞/後置增強
<!-- 1. 默認顧問自動代理:掃描容器中所有 Advisor Bean,自動代理匹配的目標 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<!-- 2. BeanName 自動代理:指定目標 Bean 名稱,指定攔截器 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="human,worker"/>
<property name="interceptorNames" value="sleepAdvisor"/>
</bean>
原理:實現BeanPostProcessor,在postProcessAfterInitialization階段檢查Bean是否匹配切點,匹配則創建代理替換原Bean。
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // true: CGLIB代理類;false: JDK動態代理接口
public class AopConfig { }
或 XML:<aop:aspectj-autoproxy proxy-target-class="true"/>
@Aspect
@Component // 掃描為Bean
public class LoggingAspect {
// 定義切點:執行 service 包下所有公共方法
@Pointcut("execution(public * com.example.service..*(..))")
public void serviceLayer() {}
@Pointcut("@annotation(com.example.annotation.LogExecutionTime)")
public void logTimeAnnotated() {}
// 前置增強
@Before("serviceLayer()")
public void logBefore(JoinPoint jp) {
log.info("ENTER: {}.{} args={}", jp.getSignature().getDeclaringTypeName(),
jp.getSignature().getName(), Arrays.toString(jp.getArgs()));
}
// 後置返回增強
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturn(JoinPoint jp, Object result) {
log.info("EXIT: {}.{} result={}", jp.getSignature().getDeclaringTypeName(),
jp.getSignature().getName(), result);
}
// 異常增強
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logAfterThrow(JoinPoint jp, Exception ex) {
log.error("EXCEPTION: {}.{} msg={}", jp.getSignature().getDeclaringTypeName(),
jp.getSignature().getName(), ex.getMessage(), ex);
}
// 環繞增強 (最強大,可控制是否執行目標、修改參數/返回值)
@Around("serviceLayer()")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed(); // 執行目標方法
} finally {
long elapsed = System.nanoTime() - start;
log.info("TIME: {}.{} took {} ms", pjp.getSignature().getDeclaringTypeName(),
pjp.getSignature().getName(), elapsed / 1_000_000);
}
}
// 最終增強 (try-finally finally塊)
@After("serviceLayer()")
public void cleanup(JoinPoint jp) {
// 清理資源、MDC移除等
}
// 組合切點
@Around("serviceLayer() && logTimeAnnotated()")
public Object detailedLog(ProceedingJoinPoint pjp) throws Throwable { /*...*/ }
}
public class SimpleLoggingAspect {
public void beforeMethod() { log.info("XML 前置增強"); }
public void afterMethod(Object ret) { log.info("XML 後置增強: {}", ret); }
public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
log.info("XML 環繞前");
Object ret = pjp.proceed();
log.info("XML 環繞後");
return ret;
}
public void afterThrow(Exception ex) { log.error("XML 異常增強", ex); }
public void afterFinally() { log.info("XML 最終增強"); }
}
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:aop="https://www.springframework.org/schema/aop"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="targetService" class="com.example.TargetService"/>
<bean id="loggingAspect" class="com.example.SimpleLoggingAspect"/>
<aop:config>
<!-- 定義切點 -->
<aop:pointcut id="servicePointcut" expression="execution(* com.example.service..*(..))"/>
<!-- 定義切面 -->
<aop:aspect ref="loggingAspect">
<aop:before method="beforeMethod" pointcut-ref="servicePointcut"/>
<aop:after-returning method="afterMethod" pointcut-ref="servicePointcut" returning="ret"/>
<aop:around method="aroundMethod" pointcut-ref="servicePointcut"/>
<aop:after-throwing method="afterThrow" pointcut-ref="servicePointcut" throwing="ex"/>
<aop:after method="afterFinally" pointcut-ref="servicePointcut"/>
</aop:aspect>
</aop:config>
</beans>
| 表達式 | 含義 | 示例 |
|---|---|---|
| execution(modifiers? ret-type pkg.Class.method(args)) | 方法執行 (最常用) | execution(public * com..service.*Service.*(..)) |
| within(pkg.Class) | 類型內方法執行 | within(com.example.service.*) |
| this(type) | 代理對象類型匹配 (CGLIB代理) | this(com.example.service.UserService) |
| target(type) | 目標對象類型匹配 | target(com.example.service.UserService) |
| args(type) | 參數類型匹配 | args(java.io.Serializable) |
| @annotation(AnnotationClass) | 方法有註解 | @annotation(com.example.Log) |
| @within(AnnotationClass) | 類有註解 | @within(org.springframework.stereotype.Service) |
| @target(AnnotationClass) | 目標對象類有註解 | @target(org.springframework.stereotype.Service) |
| @args(AnnotationClass) | 參數運行時類型有註解 | @args(com.example.Validated) |
| bean(beanName) | Spring Bean名稱匹配 | bean(*Service) |
組合:&& (and)、|| (or)、! (not)
| 維度 | JDK 動態代理 | CGLIB |
|---|---|---|
| 原理 | 實現目標接口,委派調用 | 繼承目標類,重寫方法 |
| 前提 | 目標類必須實現接口 | 無需接口,但類/方法非 final |
| 性能 | 創建快、調用略慢 (反射) | 創建慢 (字節碼生成)、調用快 (FastClass索引) |
| Spring Boot 默認 | proxy-target-class=false | proxy-target-class=true (2.0+ 默認開啟) |
| 強制指定 | @EnableAspectJAutoProxy(proxyTargetClass=false) | @EnableAspectJAutoProxy(proxyTargetClass=true) |
aop:config XML 配置外部化pjp.proceed()有開銷,高頻調用路徑謹慎使用