下面是详细的攻略。
什么是AOP?
AOP(Aspect Oriented Programming)是一种编程范式,主要思想是将程序中的横切关注点(Cross-Cutting Concerns)从业务逻辑模块中剥离出来,采用模块化的方式组合起来。
在实现AOP时,通常采用的方式是在程序运行时动态地将关注点与业务逻辑模块进行合并,以达到代码重用的目的。这主要是通过切面(Aspect)来实现。切面被定义为一组与业务逻辑关注点相关的通知(Advice)和切点(Pointcut)的组合。
什么是AspectJ?
AspectJ 是基于 Java 语言的 AOP 框架,它通过一系列的关键字和 API 提供了对 AOP 的支持。AspectJ 支持多种粒度的编程方式,包括将通知插入到方法调用之前、之后,甚至之中,可以让你很方便地使用切面编程思想来解决各种问题。
Spring使用AspectJ注解式实现AOP的步骤
- 引入Spring AOP的依赖
在pom.xml中加入以下代码:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
- 开启注解式AOP支持
在Spring的配置文件(例如xml版的applicationContext.xml)中加入以下代码,启动注解式AOP支持:
<aop:aspectj-autoproxy />
- 编写通知类
编写切面类,这里我们编写一个日志切面类,使用@Aspect注解标记,同时采用@Before和@AfterReturning注解定义通知:
@Aspect
public class LogAspect {
@Before("execution(* com.example.demo.controller.*.*(..))")
public void before(Controller controller) {
System.out.println("调用了" + controller.getClass().getName() + "中的方法");
}
@AfterReturning("execution(* com.example.demo.controller.*.*(..))")
public void afterReturning(Controller controller) {
System.out.println("成功调用了" + controller.getClass().getName() + "中的方法");
}
}
在这里,我们使用了@Before和@AfterReturning注解来分别定义“方法调用前”和“方法调用后”的操作。这里的"execution( com.example.demo.controller..*(..))"表示执行Controller包下所有类的所有方法。
- 声明切面类
在Spring配置文件中,使用
<bean id="logAspect" class="com.example.demo.aspect.LogAspect"/>
- 应用切面
在被应用的Bean类上使用@Aspect注解和@Component注解注释表示将它们与切面类结合起来:
@Controller
@Aspect
public class HelloController {
@RequestMapping("/hello")
@ResponseBody
public String hello() throws InterruptedException {
Thread.sleep(1000);
return "hello world";
}
}
在这里,我们使用了@Controller注解和@ResponseBody注解来处理HTTP请求,并且使用@Aspect注解来添加控制器切面。
- 运行程序
现在,我们就可以启动程序了,程序会启动一个Web服务器并运行指定的控制器。在访问http://localhost:8080/hello的过程中,会自动记录HelloController的执行日志。
示例1
上面是一个简单的AOP切面示例,下面我们来看一个更为复杂的示例:在Spring中使用AspectJ针对多个BEAN类型类进行性能统计。
- 首先,依然需要引入Spring AOP依赖和aspectjweaver依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
- 创建切面
创建一个切面来衡量应用程序中的方法的执行时间,为了简化示例,我们假设需要测量com.example.demo.service包下所有类的所有方法的执行时间,这里的切面名字是PerformanceTracker:
@Aspect
public class PerformanceTracker {
private static final Logger log = LoggerFactory.getLogger(PerformanceTracker.class);
private static final long ONE_MINUTE = 60 * 1000;
private ThreadLocal<Map<String, PerformaceStatistic>> performanceMap =
new ThreadLocal<Map<String, PerformaceStatistic>>() {
@Override protected Map<String, PerformaceStatistic> initialValue() {
return new HashMap<String, PerformaceStatistic>();
}
};
@Pointcut("execution(* com.example.demo.service..*(..))")
public void serviceMethods() {}
@Around("serviceMethods()")
public Object measureMethodPerformance(ProceedingJoinPoint jp) throws Throwable {
long start = System.currentTimeMillis();
try {
return jp.proceed();
} finally {
long end = System.currentTimeMillis();
long elapsedTime = end - start;
addToStatistic(jp, elapsedTime);
checkLongMethod(jp, elapsedTime);
log.trace("执行时间 (msec):" + elapsedTime);
}
}
private void addToStatistic(JoinPoint jp, long elapsedTime) {
String className = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
Map<String, PerformaceStatistic> map = performanceMap.get();
PerformaceStatistic stats = map.get(methodName);
if (stats == null) {
stats = new PerformaceStatistic();
stats.setClassName(className);
stats.setMethodName(methodName);
map.put(methodName, stats);
}
stats.setTotalTime(stats.getTotalTime() + elapsedTime);
stats.setInvocationCount(stats.getInvocationCount() + 1);
}
private void checkLongMethod(JoinPoint jp, long elapsedTime) {
if (elapsedTime > ONE_MINUTE) {
String className = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
log.warn("Long Running Method - " + className + "." + methodName + " (msec):" + elapsedTime);
}
}
public void printStatistic() {
Map<String, PerformaceStatistic> map = performanceMap.get();
for (String methodName : map.keySet()) {
PerformaceStatistic stats = map.get(methodName);
log.info(stats.getClassName() + "." + stats.getMethodName()
+ "() 方法执行时间: " + stats.getTotalTime()
+ " msec, 调用次数:" + stats.getInvocationCount());
}
}
private static class PerformaceStatistic {
private String className;
private String methodName;
private long totalTime;
private int invocationCount;
public String getClassName() { return className; }
public void setClassName(String className) { this.className = className; }
public String getMethodName() { return methodName; }
public void setMethodName(String methodName) { this.methodName = methodName; }
public long getTotalTime() { return totalTime; }
public void setTotalTime(long totalTime) { this.totalTime = totalTime; }
public int getInvocationCount() { return invocationCount; }
public void setInvocationCount(int invocationCount) { this.invocationCount = invocationCount; }
}
}
在这个切面中,我们定义了一个@Pointcut方法名叫做serviceMethods,它是用来描述需要跟踪的所有服务方法的:
@Pointcut("execution(* com.example.demo.service..*(..))")
public void serviceMethods() {}
该method定义了会被计时的方法的范围。因为AOP可能会在应用程序的不同层之间工作,它是拦截特定方法签名的一种有效方式。在这个例子中,我们只关心在应用程序服务层中执行的方法。
接着,我们使用了一个Around通知度量方法执行时间:
@Around("serviceMethods()")
public Object measureMethodPerformance(ProceedingJoinPoint jp) throws Throwable {
long start = System.currentTimeMillis();
try {
return jp.proceed();
} finally {
long end = System.currentTimeMillis();
long elapsedTime = end - start;
addToStatistic(jp, elapsedTime);
checkLongMethod(jp, elapsedTime);
log.trace("执行时间 (msec):" + elapsedTime);
}
}
这个通知测量方法执行时间并向统计信息中添加有关执行次数和累计时间的信息。在实现中,使用了一个Map来存储统计数据,它使用ThreadLocal类来确保这些数据不会被其他线程覆盖:
private ThreadLocal<Map<String, PerformaceStatistic>> performanceMap =
new ThreadLocal<Map<String, PerformaceStatistic>>() {
@Override
protected Map<String, PerformaceStatistic> initialValue() {
return new HashMap<String, PerformaceStatistic>();
}
};
最后,我们还添加了一个检查长时间运行的方法的方法。在该实现中,如果方法执行时间超过一分钟,它将向log文件记录警告信息:
private void checkLongMethod(JoinPoint jp, long elapsedTime) {
if (elapsedTime > ONE_MINUTE) {
String className = jp.getTarget().getClass().getName();
String methodName = jp.getSignature().getName();
log.warn("Long Running Method - " + className + "." + methodName + " (msec):" + elapsedTime);
}
}
- 启用切面
现在,我们可以像第一个示例中一样启用PerformanceTracker切面了:
<bean id="performanceTracker" class="com.example.demo.aspect.PerformanceTracker"/>
<aop:aspectj-autoproxy proxy-target-class="true" />
在这个spring配置中,我们声明一个PerformanceTracker bean,同时需要使用proxy-target-class参数告诉 aspectJ 使用 cglib 动态代理方式来创建代理对象,具体信息可参考AspectJ状态文章。
现在,如果我们运行这个应用程序并测试一些客户端请求,将在容器的日志系统中看到服务方法的性能统计信息。
结论
使用Spring AOP与AspectJ实现AOP,不仅代码简单、便利,而且提供了许多便捷的功能,在实践中展示了高效和强大的面向切面编程。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring使用AspectJ的注解式实现AOP面向切面编程 - Python技术站