首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring AOP 四大實現方式全解析:從代理工廠到AspectJ註解與XML配置

     編輯:Java 綜合教學

Spring AOP 實現演進脈絡

方式時代核心類/註解特點現狀
1. 經典代理 APISpring 1.xProxyFactoryBean, MethodBeforeAdvice, Advisor程式化配置繁瑣、侵入性強教學/遺留維護
2. 自動代理創建器Spring 2.0DefaultAdvisorAutoProxyCreator, 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依賴、配置外部化遺留/無註解場景

方式一:經典 ProxyFactoryBean 手工裝配

核心組件

  • Advice (通知):MethodBeforeAdvice (前置)、AfterReturningAdvice (後置)、ThrowsAdvice (異常)、MethodInterceptor (環繞)
  • Advisor (顧問):Pointcut (切點) + Advice = 針對性增強
  • ProxyFactoryBean:代理工廠,生成代理對象

完整 XML 配置

<?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。

方式三:@AspectJ 註解驅動 (現代標準)

啟用支持

@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 { /*...*/ }
}

方式四:Schema XML (aop:config) - 純POJO切面

切面類:普通POJO,無註解

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 最終增強"); }
}

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>

切點表達式 (Pointcut Expression) 速查表

表達式含義示例
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 動態代理 vs CGLIB

維度JDK 動態代理CGLIB
原理實現目標接口,委派調用繼承目標類,重寫方法
前提目標類必須實現接口無需接口,但類/方法非 final
性能創建快、調用略慢 (反射)創建慢 (字節碼生成)、調用快 (FastClass索引)
Spring Boot 默認proxy-target-class=falseproxy-target-class=true (2.0+ 默認開啟)
強制指定@EnableAspectJAutoProxy(proxyTargetClass=false)@EnableAspectJAutoProxy(proxyTargetClass=true)

總結與選型建議

  1. 新項目首選 @AspectJ 註解:代碼與配置合一、重構友好、IDE導航、支持完整切點語法
  2. 舊項目/第三方Bean無法加註解:用 aop:config XML 配置外部化
  3. 理解底層:無論哪種方式,本質都是BeanPostProcessor + 動態代理 (JDK/CGLIB) 在初始化後替換Bean
  4. 避免濫用:AOP適合橫切關注點 (事務、日志、安全、監控、幂等、緩存),業務邏輯應留在Service
  5. 性能意識:環繞增強pjp.proceed()有開銷,高頻調用路徑謹慎使用
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved