AOP (Aspect Oriented Programming) 是 OOP 的延續,旨在將橫切關注點(日誌、事務、安全等)與業務邏輯分離,實現解耦。本質上是動態代理機制的應用。
| 術語 | 定義 | 對應配置 |
|---|---|---|
| 切面 | 橫切關注點的模組化 | <aop:aspect> / @Aspect |
| 連接點 | 程式執行過程中的特定點(方法調用、異常拋出) | 隱含 |
| 通知 | 切面在連接點執行的動作 | <aop:before> 等 / @Before 等 |
| 切入點 | 匹配連接點的斷言表達式 | <aop:pointcut> / @Pointcut |
| 目標對象 | 被代理的業務對象 | <bean> 定義 |
| AOP 代理 | Spring 生成的代理物件(JDK 動態代理 / CGLIB) | proxy-target-class 屬性控制 |
@Before):方法執行前,不可阻止執行@After):方法退出時(正常/異常)@AfterReturning):正常返回後@Around):包圍方法執行,可控制是否執行目標方法,最強大@AfterThrowing):拋出異常時<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:context="https://www.springframework.org/schema/context"
xmlns:aop="https://www.springframework.org/schema/aop"
xsi:schemaLocation="
https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.1.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="cn.ysh.studio.spring.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 強制 CGLIB -->
<bean id="userService" class="cn.ysh.studio.spring.aop.service.UserService"/>
</beans>
@Component
@Aspect
public class ServiceAspect {
private static final Log log = LogFactory.getLog(ServiceAspect.class);
@Pointcut("execution(* cn.ysh.studio.spring.aop.service..*(..))")
public void aspect() {} // 空方法體,供其他通知引用
@Before("aspect()")
public void before(JoinPoint jp) {
log.info("before " + jp);
}
@After("aspect()")
public void after(JoinPoint jp) {
log.info("after " + jp);
}
@Around("aspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed(); // 執行目標方法
log.info("around " + pjp + " 耗時:" + (System.currentTimeMillis() - start) + "ms");
return result;
} catch (Throwable e) {
log.info("around " + pjp + " 異常:" + e.getMessage());
throw e;
}
}
@AfterReturning("aspect()")
public void afterReturn(JoinPoint jp) {
log.info("afterReturn " + jp);
}
@AfterThrowing(pointcut = "aspect()", throwing = "ex")
public void afterThrow(JoinPoint jp, Exception ex) {
log.info("afterThrow " + jp + " " + ex.getMessage());
}
}
public class Tester {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) ctx.getBean("userService");
service.get(1L);
service.save(new User());
try { service.delete(1L); } catch (Exception e) { /* 記錄日誌 */ }
}
}
before execution(User get(long)) getUser method ... around execution(User get(long)) 耗時:42ms after execution(User get(long)) afterReturn execution(User get(long)) before execution(void save(User)) saveUser method ... around execution(void save(User)) 耗時:2ms after execution(void save(User)) afterReturn execution(void save(User)) before execution(boolean delete(long)) delete method ... around execution(boolean delete(long)) 耗時:5ms with exception ... after execution(boolean delete(long)) afterReturn execution(boolean delete(long)) // 注意:異常時 afterReturn 仍執行 WARN - Delete user : Null return value from advice...
<beans ... xmlns:aop="https://www.springframework.org/schema/aop" ...>
<bean id="serviceAspect" class="cn.ysh.studio.spring.aop.aspect.ServiceAspect"/>
<aop:config>
<aop:aspect id="simpleAspect" ref="serviceAspect">
<aop:pointcut expression="execution(* cn.ysh.studio.spring.aop.service..*(..))" id="simplePointcut"/>
<aop:before pointcut-ref="simplePointcut" method="before"/>
<aop:after pointcut-ref="simplePointcut" method="after"/>
<aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
<aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
<!-- 環繞通知需方法簽名為 Object method(ProceedingJoinPoint) -->
<aop:around pointcut-ref="simplePointcut" method="around"/>
</aop:aspect>
</aop:config>
</beans>
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:權限修飾符(可選)ret-type-pattern:返回類型(**必填**),* 表任意declaring-type-pattern:類全路徑(可選),支援 .. 遞歸匹配子包name-pattern:方法名(**必填**),* 表任意param-pattern:參數列表,**必填**,(..) 表任意參數throws-pattern:異常(可選)| 表達式 | 含義 |
|---|---|
execution(* *(..)) | 匹配所有方法 |
execution(public * com.svc.UserService.*(..)) | 匹配 UserService 所有 public 方法 |
execution(* com.svc..*.*(..)) | 匹配 com.svc 及子包所有類的所有方法 |
&& (and):execution(* foo()) && execution(* bar())|| (or):within(com.pkg1) || within(com.pkg2)! (not):!execution(* setter())within:限定類範圍this:代理物件類型target:目標物件類型args:參數類型@annotation:標註特定註解的方法<aop:config proxy-target-class="true"> 或 @EnableAspectJAutoProxy(proxyTargetClass = true)掌握 AOP 核心在於理解「切入點精準匹配」與「通知類型正確選擇」,環繞通知雖強大但需謹慎使用 ProceedingJoinPoint.proceed(),否則目標方法不會執行。