Skip to content

AOP面向切面编程

AOP概念与原理

什么是AOP

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将横切关注点与业务逻辑分离。

为什么需要AOP

传统方式的问题:

java
public class UserService {
    public void addUser(User user) {
        // 日志记录
        System.out.println("开始添加用户");
        long start = System.currentTimeMillis();
        
        try {
            // 业务逻辑
            userRepository.save(user);
            
            // 日志记录
            System.out.println("添加用户成功,耗时: " + (System.currentTimeMillis() - start));
        } catch (Exception e) {
            // 异常处理
            System.out.println("添加用户失败: " + e.getMessage());
            throw e;
        }
    }
}

使用AOP后:

java
public class UserService {
    public void addUser(User user) {
        // 纯粹的业务逻辑
        userRepository.save(user);
    }
}

// 切面处理日志、异常等
@Aspect
@Component
public class LoggingAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        // 日志、异常处理等
    }
}

AOP核心概念

概念说明
切面(Aspect)横切关注点的模块化
切点(Pointcut)定义在哪些连接点执行通知
通知(Advice)在切点执行的具体操作
连接点(Joinpoint)程序执行的特定点(方法调用、异常抛出等)
目标对象(Target)被通知的对象
代理(Proxy)AOP框架创建的对象
织入(Weaving)将切面应用到目标对象的过程

代理模式

JDK动态代理

java
public class JdkProxyHandler implements InvocationHandler {
    private Object target;
    
    public JdkProxyHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置通知");
        Object result = method.invoke(target, args);
        System.out.println("后置通知");
        return result;
    }
    
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new JdkProxyHandler(target)
        );
        proxy.addUser(new User());
    }
}

CGLIB代理

java
public class CglibProxyHandler implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置通知");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("后置通知");
        return result;
    }
    
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new CglibProxyHandler());
        UserService proxy = (UserService) enhancer.create();
        proxy.addUser(new User());
    }
}

Spring 7默认使用CGLIB

Spring 7默认使用CGLIB代理,无需接口:

java
@Service
public class UserService {
    public void addUser(User user) {
        // 业务逻辑
    }
}

@AspectJ注解

启用AspectJ

java
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}

定义切面

java
@Aspect
@Component
public class LoggingAspect {
    
    // 切点定义
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethod() {}
    
    // 通知
    @Before("serviceMethod()")
    public void before(JoinPoint joinPoint) {
        System.out.println("执行方法: " + joinPoint.getSignature().getName());
    }
}

切点表达式

execution表达式

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

// 匹配com.example.service包下所有方法
execution(* com.example.service.*.*(..))

// 匹配com.example.service包及子包下所有方法
execution(* com.example.service..*.*(..))

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

// 匹配所有以save开头的方法
execution(* save*(..))

// 匹配只有一个参数且参数类型为String的方法
execution(* *(String))

// 匹配返回值为User的方法
execution(com.example.entity.User *(..))

其他切点表达式

java
// 匹配带有@Log注解的方法
@annotation(com.example.annotation.Log)

// 匹配带有@Service注解的类中的所有方法
@within(org.springframework.stereotype.Service)

// 匹配参数带有@Valid注解的方法
@args(javax.validation.Valid)

// 匹配Bean名称
bean(userService)

// 组合表达式
execution(* com.example.service.*.*(..)) && @annotation(Log)
execution(* com.example.service.*.*(..)) || execution(* com.example.repository.*.*(..))
!execution(* com.example.service.*.get*(..))

切点表达式示例

java
@Aspect
@Component
public class PointcutDemo {
    
    // 匹配service包下所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    // 匹配repository包下所有方法
    @Pointcut("execution(* com.example.repository.*.*(..))")
    public void repositoryLayer() {}
    
    // 匹配service或repository层
    @Pointcut("serviceLayer() || repositoryLayer()")
    public void serviceOrRepository() {}
    
    // 匹配带有@Transactional注解的方法
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethod() {}
}

通知类型

@Before(前置通知)

java
@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("执行方法: " + methodName + ", 参数: " + Arrays.toString(args));
    }
}

@AfterReturning(返回通知)

java
@Aspect
@Component
public class LoggingAspect {
    
    @AfterReturning(
        pointcut = "execution(* com.example.service.*.*(..))",
        returning = "result"
    )
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + " 返回: " + result);
    }
}

@AfterThrowing(异常通知)

java
@Aspect
@Component
public class ExceptionAspect {
    
    @AfterThrowing(
        pointcut = "execution(* com.example.service.*.*(..))",
        throwing = "ex"
    )
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + " 抛出异常: " + ex.getMessage());
    }
}

@After(后置通知)

java
@Aspect
@Component
public class FinallyAspect {
    
    @After("execution(* com.example.service.*.*(..))")
    public void after(JoinPoint joinPoint) {
        System.out.println("方法执行完成: " + joinPoint.getSignature().getName());
    }
}

@Around(环绕通知)

java
@Aspect
@Component
public class AroundAspect {
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        
        // 前置处理
        System.out.println("开始执行: " + methodName);
        long start = System.currentTimeMillis();
        
        try {
            // 执行目标方法
            Object result = pjp.proceed();
            
            // 返回处理
            long end = System.currentTimeMillis();
            System.out.println("执行完成: " + methodName + ", 耗时: " + (end - start) + "ms");
            
            return result;
        } catch (Exception e) {
            // 异常处理
            System.out.println("执行异常: " + methodName + ", 异常: " + e.getMessage());
            throw e;
        } finally {
            // 最终处理
            System.out.println("最终处理: " + methodName);
        }
    }
}

实战示例

日志切面

java
@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    @Around("@annotation(com.example.annotation.Log)")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        
        log.info("{}.{} 开始执行, 参数: {}", className, methodName, args);
        
        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();
            long end = System.currentTimeMillis();
            
            log.info("{}.{} 执行成功, 耗时: {}ms, 返回: {}", 
                className, methodName, end - start, result);
            
            return result;
        } catch (Exception e) {
            log.error("{}.{} 执行失败, 异常: {}", className, methodName, e.getMessage());
            throw e;
        }
    }
}

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

// 使用
@Service
public class UserService {
    @Log("添加用户")
    public void addUser(User user) {
        // 业务逻辑
    }
}

性能监控切面

java
@Aspect
@Component
public class PerformanceAspect {
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        try {
            return pjp.proceed();
        } finally {
            stopWatch.stop();
            long time = stopWatch.getTotalTimeMillis();
            
            if (time > 1000) {
                System.out.println("警告: 方法 " + methodName + " 执行时间过长: " + time + "ms");
            }
        }
    }
}

事务切面

java
@Aspect
@Component
public class TransactionAspect {
    
    @Around("@annotation(Transactional)")
    public Object transaction(ProceedingJoinPoint pjp) throws Throwable {
        // 开启事务
        System.out.println("开启事务");
        
        try {
            Object result = pjp.proceed();
            // 提交事务
            System.out.println("提交事务");
            return result;
        } catch (Exception e) {
            // 回滚事务
            System.out.println("回滚事务");
            throw e;
        }
    }
}

下一步

继续学习 JDBC数据访问,了解Spring的数据访问能力。