Spring AOP与实践指南

什么是AOP?

面向切面编程(Aspect-Oriented Programming)是一种通过预编译方式和运行期动态代理实现程序功能增强的技术。它与OOP形成互补关系,专注于横切关注点(如日志记录、事务管理等)的模块化处理。

核心概念解析:

  1. 切面(Aspect):封装横切逻辑的模块化单元
  2. 连接点(Join Point):程序执行过程中的特定节点(方法调用、异常抛出等)
  3. 切入点(Pointcut):定义在哪些连接点应用通知
  4. 通知(Advice):在特定连接点执行的动作
  5. 目标对象(Target Object):被代理的原始对象
  6. AOP代理(AOP Proxy):实现切面功能的增强对象

Spring AOP实现原理

Spring AOP基于动态代理技术实现,主要采用两种方式:

1. JDK动态代理

  • 适用于接口代理
  • 通过java.lang.reflect.Proxy创建代理实例
  • 代理对象实现目标接口

示例代码:

public class JdkProxyDemo {
    public interface Service {
        void execute();
    }

    public static class ServiceImpl implements Service {
        public void execute() {
            System.out.println("Actual service execution");
        }
    }

    public static void main(String[] args) {
        Service proxy = (Service) Proxy.newProxyInstance(
            Service.class.getClassLoader(),
            new Class[]{Service.class},
            (proxy1, method, args1) -> {
                System.out.println("Before method");
                Object result = method.invoke(new ServiceImpl(), args1);
                System.out.println("After method");
                return result;
            });
        proxy.execute();
    }
}

2. CGLIB代理

  • 适用于类代理
  • 通过继承方式生成子类
  • 需要引入CGLIB依赖
  • 无法代理final类和final方法

性能对比:

代理方式 启动速度 运行速度 适用场景
JDK动态代理 较快 较快 接口代理
CGLIB 较慢 较快 类代理、无接口情况

Spring AOP通知类型

Spring支持五种通知类型:

1. 前置通知(@Before)

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Method called: " + joinPoint.getSignature().getName());
    }
}

2. 后置返回通知(@AfterReturning)

@AfterReturning(
    pointcut = "execution(* com.example.service.*.*(..))",
    returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
    System.out.println("Method returned: " + result);
}

3. 异常通知(@AfterThrowing)

@AfterThrowing(
    pointcut = "execution(* com.example.service.*.*(..))",
    throwing = "ex")
public void logException(JoinPoint joinPoint, Exception ex) {
    System.out.println("Exception in method: " + ex.getMessage());
}

4. 最终通知(@After)

@After("execution(* com.example.service.*.*(..))")
public void logFinally(JoinPoint joinPoint) {
    System.out.println("Method execution completed");
}

5. 环绕通知(@Around)

@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    try {
        Object result = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - start;
        System.out.println("Method executed in " + executionTime + "ms");
        return result;
    } catch (Exception ex) {
        System.out.println("Exception occurred: " + ex.getMessage());
        throw ex;
    }
}

切入点表达式详解

Spring AOP使用AspectJ切入点表达式语法:

1. 执行表达式

execution(modifiers-pattern? 
          ret-type-pattern 
          declaring-type-pattern? 
          name-pattern(param-pattern)
          throws-pattern?)

示例:

// 匹配所有public方法
execution(public * *(..))

// 匹配com.example包下所有类的save开头方法
execution(* com.example..*.save*(..))

// 匹配UserService接口的所有方法
execution(* com.example.service.UserService.*(..))

2. 类型限定表达式

// 匹配特定注解标注的方法
@annotation(com.example.Loggable)

// 匹配特定类型的方法
within(com.example.service.*)

// 匹配实现指定接口的类
this(com.example.service.BaseService)

// 匹配目标对象类型
target(com.example.service.UserServiceImpl)

3. 组合表达式

@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(loggable)")
public void loggableMethod(Loggable loggable) {}

@Before("loggableMethod(loggable)")
public void logWithLevel(JoinPoint jp, Loggable loggable) {
    System.out.println("Log level: " + loggable.level());
}

高级特性与应用

1. 引入(Introduction)

为对象动态添加接口实现:

@Aspect
public class IntroductionAspect {
    @DeclareParents(value = "com.example.service.*Service+", 
                   defaultImpl = DefaultAuditable.class)
    public static Auditable mixin;
}

public interface Auditable {
    void audit();
}

public class DefaultAuditable implements Auditable {
    public void audit() {
        System.out.println("Audit performed");
    }
}

2. 参数绑定

@Before("com.example.SystemArchitecture.dataAccessOperation() && args(id)")
public void logId(long id) {
    System.out.println("Accessing record with ID: " + id);
}

