Spring14——案例:利用AOP环绕通知计算业务层接口执行效率

前面介绍了这么多种通知类型,具体该选哪一种呢?

我们可以通过一些案例加深下对通知类型的学习。

34-案例:利用AOP环绕通知计算业务层接口执行效率

需求分析

这个需求也比较简单,前面我们在介绍AOP的时候已经演示过:

  • 需求:任意业务层接口执行均可显示其执行效率(执行时长)
    这个案例的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化

具体实现的思路:

  1. 开始执行方法之前记录一个时间
  2. 执行方法
  3. 执行完方法之后记录一个时间
  4. 用后一个时间减去前一个时间的差值,就是我们需要的结果

所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知。

说明:原始方法如果只执行一次,时间太快,两个时间差可能为0,所以我们要执行万次来计算时间差。

环境准备

  • 创建一个Maven项目
  • pom.xml添加Spring依赖
xml 复制代码
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
  • 创建数据库与表
sql 复制代码
create database spring_db character set utf8;
use spring_db;
create table tbl_account(
    id int primary key auto_increment,
    name varchar(35),
    money double
);

INSERT INTO tbl_account(`name`,money) VALUES
('Tom',2800),
('Jerry',3000),
('Jhon',3100);
  • 添加Account、AccountDao、AccountService、AccountServiceImpl类
java 复制代码
public class Account {
    private Integer id;
    private String name;
    private Double money;

    public Account() {
    }

    public Account(Integer id, String name, Double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Double getMoney() {
        return money;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
java 复制代码
public interface AccountDao {

    @Insert("insert into tbl_account(`name`,money) values(#{name},#{money}) ")
    void save(Account account);

    @Delete("delete from tbl_account where id=#{id}")
    void delete(Integer id);

    @Update("update tbl_account set `name`=#{name},money=#{money}")
    void update(Account account);

    @Select("select * from tbl_account")
    List<Account> findAll();

    @Select("select * from tbl_account where id=#{id}")
    Account findById(Integer id);
}
java 复制代码
public interface AccountService {
    void save(Account account);
    void update(Account account);
    void delete(Integer id);
    List<Account> findAll();
    Account findById(Integer id);
}
java 复制代码
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }
}
  • resources下提供一个jdbc.properties
yaml 复制代码
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=password
  • 创建相关配置类
java 复制代码
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
java 复制代码
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setTypeAliasesPackage("com.yolo.pojo");
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.yolo.dao");
        return mapperScannerConfigurer;
    }
}
  • 第一个方法sqlSessionFactoryBean接受一个DataSource类型的参数,创建了一个SqlSessionFactoryBean对象。这个对象设置了类型别名包为 "com.yolo.pojo",并设置了数据源dataSource。
    这个方法的目的是创建并配置 MyBatis的SqlSessionFactoryBean,它是 MyBatis 和 Spring 集成的关键组件之一,用于创建SqlSession,从而执行数据库操作。
  • 第二个方法mapperScannerConfigurer创建了一个MapperScannerConfigurer对象,并设置了基础包为 "com.yolo.dao"。这个对象用于扫描指定包下的 MyBatis Mapper 接口,并将它们注册到 Spring 容器中,使得这些 Mapper 接口可以被自动注入到其他组件中,方便进行数据库操作。
java 复制代码
@Configuration
@ComponentScan("com.yolo")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
public class SpringConfig {
}
  • 编写Spring整合Junit的测试类
java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById() {
        Account byId = accountService.findById(2);
        System.out.println(byId);
    }

    @Test
    public void testFindAll(){
        List<Account> accountList = accountService.findAll();
        System.out.println(accountList);
    }
}

运行测试类,结果如下

功能开发

  • 步骤一:开启SpringAOP的注解功能
    在Spring的主配置文件SpringConfig类中添加注解
java 复制代码
@EnableAspectJAutoProxy
  • 步骤二:创建AOP的通知类
    • 该类要被Spring管理,需要添加@Component
    • 要标识该类是一个AOP的切面类,需要添加@Aspect
    • 配置切入点表达式,需要添加一个方法,并添加@Pointcut
java 复制代码
@Component
@Aspect
public class ProjectAdvice {
    @Pointcut("execution(* com.yolo.service.*Service(..))")
    public void servicePt() {

    }

    public void runSpeed() {

    }
}
  • 步骤三:添加环绕通知
    在runSpeed()方法上添加@Around
java 复制代码
@Component
@Aspect
public class ProjectAdvice {
    @Pointcut("execution(* com.yolo.service.*Service.*(..))")
    public void servicePt() {
    }

    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint point) {
    }    
}
  • 步骤四:完成核心业务,记录万次执行的时间
java 复制代码
@Component
@Aspect
public class ProjectAdvice {
	//匹配业务层的所有方法
    @Pointcut("execution(* com.yolo.service.*Service.*(..))")
    public void servicePt() {
    }

    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint point) throws Throwable {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            point.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("业务层接口万次执行时间:" + (end - start) + "ms");
    }
}
  • 步骤五:运行单元测试类

    运行结果如下

  • 步骤六: 程序优化

    目前还存在一个问题,当我们一次执行多个方法时,控制台输出的都是业务层接口万次执行时间: XXXms

    我们无法得知具体哪个方法的耗时,那么该如何优化呢?

    ProceedingJoinPoint中有一个getSignature()方法来获取签名,然后调用getDeclaringTypeName可以获取类名,getName()可以获取方法名

java 复制代码
@Component
@Aspect
public class ProjectAdvice {
    @Pointcut("execution(* com.yolo.service.*Service.*(..))")
    public void servicePt() {
    }

    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint point) throws Throwable {
        //Signature指签名信息,可理解为封装了这次执行过程
        Signature signature = point.getSignature();
        //获取类名
        String className = String.valueOf(signature.getDeclaringType());
        //获取方法名
        String methodName = signature.getName();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            point.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("业务层接口万次执行时间:" + className + "." +methodName + "耗时" + (end - start) + "ms");
    }
}

再次运行程序,结果如下

相关推荐
是店小二呀几秒前
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
后端
pjx9871 分钟前
超越单体:进入微服务世界与Spring Cloud概述
java·spring cloud·微服务
Victor3561 分钟前
Dubbo(86)如何设计一个多租户的Dubbo服务?
后端
天天摸鱼的java工程师2 分钟前
爆肝 30 天!从 JVM 调优到百万级 QPS,我的 Java 性能飞升全记录
java·后端
TDengine (老段)6 分钟前
TDengine 订阅不到数据问题排查
java·数据库·tdengine
刘立军7 分钟前
本地大模型编程实战(26)用langgraph实现基于SQL数据构建的问答系统(5)
人工智能·后端·python
冼紫菜8 分钟前
Spring Cloud 项目中优雅地传递用户信息:基于 Gateway + ThreadLocal 的用户上下文方案
java·开发语言·spring boot·后端·spring cloud·gateway
为美好的生活献上中指9 分钟前
java每日精进 4.29【框架之自动记录日志并插入如数据库流程分析】
java·linux·数据库
初心_202414 分钟前
2. python协程/异步编程详解
java·前端·python
camellia15 分钟前
SpringBoot(三十九)SpringBoot集成RabbitMQ实现流量削峰添谷
java·后端