Spring AOP面向切面编程7:AOP相关概念五:AOP通知之:Around Advice 环绕通知;(以【统计方法执行时间,定位低效率方法】为案例)(环绕通知十分重要,在实际中使用的比较多)

52 篇文章 0 订阅

说明: 

 

(1)本篇博客主要内容:Around Advice 环绕通知,这是最强大的通知,自定义通知执行时机,可决定目标方法是否运行。这是核心的,也是最重要的通知。

(2)虽然在【MyBatis进阶一:MyBatis日志管理;(【如何输出日志到日志文件中】待补充……)】介绍了日志的内容,但是如何把日志存放在一个日志文件中尚未接触;然后,估计日志还有很多其他内容尚不了解;所以,以后一定要较系统的了解下系统日志的内容;

(3)本篇博客的案例还是比较重要的,可以预估在以后的开发中,案例中的问题大概率会经常遇到;

目录

零:需求说明

一:准备一个工程,演示用;

(1)readme.md文档:

(2)EmployeeDao类:

(3)UserDao类:

(4)EmployeeService类:

(5)UserService类:

二:正式编码前的准备

(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】

(2)创建applicationContext.xml配置文件:引入【默认命名空间】,【context命名空间】,【aop命名空间】;配置IoC容器中的bean;

三:正式实现:使用【Spring AOP技术】中的【Around Advice 环绕通知】去实现【零:需求说明】中的需求;

(1)创建容纳切面类的包:aspect包;添加用户检查方法运行效率的切面类:MethodChecker类;

(2)配置applicationContext.xml:配置究竟在【哪些类的哪些方法上】应用【MethodChecker切面类】;

(3)创建SpringApplication入口类,去测试;


零:需求说明

场景和需求:随着数据量的累计、用户量的增大,生产环境中系统越来越慢,如何定位到底是哪个方法执行慢?

问题分析:

          ● 这个问题看似简单,但实际上挺复杂的;因为,一个大型系统的类和方法可能有成千上万,我们肯定不能给每一个方法都增加代码去捕捉方法的执行时间,因为这样做效率会很差;

          ● 合适的策略:【Spring AOP】就是一个很好的方案;我们只需要在方法执行前捕捉方法的开始时间,方法执行后捕获方法的结束时间,然后就能算出方法的执行时间;如果方法的执行时间超过了规定范围,我们就将其输出保存在日志中;

          ● 因为这个场景下,我们要获取两个时间(运行前时间和运行后时间),可以知道AOP中的【前置通知、后置通知、返回后通知、异常通知】都不能解决这个问题;Spring提供了一个更强大的通知【Around Advice 环绕通知】,利用环绕通知,可以控制目标方法完整的运行周期;


一:准备一个工程,演示用;

为了演示,导入示例工程s01(这个工程在WorkSpace的aop目录下):

其中预置了readme.md,EmployeeDao,UserDao,EmployeeService,EmployeeDao;

(1)readme.md文档:

XML配置Sring AOP

(2)EmployeeDao类:

package com.imooc.spring.aop.dao;

/**
 * 员工表Dao
 */
public class EmployeeDao {
    public void insert(){
        System.out.println("新增员工数据");
    }
}

说明:

(1)这个类模拟了,操作数据中的Employee员工表; 其中添加了一个示意性的方法,向Employee员工表插入一条数据;

(3)UserDao类:

package com.imooc.spring.aop.dao;

/**
 * 用户表Dao
 */
public class UserDao {
    public void insert(){
        System.out.println("新增用户数据");
    }
}

说明:

(1)这个类模拟了,操作数据中User用户表; 其中添加了一个示意性的方法,向User用户表插入一条数据;

(4)EmployeeService类:

package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.EmployeeDao;

/**
 * 员工服务
 */
public class EmployeeService {
    private EmployeeDao employeeDao;
    public void entry(){
        System.out.println("执行员工入职业务逻辑");
        employeeDao.insert();
    }

    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }

    public void setEmployeeDao(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
}

说明:

(1)EmployeeService主要是处理员工入职的逻辑,其中会调用EmployeeDao中的方法;

(2)然后,其中的EmployeeDao属性,也生成了get和set方法;

(5)UserService类:

package com.imooc.spring.aop.service;

import com.imooc.spring.aop.dao.UserDao;

/**
 * 用户服务
 */
public class UserService {
    private UserDao userDao;

    public void createUser(){
        System.out.println("执行员工入职业务逻辑");
        userDao.insert();
    }

    public String generateRandomPassword(String type , Integer length){
        System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
        return "Zxcquei1";
    }

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

说明:

(1)代码分析

(2)然后,其中的UserDao属性,也生成了get和set方法;

这个工程,目前有四个类:UserDao类,EmployeeDao类,UserService类,EmployeeService类;我们要在这四个类的每一个方法上,进行运行时间的检查,如果某个方法的运行时间超过一定时间,就认为这个方法执行太慢,需要优化;


二:正式编码前的准备

(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc.spring</groupId>
    <artifactId>aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>
</project>

 说明:

(1)在【Spring AOP面向切面编程2:初识AOP二:AOP初体验;(一个Spring AOP的案例,走了一遍流程)】中,【spring-context】引入5.3.9版本会出错;但是,这儿不会报错了,有点莫名其妙;

(2)创建applicationContext.xml配置文件:引入【默认命名空间】,【context命名空间】,【aop命名空间】;配置IoC容器中的bean;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
    <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
    <bean id="userService" class="com.imooc.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>
    
</beans>

说明:

(1)引入了默认命名空间,context命名空间,aop命名空间;以及,设置对应命名空间的schema约束;

(2)然后,设置了UserDao类,EmployeeDao类,UserService类,EmployeeService类的bean;


三:正式实现:使用【Spring AOP技术】中的【Around Advice 环绕通知】去实现【零:需求说明】中的需求;

(1)创建容纳切面类的包:aspect包;添加用户检查方法运行效率的切面类:MethodChecker类;

MethodChecker类:

package com.imooc.spring.aop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MethodChecker {
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        try {
            long startTime = new Date().getTime();//得到方法执行前的时间
            Object ret = pjp.proceed();//执行目标方法;返回值是目标方法的返回值;
            long endTime = new Date().getTime();//得到方法执行后的时间;
            long duration = endTime - startTime;//获取方法执行时间;
            if (duration >= 1000) { //如果方法执行时间大于等于1000ms;
                String className = pjp.getTarget().getClass().getName();
                String methodName = pjp.getSignature().getName();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now = sdf.format(new Date());
                System.out.println("=====" + now + ":" + className + "." + methodName + "(" + duration + "ms)=====");
            }
            return ret;
        } catch (Throwable throwable) {
            System.out.println("Exception message:" + throwable.getMessage());
            throw throwable;
        }
    }
}

说明:

(1)环绕通知的切面方法,需要返回一个Object类型的对象;

(2)【ProceedingJoinPoint】:是一个特殊的连接点;ProceedingJoinPoint是JoinPoint的升级版;JointPoint有的功能,ProceedingJoinPoint都有,而且ProceedingJoinPoint还新增了【控制目标方法是否执行】的功能;

(3)【pjp.proceed();】:proceed()方法是ProceedingJoinPoint的核心方法;这个方法的作用是执行目标方法,如果不写这条语句,那么目标方法将不会执行,即这条语句就是控制目标方法是否执行的核心所在;(其实,能感觉到这背后的支撑是IoC容器)

(4)程序结构

 (5)可以看到【环绕通知】可以完成【前置通知、后置通知、返回后通知、异常通知】这四种通知的所有工作;如下图;这也印证了【环绕通知】是最强大的一个通知;

(2)配置applicationContext.xml:配置究竟在【哪些类的哪些方法上】应用【MethodChecker切面类】;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
    <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
    <bean id="userService" class="com.imooc.spring.aop.service.UserService">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
        <property name="employeeDao" ref="employeeDao"/>
    </bean>

    <bean id="methodChecker" class="com.imooc.spring.aop.aspect.MethodChecker"/>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.imooc..*.*(..))"/>
        <aop:aspect ref="methodChecker">
            <aop:around method="check" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

说明:

(1)配置说明:【环绕通知】的配置,和,【前置通知、后置通知、返回后通知、异常通知】的配置,基本类似;

(3)创建SpringApplication入口类,去测试;

package com.imooc.spring.aop;

import com.imooc.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser();

    }
}

说明:

(1)为了能够更好应用切面类中切面方法,让目标方法creatUser()方法所在线程休眠3秒;

(2)这儿我只调用了creatUser()方法;(调用那么多方法干嘛,能演示环绕通知就行了)

(3)运行效果

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值