AOP 支持
LCGYL Framework 提供轻量级的 AOP(面向切面编程)支持,基于 JDK 动态代理实现方法拦截。
什么是 AOP?
AOP 允许你在不修改原有代码的情况下,为方法添加横切关注点(如日志、事务、权限检查等)。
核心概念
- 切面(Aspect):横切关注点的模块化
- 连接点(Join Point):程序执行的某个点(如方法调用)
- 通知(Advice):在连接点执行的动作
- 切入点(Pointcut):匹配连接点的表达式
定义拦截器
基本拦截器
java
@Component
public class LoggingInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object[] args = invocation.getArguments();
// 方法执行前
logger.info("调用方法: {},参数: {}", methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis();
try {
// 执行原方法
Object result = invocation.proceed();
// 方法执行后
long duration = System.currentTimeMillis() - startTime;
logger.info("方法 {} 执行完成,耗时: {}ms,返回: {}", methodName, duration, result);
return result;
} catch (Throwable e) {
// 异常处理
logger.error("方法 {} 执行异常: {}", methodName, e.getMessage());
throw e;
}
}
}使用注解定义
java
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
logger.info("调用方法: {}", joinPoint.getSignature().getName());
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
logger.info("方法执行完成: {}", joinPoint.getSignature().getName());
}
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
logger.info("方法 {} 耗时: {}ms", joinPoint.getSignature().getName(), duration);
return result;
}
}通知类型
@Before - 前置通知
方法执行前调用。
java
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(RequiresPermission)")
public void checkPermission(JoinPoint joinPoint) {
// 检查权限
User user = SecurityContext.getCurrentUser();
if (user == null) {
throw new UnauthorizedException("用户未登录");
}
RequiresPermission annotation = getAnnotation(joinPoint, RequiresPermission.class);
if (!user.hasPermission(annotation.value())) {
throw new ForbiddenException("权限不足");
}
}
}@After - 后置通知
方法执行后调用(无论是否异常)。
java
@Aspect
@Component
public class ResourceAspect {
@After("execution(* com.example.service.*.*(..))")
public void releaseResource(JoinPoint joinPoint) {
// 释放资源
ResourceHolder.release();
}
}@AfterReturning - 返回通知
方法正常返回后调用。
java
@Aspect
@Component
public class AuditAspect {
@AfterReturning(
pointcut = "execution(* com.example.service.UserService.create*(..))",
returning = "result"
)
public void auditCreate(JoinPoint joinPoint, Object result) {
// 记录审计日志
auditService.log("CREATE", result);
}
}@AfterThrowing - 异常通知
方法抛出异常后调用。
java
@Aspect
@Component
public class ExceptionAspect {
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void handleException(JoinPoint joinPoint, Throwable ex) {
// 记录异常
logger.error("方法 {} 抛出异常: {}",
joinPoint.getSignature().getName(),
ex.getMessage());
// 发送告警
alertService.sendAlert(ex);
}
}@Around - 环绕通知
完全控制方法执行。
java
@Aspect
@Component
public class TransactionAspect {
@Around("@annotation(Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
Transaction tx = transactionManager.begin();
try {
Object result = joinPoint.proceed();
tx.commit();
return result;
} catch (Throwable e) {
tx.rollback();
throw e;
}
}
}切入点表达式
execution 表达式
匹配方法执行。
java
// 匹配所有 public 方法
@Pointcut("execution(public * *(..))")
// 匹配 UserService 的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
// 匹配 service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配 service 包及子包下所有类的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
// 匹配返回 User 的方法
@Pointcut("execution(User com.example.service.*.*(..))")
// 匹配第一个参数为 Long 的方法
@Pointcut("execution(* com.example.service.*.*(Long, ..))")@annotation 表达式
匹配带有指定注解的方法。
java
// 匹配带有 @Transactional 注解的方法
@Pointcut("@annotation(com.example.annotation.Transactional)")
// 匹配带有 @Cacheable 注解的方法
@Pointcut("@annotation(Cacheable)")within 表达式
匹配指定类型内的方法。
java
// 匹配 UserService 内的所有方法
@Pointcut("within(com.example.service.UserService)")
// 匹配 service 包下所有类的方法
@Pointcut("within(com.example.service.*)")组合表达式
java
// AND
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(Transactional)")
// OR
@Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.repository.*.*(..))")
// NOT
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")自定义注解
定义注解
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
String value() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
int maxAttempts() default 3;
long delay() default 1000;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cached {
String key() default "";
long ttl() default 3600;
}使用注解
java
@Component
public class UserService {
@Timed("user.findById")
public User findById(Long id) {
return userRepository.findById(id);
}
@Retry(maxAttempts = 3, delay = 1000)
public void sendEmail(User user) {
emailService.send(user.getEmail(), "Welcome!");
}
@Cached(key = "user:list", ttl = 300)
public List<User> findAll() {
return userRepository.findAll();
}
}实现切面
java
@Aspect
@Component
public class TimedAspect {
@Around("@annotation(timed)")
public Object measureTime(ProceedingJoinPoint joinPoint, Timed timed) throws Throwable {
String name = timed.value().isEmpty()
? joinPoint.getSignature().getName()
: timed.value();
long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
metricsService.recordTiming(name, duration);
}
}
}
@Aspect
@Component
public class RetryAspect {
@Around("@annotation(retry)")
public Object retry(ProceedingJoinPoint joinPoint, Retry retry) throws Throwable {
int attempts = 0;
Throwable lastException = null;
while (attempts < retry.maxAttempts()) {
try {
return joinPoint.proceed();
} catch (Throwable e) {
lastException = e;
attempts++;
if (attempts < retry.maxAttempts()) {
Thread.sleep(retry.delay());
}
}
}
throw lastException;
}
}
@Aspect
@Component
public class CachedAspect {
@Inject
private CacheService cacheService;
@Around("@annotation(cached)")
public Object cache(ProceedingJoinPoint joinPoint, Cached cached) throws Throwable {
String key = cached.key().isEmpty()
? generateKey(joinPoint)
: cached.key();
// 尝试从缓存获取
Object cachedValue = cacheService.get(key);
if (cachedValue != null) {
return cachedValue;
}
// 执行方法
Object result = joinPoint.proceed();
// 存入缓存
cacheService.put(key, result, cached.ttl());
return result;
}
}拦截器链
多个拦截器
java
@Component
@Order(1)
public class SecurityInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 安全检查
checkSecurity();
return invocation.proceed();
}
}
@Component
@Order(2)
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 日志记录
log(invocation);
return invocation.proceed();
}
}
@Component
@Order(3)
public class TransactionInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 事务管理
return executeInTransaction(invocation);
}
}执行顺序
请求 → SecurityInterceptor → LoggingInterceptor → TransactionInterceptor → 目标方法
响应 ← SecurityInterceptor ← LoggingInterceptor ← TransactionInterceptor ← 目标方法代理工厂
创建代理
java
// 创建代理工厂
ProxyFactory factory = new ProxyFactory();
factory.setTarget(new UserServiceImpl());
factory.addInterceptor(new LoggingInterceptor());
factory.addInterceptor(new TransactionInterceptor());
// 获取代理对象
UserService proxy = factory.getProxy();
// 调用方法(会经过拦截器)
proxy.createUser(user);JDK 动态代理
java
// 基于接口的代理
public interface UserService {
User findById(Long id);
}
@Component
public class UserServiceImpl implements UserService {
@Override
public User findById(Long id) {
return userRepository.findById(id);
}
}
// 自动创建 JDK 动态代理实际应用场景
1. 日志记录
java
@Aspect
@Component
public class MethodLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(MethodLoggingAspect.class);
@Around("execution(* com.example.service.*.*(..))")
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.debug("[{}#{}] 开始执行,参数: {}", className, methodName, Arrays.toString(args));
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
logger.debug("[{}#{}] 执行完成,耗时: {}ms,返回: {}", className, methodName, duration, result);
return result;
} catch (Throwable e) {
long duration = System.currentTimeMillis() - start;
logger.error("[{}#{}] 执行异常,耗时: {}ms,异常: {}", className, methodName, duration, e.getMessage());
throw e;
}
}
}2. 性能监控
java
@Aspect
@Component
public class PerformanceAspect {
@Inject
private MetricsService metricsService;
@Around("@annotation(Monitored)")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
String metricName = joinPoint.getSignature().toShortString();
long start = System.nanoTime();
try {
Object result = joinPoint.proceed();
metricsService.recordSuccess(metricName, System.nanoTime() - start);
return result;
} catch (Throwable e) {
metricsService.recordFailure(metricName, System.nanoTime() - start);
throw e;
}
}
}3. 权限控制
java
@Aspect
@Component
public class AuthorizationAspect {
@Before("@annotation(requiresRole)")
public void checkRole(JoinPoint joinPoint, RequiresRole requiresRole) {
User user = SecurityContext.getCurrentUser();
if (user == null) {
throw new UnauthorizedException("请先登录");
}
String[] requiredRoles = requiresRole.value();
boolean hasRole = Arrays.stream(requiredRoles)
.anyMatch(user::hasRole);
if (!hasRole) {
throw new ForbiddenException("权限不足,需要角色: " + Arrays.toString(requiredRoles));
}
}
}4. 缓存管理
java
@Aspect
@Component
public class CacheAspect {
@Inject
private CacheManager cacheManager;
@Around("@annotation(cacheable)")
public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String cacheName = cacheable.value();
String key = generateKey(joinPoint);
Cache cache = cacheManager.getCache(cacheName);
// 查询缓存
Object cached = cache.get(key);
if (cached != null) {
return cached;
}
// 执行方法
Object result = joinPoint.proceed();
// 存入缓存
cache.put(key, result);
return result;
}
@After("@annotation(cacheEvict)")
public void evictCache(JoinPoint joinPoint, CacheEvict cacheEvict) {
String cacheName = cacheEvict.value();
Cache cache = cacheManager.getCache(cacheName);
if (cacheEvict.allEntries()) {
cache.clear();
} else {
String key = generateKey(joinPoint);
cache.evict(key);
}
}
}5. 事务管理
java
@Aspect
@Component
public class TransactionalAspect {
@Inject
private TransactionManager transactionManager;
@Around("@annotation(transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint, Transactional transactional) throws Throwable {
TransactionDefinition definition = new TransactionDefinition();
definition.setPropagation(transactional.propagation());
definition.setIsolation(transactional.isolation());
definition.setReadOnly(transactional.readOnly());
TransactionStatus status = transactionManager.getTransaction(definition);
try {
Object result = joinPoint.proceed();
transactionManager.commit(status);
return result;
} catch (Throwable e) {
if (shouldRollback(e, transactional)) {
transactionManager.rollback(status);
} else {
transactionManager.commit(status);
}
throw e;
}
}
}最佳实践
1. 保持切面简单
java
// ✅ 推荐:简单的切面
@Aspect
@Component
public class SimpleLoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
logger.info("调用: {}", joinPoint.getSignature().getName());
}
}
// ❌ 不推荐:复杂的切面
@Aspect
@Component
public class ComplexAspect {
// 太多逻辑,难以维护
}2. 使用自定义注解
java
// ✅ 推荐:使用注解标记
@Timed
public User findById(Long id) {
return userRepository.findById(id);
}
// ❌ 不推荐:硬编码切入点
@Pointcut("execution(* com.example.service.UserService.findById(..))")3. 注意性能影响
java
// ✅ 推荐:只在需要的地方使用
@Around("@annotation(Timed)")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
// ...
}
// ❌ 不推荐:拦截所有方法
@Around("execution(* *.*(..))")
public Object interceptAll(ProceedingJoinPoint joinPoint) throws Throwable {
// 性能影响大
}4. 处理异常
java
@Around("@annotation(Retry)")
public Object retry(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (Throwable e) {
// 正确处理异常
logger.error("执行失败", e);
throw e; // 重新抛出
}
}5. 避免循环调用
java
// ❌ 不推荐:可能导致循环
@Aspect
@Component
public class BadAspect {
@Inject
private SomeService someService; // 这个服务也被切面拦截
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
someService.doSomething(); // 可能导致循环
return joinPoint.proceed();
}
}常见问题
Q: AOP 对性能有影响吗?
A: 有轻微影响,但通常可以忽略。建议:
- 只在需要的地方使用
- 避免在高频方法上使用复杂切面
- 使用
@annotation而不是execution(*.*(..))
Q: 为什么我的切面没有生效?
A: 检查以下几点:
- 切面类是否有
@Aspect和@Component注解 - 切入点表达式是否正确
- 目标类是否是容器管理的组件
- 是否是自调用(同一个类内部调用)
Q: 如何调试切面?
A:
properties
logging.level.com.lcgyl.framework.core.aop=DEBUG