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

再次运行程序,结果如下

相关推荐
Ling_suu27 分钟前
SpringBoot3——Web开发
java·服务器·前端
hanglove_lucky36 分钟前
本地摄像头视频流在html中打开
前端·后端·html
天使day43 分钟前
SpringMVC
java·spring·java-ee
CodeClimb1 小时前
【华为OD-E卷-简单的自动曝光 100分(python、java、c++、js、c)】
java·python·华为od
风清云淡_A1 小时前
【java基础系列】实现数字的首位交换算法
java·算法
Gao_xu_sheng1 小时前
Java程序打包成exe,无Java环境也能运行
java·开发语言
大卫小东(Sheldon)1 小时前
Java的HTTP接口测试框架Gatling
java
谢家小布柔1 小时前
java中的继承
java·开发语言
l138494274511 小时前
Java每日一题(2)
java·开发语言·游戏
苹果醋32 小时前
SpringBoot快速入门
java·运维·spring boot·mysql·nginx