Spring使用AspectJ的注解式实现AOP面向切面编程

下面是详细的攻略。

什么是AOP?

AOP(Aspect Oriented Programming)是一种编程范式,主要思想是将程序中的横切关注点(Cross-Cutting Concerns)从业务逻辑模块中剥离出来,采用模块化的方式组合起来。

在实现AOP时,通常采用的方式是在程序运行时动态地将关注点与业务逻辑模块进行合并,以达到代码重用的目的。这主要是通过切面(Aspect)来实现。切面被定义为一组与业务逻辑关注点相关的通知(Advice)和切点(Pointcut)的组合。

什么是AspectJ?

AspectJ 是基于 Java 语言的 AOP 框架,它通过一系列的关键字和 API 提供了对 AOP 的支持。AspectJ 支持多种粒度的编程方式,包括将通知插入到方法调用之前、之后,甚至之中,可以让你很方便地使用切面编程思想来解决各种问题。

Spring使用AspectJ注解式实现AOP的步骤

  1. 引入Spring AOP的依赖

在pom.xml中加入以下代码:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.8.RELEASE</version>
</dependency>
  1. 开启注解式AOP支持

在Spring的配置文件(例如xml版的applicationContext.xml)中加入以下代码,启动注解式AOP支持:

<aop:aspectj-autoproxy />
  1. 编写通知类

编写切面类,这里我们编写一个日志切面类,使用@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包下所有类的所有方法。

  1. 声明切面类

在Spring配置文件中,使用标签声明切面类:

<bean id="logAspect" class="com.example.demo.aspect.LogAspect"/>
  1. 应用切面

在被应用的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注解来添加控制器切面。

  1. 运行程序

现在,我们就可以启动程序了,程序会启动一个Web服务器并运行指定的控制器。在访问http://localhost:8080/hello的过程中,会自动记录HelloController的执行日志。

示例1

上面是一个简单的AOP切面示例,下面我们来看一个更为复杂的示例:在Spring中使用AspectJ针对多个BEAN类型类进行性能统计。

  1. 首先,依然需要引入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>
  1. 创建切面

创建一个切面来衡量应用程序中的方法的执行时间,为了简化示例,我们假设需要测量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);
    }
}
  1. 启用切面

现在,我们可以像第一个示例中一样启用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技术站

(0)
上一篇 2023年5月30日
下一篇 2023年5月30日

相关文章

  • Android开发自学笔记(二):工程文件剖析

    Android开发自学笔记(二):工程文件剖析 本篇文章主要介绍 Android 工程的文件结构和主要文件作用,加深对 Android 工程的理解。 工程文件结构 Android 工程一般都有以下文件/文件夹: . ├── libs ├── src │ ├── androidTest │ ├── main │ └── test ├── build.gradl…

    html 2023年5月31日
    00
  • jsp实现将信息放入xml中的方法

    首先,我们需要了解jsp中实现将信息放入xml中的方法。在jsp中,我们可以使用JDOM或者DOM4J等库来实现将信息放入xml文件中的操作,接下来,我们会介绍使用JDOM库的方法。 步骤一:引入JDOM库在jsp文件中,我们需要引入JDOM库,通常可以通过将相应的jar包放入Web项目的lib文件夹中实现。以下是引入jdom2.0.6.jar的示例代码: …

    html 2023年5月30日
    00
  • HTML页面插入SVG的多种方式

    插入SVG图像到HTML页面中有多种方式,下面将为大家介绍其中的几种。 1. 直接插入SVG标签 在HTML页面中,我们可以直接使用SVG标签来插入SVG图像。示例代码如下: <svg xmlns="http://www.w3.org/2000/svg" width="200" height="100&…

    html 2023年5月30日
    00
  • hbuilderx怎么折叠其他项目?hbuilderx折叠其他项目方法

    以下是关于HBuilderX折叠其他项目的攻略: HBuilderX怎么折叠其他项目? HBuilderX是一款跨平台的前端开发工具,支持多种编程语言和框架。在HBuilderX中,您可以通过折叠其他项目来更好地管理您的项目。以下是详细的攻略: 打开项目:首先,打开您的项目,进入项目文件夹。 折叠其他项目:在项目文件夹中,右键单击要折叠的项目,然后选择“折叠…

    html 2023年5月17日
    00
  • hbuilderx怎么切换纯净模式?hbuilderx切换纯净模式方法

    HBuilderX怎么切换纯净模式? HBuilderX是一款跨平台的前端开发工具,支持多种编程语言和框架。如果您需要切换HBuilderX的纯净模式,可以按照以下步骤操作: 打开HBuilderX:首先,打开HBuilderX开发工具。 进入设置页面:在HBuilderX的主界面中,点击左下角的“设置”按钮,进入设置页面。 进入编辑器设置:在设置页面中,选…

    html 2023年5月17日
    00
  • iis伪静态中文url出现乱码的解决办法

    以下是关于”iis伪静态中文url出现乱码的解决办法”的详细攻略: 背景 IIS是Microsoft开发的Web服务器程序,为Windows操作系统提供Web服务。伪静态是当接收到请求后,通过URL Rewrite将参数重写成带有扩展名的静态URL,并且让用户认为它是静态的。而中文URL出现乱码的问题,则是因为IIS默认以Unicode编码的方式处理URL。…

    html 2023年5月31日
    00
  • C#(4.0)不常见的语法

    我们来详细讲解一下“C#(4.0)不常见的语法”的完整攻略。 1. Tuples(元组) Tuple是一个有序的、不可更改的、可以包含不同类型值的集合。 例如: var tupleExample = (1, "string", true); 使用tuple时,可以通过索引获取元素的值,如: var value1 = tupleExampl…

    html 2023年5月30日
    00
  • springboot页面国际化配置指南

    下面我将详细讲解“Spring Boot 页面国际化配置指南”的完整攻略。 前言 在当前的全球化时代,应用程序需要支持多种语言和文化,因此国际化已成为开发项目的一个重要特性。Spring Boot 提供了一种非常方便的方式来实现页面国际化,本文将介绍如何在 Spring Boot 中配置页面国际化,帮助开发者更好地支持不同语言和文化环境。 步骤 1. 创建资…

    html 2023年5月30日
    00
合作推广
合作推广
分享本页
返回顶部