一、AOP核心概念:理解横切关注点
1.1 什么是AOP?
面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是指那些影响多个模块的功能,如日志记录、事务管理、安全控制等。
传统编程 vs AOP编程:
// 传统方式:业务逻辑与横切关注点混合
public class UserService {
public void createUser(User user) {
// 事务开始
Transaction transaction = beginTransaction();
try {
// 权限检查
if (!hasPermission()) {
throw new SecurityException("No permission");
}
// 业务逻辑
userRepository.save(user);
auditService.logAction("CREATE_USER");
// 事务提交
transaction.commit();
} catch (Exception e) {
// 事务回滚
transaction.rollback();
throw e;
}
}
}
// AOP方式:业务逻辑保持纯净
public class UserService {
public void createUser(User user) {
// 纯粹的業務邏輯
userRepository.save(user);
}
}
1.2 AOP核心术语
让我们通过一个比喻来理解AOP的核心概念: imagine一个餐厅厨房作为我们的业务系统

对应到AOP术语:
- 连接点 (Join Point):厨房中的各个工作点(接单、烹饪、装盘)
- 切点 (Pointcut):选择在哪些工作点进行操作(如"所有烹饪过程")
- 通知 (Advice):在特定工作点执行的操作(如记录日志、检查库存)
- 切面 (Aspect):食品安全管理系统(包含所有横切关注点)
- 目标对象 (Target Object):厨师(执行核心业务逻辑的对象)
- AOP代理 (AOP Proxy):厨房经理(增强厨师工作的代理)
二、Spring AOP实现机制
2.1 代理模式:Spring AOP的基石
Spring AOP使用代理模式实现AOP功能,主要有两种代理方式:
// 接口代理 - JDK动态代理
public interface UserService {
void createUser(User user);
}
public class UserServiceImpl implements UserService {
public void createUser(User user) {
// 业务实现
}
}
// 类代理 - CGLIB字节码增强
public class UserService {
public void createUser(User user) {
// 业务实现
}
}
// Spring自动选择代理方式
@Configuration
@EnableAspectJAutoProxy // 启用AOP自动代理
public class AppConfig {
}
2.2 AOP配置的三种方式
2.2.1 XML配置方式(传统)
<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义Bean -->
<bean id="userService" class="com.example.UserService"/>
<bean id="loggingAspect" class="com.example.LoggingAspect"/>
<!-- AOP配置 -->
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut id="serviceMethods"
expression="execution(* com.example.*Service.*(..))"/>
<aop:before pointcut-ref="serviceMethods" method="logBefore"/>
<aop:after-returning pointcut-ref="serviceMethods" method="logAfterReturning"/>
</aop:aspect>
</aop:config>
</beans>
2.2.2 注解配置方式(推荐)
// 启用AspectJ自动代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example")
public class AppConfig {
}
// 定义切面
@Aspect
@Component
public class LoggingAspect {
// 定义切点:匹配所有Service层方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 前置通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("方法执行前: " + methodName + ", 参数: " + Arrays.toString(args));
}
// 返回后通知
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法执行完成: " + methodName + ", 返回值: " + result);
}
}
2.2.3 编程方式(高级)
@Configuration
@EnableAspectJAutoProxy
public class ProgrammaticAopConfig implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof UserService) {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(bean);
// 添加通知
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before method: " + method.getName());
}
});
return proxyFactory.getProxy();
}
return bean;
}
}
三、五种通知类型详解
3.1 前置通知(@Before)
@Aspect
@Component
public class SecurityAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods() && args(user,..)")
public void checkPermission(JoinPoint joinPoint, User user) {
String methodName = joinPoint.getSignature().getName();
if (!user.hasPermission(methodName)) {
throw new SecurityException("用户没有执行 " + methodName + " 的权限");
}
System.out.println("权限检查通过: " + methodName);
}
}
3.2 后置通知(@AfterReturning)
@Aspect
@Component
public class MonitoringAspect {
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result"
)
public void monitorPerformance(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法 " + methodName + " 执行成功,返回值: " + result);
// 可以在这里记录性能指标、监控数据等
Metrics.recordSuccess(methodName);
}
}
3.3 异常通知(@AfterThrowing)
@Aspect
@Component
public class ExceptionHandlingAspect {
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void handleException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法 " + methodName + " 执行异常: " + ex.getMessage());
// 异常处理逻辑:记录日志、发送警报等
ErrorReporter.reportError(methodName, ex);
// 可以根据异常类型进行不同的处理
if (ex instanceof DatabaseException) {
// 数据库异常特殊处理
DatabaseHealthChecker.checkStatus();
}
}
}
3.4 最终通知(@After)
@Aspect
@Component
public class ResourceCleanupAspect {
@After("execution(* com.example.service.*.*(..))")
public void cleanupResources(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法 " + methodName + " 执行完成,进行资源清理");
// 清理资源:关闭文件、数据库连接等
ResourceManager.cleanup();
// 重置线程局部变量
ThreadLocalManager.reset();
}
}
3.5 环绕通知(@Around)- 最强大的通知类型
@Aspect
@Component
public class TransactionAspect {
@Autowired
private PlatformTransactionManager transactionManager;
@Around("execution(* com.example.service.*Service.*(..))")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
// 创建事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName(methodName);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 开始事务
TransactionStatus status = transactionManager.getTransaction(def);
System.out.println("开始事务: " + methodName);
try {
// 执行目标方法
Object result = joinPoint.proceed();
// 提交事务
transactionManager.commit(status);
System.out.println("事务提交: " + methodName);
return result;
} catch (Exception ex) {
// 回滚事务
transactionManager.rollback(status);
System.out.println("事务回滚: " + methodName + ", 原因: " + ex.getMessage());
// 重新抛出异常
throw ex;
}
}
}
四、切点表达式语言详解
4.1 常用切点表达式
Spring AOP使用AspectJ切点表达式语言来定义切点:
@Aspect
@Component
public class PointcutExamplesAspect {
// 1. 匹配所有public方法
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {}
// 2. 匹配指定包下的所有方法
@Pointcut("within(com.example.service..*)")
public void inServicePackage() {}
// 3. 匹配实现了特定接口的类
@Pointcut("this(com.example.service.UserService)")
public void implementsUserService() {}
// 4. 匹配带有特定注解的方法
@Pointcut("@annotation(com.example.annotation.Logged)")
public void annotatedWithLogged() {}
// 5. 匹配带有特定注解的参数
@Pointcut("@args(com.example.annotation.Validated)")
public void hasValidatedParameter() {}
// 6. 匹配Bean名称
@Pointcut("bean(userService) || bean(orderService)")
public void specificBeans() {}
// 7. 组合切点
@Pointcut("inServicePackage() && anyPublicMethod()")
public void publicServiceMethods() {}
}
4.2 复杂切点表达式示例
@Aspect
@Component
public class ComplexPointcutAspect {
// 匹配Service层中以find开头的方法,且第一个参数为Long类型
@Pointcut("execution(* com.example.service.*.find*(Long, ..))")
public void findMethodsWithLongId() {}
// 匹配带有@Transactional注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
// 匹配执行时间超过100ms的方法
@Around("transactionalMethods()")
public Object monitorSlowMethods(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long elapsedTime = System.currentTimeMillis() - startTime;
if (elapsedTime > 100) {
String methodName = joinPoint.getSignature().getName();
System.out.println("慢方法警告: " + methodName + " 执行了 " + elapsedTime + "ms");
}
}
}
}
五、实战案例:完整的AOP应用
5.1 性能监控切面
@Aspect
@Component
public class PerformanceMonitoringAspect {
private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringAspect.class);
private final ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("execution(* com.example.service..*(..)) || " +
"execution(* com.example.controller..*(..))")
public void monitorableMethods() {}
@Around("monitorableMethods()")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
// 记录开始时间
startTime.set(System.currentTimeMillis());
try {
// 执行目标方法
return joinPoint.proceed();
} finally {
// 计算执行时间
long elapsedTime = System.currentTimeMillis() - startTime.get();
String methodName = joinPoint.getSignature().toShortString();
// 记录性能数据
if (elapsedTime > 500) {
logger.warn("方法 {} 执行缓慢: {}ms", methodName, elapsedTime);
} else {
logger.debug("方法 {} 执行时间: {}ms", methodName, elapsedTime);
}
// 清理ThreadLocal
startTime.remove();
}
}
}
5.2 缓存切面
@Aspect
@Component
public class CachingAspect {
@Autowired
private CacheManager cacheManager;
@Pointcut("@annotation(com.example.annotation.Cacheable)")
public void cacheableMethods() {}
@Around("cacheableMethods()")
public Object handleCache(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取缓存注解
Cacheable cacheable = method.getAnnotation(Cacheable.class);
String cacheName = cacheable.value();
long ttl = cacheable.ttl();
// 生成缓存键
String cacheKey = generateCacheKey(joinPoint);
// 检查缓存
Cache cache = cacheManager.getCache(cacheName);
Cache.ValueWrapper cachedValue = cache.get(cacheKey);
if (cachedValue != null) {
// 缓存命中
return cachedValue.get();
}
// 缓存未命中,执行方法
Object result = joinPoint.proceed();
// 缓存结果
if (result != null) {
cache.put(cacheKey, result);
}
return result;
}
private String generateCacheKey(ProceedingJoinPoint joinPoint) {
// 基于方法签名和参数生成唯一的缓存键
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getName();
Object[] args = joinPoint.getArgs();
return methodName + ":" + Arrays.deepHashCode(args);
}
}
5.3 自定义注解与AOP结合
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
String value() default "";
boolean logParameters() default true;
boolean logResult() default false;
}
// 对应的切面
@Aspect
@Component
public class LogExecutionAspect {
private static final Logger logger = LoggerFactory.getLogger(LogExecutionAspect.class);
@Around("@annotation(logExecution)")
public Object logMethodExecution(ProceedingJoinPoint joinPoint, LogExecution logExecution) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String customMessage = logExecution.value();
// 记录方法开始
if (logExecution.logParameters()) {
logger.info("开始执行 {}{},参数: {}",
customMessage.isEmpty() ? "" : customMessage + " - ",
methodName, Arrays.toString(joinPoint.getArgs()));
} else {
logger.info("开始执行 {}{}",
customMessage.isEmpty() ? "" : customMessage + " - ",
methodName);
}
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
// 记录方法完成
if (logExecution.logResult()) {
logger.info("完成执行 {}{},耗时: {}ms,结果: {}",
customMessage.isEmpty() ? "" : customMessage + " - ",
methodName, elapsedTime, result);
} else {
logger.info("完成执行 {}{},耗时: {}ms",
customMessage.isEmpty() ? "" : customMessage + " - ",
methodName, elapsedTime);
}
return result;
} catch (Exception ex) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.error("执行 {}{} 失败,耗时: {}ms,异常: {}",
customMessage.isEmpty() ? "" : customMessage + " - ",
methodName, elapsedTime, ex.getMessage(), ex);
throw ex;
}
}
}
// 使用自定义注解
@Service
public class UserService {
@LogExecution(value = "用户创建", logParameters = true, logResult = false)
public User createUser(User user) {
// 业务逻辑
return userRepository.save(user);
}
@LogExecution("用户查询")
public User getUserById(Long id) {
return userRepository.findById(id);
}
}
六、Spring AOP最佳实践与陷阱避免
6.1 最佳实践
- 合理选择通知类型
// 优先使用环绕通知处理复杂逻辑
@Around("serviceMethods()")
public Object handleComplexLogic(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置处理
preProcess();
try {
// 执行目标方法
Object result = joinPoint.proceed();
// 后置处理
postProcess(result);
return result;
} catch (Exception ex) {
// 异常处理
handleException(ex);
throw ex;
} finally {
// 最终处理
cleanup();
}
}
- 优化切点表达式性能
// 避免过于复杂的切点表达式
@Pointcut("execution(public * com.example.service..*(..)) && " +
"!execution(* com.example.service.internal..*(..))")
public void publicServiceMethodsExcludingInternal() {}
// 缓存切点评估结果
@Pointcut("execution(* *(..)) && args(arg) && @annotation(annotation)")
public void complexPointcut(Object arg, SomeAnnotation annotation) {}
- 正确处理异常
@Around("serviceMethods()")
public Object handleExceptions(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (BusinessException ex) {
// 业务异常,记录日志但不包装
logger.warn("业务异常: {}", ex.getMessage());
throw ex;
} catch (TechnicalException ex) {
// 技术异常,记录错误并包装
logger.error("技术异常", ex);
throw new RuntimeException("系统错误,请稍后重试", ex);
}
}
6.2 常见陷阱与解决方案
- 陷阱:内部方法调用导致AOP失效
@Service
public class UserService {
public void createUser(User user) {
// 内部方法调用,AOP不会生效
validateUser(user); // 不会触发AOP通知
}
@LogExecution
public void validateUser(User user) {
// 验证逻辑
}
}
// 解决方案:使用AopContext.currentProxy()
@Service
public class UserService {
@Autowired
private ApplicationContext context;
public void createUser(User user) {
// 从容器中获取代理对象
UserService proxy = context.getBean(UserService.class);
proxy.validateUser(user); // 会触发AOP通知
}
}
- 陷阱:循环依赖问题
@Aspect
@Component
public class ProblematicAspect {
@Autowired
private UserService userService; // 可能导致循环依赖
}
// 解决方案:使用setter注入或@Lazy
@Aspect
@Component
public class FixedAspect {
private UserService userService;
@Autowired
@Lazy // 延迟注入解决循环依赖
public void setUserService(UserService userService) {
this.userService = userService;
}
}
七、Spring AOP与AspectJ对比
7.1 功能对比
|
特性 |
Spring AOP |
AspectJ |
|
实现方式 |
运行时代理 |
编译时/加载时织入 |
|
性能 |
较好(运行时开销) |
优秀(无运行时开销) |
|
连接点支持 |
仅方法执行 |
方法执行、构造器调用、字段访问等 |
|
织入时机 |
运行时 |
编译时、后编译时、加载时 |
|
依赖 |
轻量,仅需Spring |
需要AspectJ编译器/织入器 |
|
学习曲线 |
平缓 |
陡峭 |
7.2 选择建议
- 选择Spring AOP:大多数企业应用,需要简单的AOP功能
- 选择AspectJ:需要高性能、复杂织入、非方法级别拦截的场景
// Spring AOP与AspectJ结合使用
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableLoadTimeWeaving // 启用加载时织入
public class HybridAopConfig {
}
// 使用AspectJ注解定义更强大的切面
@Aspect
public class PowerfulAspect {
// 构造器执行切点(仅AspectJ支持)
@Pointcut("execution(com.example.User.new(..))")
public void userConstructor() {}
// 字段访问切点(仅AspectJ支持)
@Pointcut("get(* com.example.User.name)")
public void userNameAccess() {}
@Before("userConstructor()")
public void beforeUserConstruction() {
System.out.println("User对象即将被创建");
}
}
总结
Spring AOP是Spring框架的核心功能之一,它通过代理模式实现了强大的面向切面编程能力。通过合理使用AOP,我们可以:
- 分离关注点:将横切逻辑与业务逻辑分离,提高代码可维护性
- 减少重复代码:通过切面统一处理通用功能,如日志、事务、安全等
- 提高灵活性:通过配置即可添加或移除功能,无需修改业务代码
- 增强可测试性:业务逻辑更加纯净,便于单元测试
最佳实践总结:
- 优先使用注解配置方式
- 合理选择通知类型,环绕通知最强大但也要慎用
- 优化切点表达式性能,避免过于复杂的匹配
- 注意内部方法调用和循环依赖问题
- 根据需求选择合适的AOP实现(Spring AOP vs AspectJ)
通过掌握Spring AOP,开发者可以构建出更加模块化、可维护和可扩展的应用程序,真正实现关注点分离的设计理念。
427

被折叠的 条评论
为什么被折叠?