3. 优先级控制

@Aspect
@Order(1)
public class SecurityAspect {
    // 高优先级切面
}

@Aspect
@Order(2)
public class LoggingAspect {
    // 低优先级切面
}

Spring AOP配置方式

1. XML配置

<aop:config>
    <aop:aspect id="loggingAspect" ref="logAspect">
        <aop:pointcut id="serviceMethods" 
            expression="execution(* com.example.service.*.*(..))"/>
        <aop:before pointcut-ref="serviceMethods" method="logBefore"/>
    </aop:aspect>
</aop:config>

<bean id="logAspect" class="com.example.aspect.LoggingAspect"/>

2. 注解配置

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}

与AspectJ集成

1. 加载时织入(LTW)

配置示例:

<context:load-time-weaver aspectj-weaving="autodetect"/>

<bean id="customAspect" class="com.example.CustomAspect"
      factory-method="aspectOf"/>

Maven依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

2. 编译时织入

使用AspectJ编译器进行静态织入:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>11</complianceLevel>
        <source>11</source>
        <target>11</target>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

常见应用场景

1. 日志记录

@Aspect
@Component
public class MethodLogger {
    @Around("@annotation(LogMethod)")
    public Object logMethod(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        
        System.out.println("Entering: " + methodName);
        System.out.println("Arguments: " + Arrays.toString(args));
        
        long start = System.nanoTime();
        Object result = pjp.proceed();
        long duration = System.nanoTime() - start;
        
        System.out.println("Exiting: " + methodName);
        System.out.println("Execution time: " + duration + "ns");
        return result;
    }
}

2. 事务管理

@Aspect
@Component
public class TransactionAspect {
    @Autowired
    private PlatformTransactionManager transactionManager;

    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionDefinition());
        try {
            Object result = pjp.proceed();
            transactionManager.commit(status);
            return result;
        } catch (Exception ex) {
            transactionManager.rollback(status);
            throw ex;
        }
    }
}

3. 性能监控

@Aspect
@Component
public class PerformanceMonitor {
    private final Map<String, MethodStats> methodStats = new ConcurrentHashMap<>();
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().toShortString();
        long start = System.currentTimeMillis();
        
        try {
            return pjp.proceed();
        } finally {
            long executionTime = System.currentTimeMillis() - start;
            updateStats(methodName, executionTime);
        }
    }
    
    private void updateStats(String methodName, long execTime) {
        methodStats.compute(methodName, (k, v) -> {
            if (v == null) v = new MethodStats();
            v.count++;
            v.totalTime += execTime;
            v.maxTime = Math.max(v.maxTime, execTime);
            return v;
        });
    }
    
    @Scheduled(fixedRate = 60000)
    public void logStatistics() {
        methodStats.forEach((name, stats) -> {
            System.out.printf("%s: invocations=%d, avg=%.2fms, max=%dms%n",
                name, stats.count, 
                (double)stats.totalTime/stats.count, stats.maxTime);
        });
    }
    
    private static class MethodStats {
        int count;
        long totalTime;
        long maxTime;
    }
}

性能优化与最佳实践

1. 优化切入点表达式

  • 避免使用通配符过度的表达式
  • 优先使用within限定包范围
  • 使用bean()指示符进行Spring Bean匹配
  • 组合使用多种限定条件

2. 代理选择策略

  • 优先使用接口代理(JDK动态代理)
  • 需要代理类方法时使用CGLIB
  • 配置强制使用CGLIB:
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    

3. 缓存切入点解析

@Aspect
public class CachedAspect {
    private Pointcut cachedPointcut;
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    @Around("serviceMethods()")
    public Object cachedAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 缓存相关逻辑
    }
}

4. 避免循环代理

  • 使用@Autowired时注意代理对象注入
  • 使用ObjectProvider延迟获取Bean
  • 配置排除自动代理:
    @EnableAspectJAutoProxy(exposeProxy = true)
    

常见问题解决方案

1. 内部方法调用失效

问题代码:

public class UserService {
    public void outerMethod() {
        innerMethod();
    }
    
    @Transactional
    public void innerMethod() {
        // 事务不生效
    }
}

解决方案:

public class UserService {
    @Autowired
    private ApplicationContext context;
    
    public void outerMethod() {
        context.getBean(UserService.class).innerMethod();
    }
}

2. 多切面执行顺序

控制方法:

@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SecurityAspect {
    // 最先执行
}

@Aspect
@Order(Ordered.LOWEST_PRECEDENCE)
public class LoggingAspect {
    // 最后执行
}

3. 注解继承问题

自定义元注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public @interface AuditTransaction {}
正文到此结束
评论插件初始化中...
Loading...