Spring AOP与实践指南
什么是AOP?
面向切面编程(Aspect-Oriented Programming)是一种通过预编译方式和运行期动态代理实现程序功能增强的技术。它与OOP形成互补关系,专注于横切关注点(如日志记录、事务管理等)的模块化处理。
核心概念解析:
- 切面(Aspect):封装横切逻辑的模块化单元
- 连接点(Join Point):程序执行过程中的特定节点(方法调用、异常抛出等)
- 切入点(Pointcut):定义在哪些连接点应用通知
- 通知(Advice):在特定连接点执行的动作
- 目标对象(Target Object):被代理的原始对象
- 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 {}
正文到此结束
相关文章
热门推荐
评论插件初始化中...