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日

相关文章

  • Visual Studio IDE 实用小技巧(附打包下载)

    Visual Studio IDE 实用小技巧攻略 Visual Studio IDE 是微软公司针对 Windows 开发的一款集成开发环境,提供了多种语言的开发工具和丰富的插件扩展,可满足各种开发需求。本文将针对 Visual Studio IDE 的实用小技巧,为您详细讲解其使用方法。 1. 快速查找定义 在 Visual Studio IDE 中,快…

    html 2023年5月30日
    00
  • 口袋妖怪go中国怎么下载 pokemongo下载攻略

    以下是口袋妖怪Go中国版下载攻略: 下载安装应用市场:由于口袋妖怪Go中国版未在官方应用商店上架,因此需要下载安装第三方应用市场。您可以在网上搜索“应用市场下载”,选择一个可靠的应用市场进行下载和安装。 在应用市场中搜索下载:在安装好应用市场后,打开应用市场并搜索“口袋妖怪Go中国版”,选择一个可靠的下载链接进行下载和安装。 安装并登录:下载完成后,打开应用…

    html 2023年5月17日
    00
  • MyBatis typeAliases元素标签(含注解方式)及其属性、设置方式

    MyBatis提供了一种类型别名机制,用于简化mapper.xml中引用Java类型的过程。typeAliases元素标签是用来设置类型别名的,它具有如下属性: alias:指定一个别名,要求唯一。 type:指定要别名化的Java类的全限定类名。 typeAliases可以在mapper.xml文件中所在的顶层的mybatis-config.xml文件中设…

    html 2023年5月30日
    00
  • RTX4070ti相当于30系什么显卡?

    以下是“RTX4070ti相当于30系什么显卡?”的完整攻略: RTX4070ti相当于30系什么显卡? RTX4070ti是NVIDIA公司推出的一款高性能显卡,它采用了最新的Ampere架构,拥有强大的性能和先进的功能。以下是关于RTX4070ti相当于30系什么显卡的详细攻略。 RTX4070ti相当于30系什么显卡? RTX4070ti相当于30系的…

    html 2023年5月18日
    00
  • Win10中Edge浏览器下载出现乱码该怎么办?

    当Win10中Edge浏览器下载出现乱码时,我们可以通过以下步骤来解决: Step 1: 确认浏览器和操作系统语言设置 浏览器和操作系统的语言设置不一致,可能会导致在下载过程中出现乱码的情况。因此,我们需要确保两者的语言设置一致。 在Win10系统中,打开“设置”。 点击“时间和语言”选项。 点击“区域和语言”选项。 确认“语言”选项中的语言设置。 同样的,…

    html 2023年5月31日
    00
  • 苹果iPhone快捷指令怎么用?IOS13快捷指令使用技巧图文介绍

    苹果iPhone快捷指令是一款强大的自动化工具,可以帮助用户快速执行一些常用的任务,如发送短信、播放音乐、设置闹钟等。下面是苹果iPhone快捷指令的使用方法详解: 步骤1:打开快捷指令应用程序 在iPhone设备中,找到并打开“快捷指令”应用程序。 如果您是第一次使用快捷指令应用程序,需要先创建一个新的快捷指令。 步骤2:创建新的快捷指令 在快捷指令应用程…

    html 2023年5月17日
    00
  • HTML5 微格式和相关的属性名称

    HTML5 微格式是指用于表示特定类型数据的 HTML 标记,在页面进行结构化、扩展和语义化时有重要的作用。而与此相关的属性名称是指用于表达微格式中特定意义的属性名称,使得浏览器和搜索引擎可以将页面中的数据转换成标准的格式并更好的理解页面内容。下面是 HTML5 微格式和相关属性名称的详细讲解攻略。 HTML5 微格式 HTML5 微格式是指用于标识网页内容…

    html 2023年5月30日
    00
  • 解决phpmyadmin中文乱码问题。。。

    解决phpMyAdmin中文乱码问题的攻略如下: 问题描述 当我们在phpMyAdmin中输入中文字符时,有时会出现乱码的情况。这是因为phpMyAdmin默认的字符集与数据库中的字符集不一致所导致的。 解决方案 方案一:修改phpMyAdmin的默认编码 打开phpMyAdmin的配置文件config.inc.php,一般位于/etc/phpmyadmin…

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