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");
    }
}

再次运行程序,结果如下

相关推荐
勤奋的知更鸟几秒前
Java编程之组合模式
java·开发语言·设计模式·组合模式
千|寻1 分钟前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
程序员岳焱15 分钟前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
后端·sql·mysql
爱编程的喵15 分钟前
深入理解JavaScript原型机制:从Java到JS的面向对象编程之路
java·前端·javascript
龚思凯21 分钟前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响23 分钟前
枚举在实际开发中的使用小Tips
后端
on the way 12326 分钟前
行为型设计模式之Mediator(中介者)
java·设计模式·中介者模式
保持学习ing28 分钟前
Spring注解开发
java·深度学习·spring·框架
wuhunyu29 分钟前
基于 langchain4j 的简易 RAG
后端
techzhi29 分钟前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端