前面介绍了这么多种通知类型,具体该选哪一种呢?
我们可以通过一些案例加深下对通知类型的学习。
34-案例:利用AOP环绕通知计算业务层接口执行效率
需求分析
这个需求也比较简单,前面我们在介绍AOP的时候已经演示过:
- 需求:任意业务层接口执行均可显示其执行效率(执行时长)
这个案例的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化
具体实现的思路:
- 开始执行方法之前记录一个时间
- 执行方法
- 执行完方法之后记录一个时间
- 用后一个时间减去前一个时间的差值,就是我们需要的结果
所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知。
说明:原始方法如果只执行一次,时间太快,两个时间差可能为0,所以我们要执行万次来计算时间差。
环境准备
- 创建一个Maven项目
- pom.xml添加Spring依赖
<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>
- 创建数据库与表
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类
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;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';}
}
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);
}
public interface AccountService {void save(Account account);void update(Account account);void delete(Integer id);List<Account> findAll();Account findById(Integer id);
}
@Service
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Overridepublic void save(Account account) {accountDao.save(account);}@Overridepublic void update(Account account) {accountDao.update(account);}@Overridepublic void delete(Integer id) {accountDao.delete(id);}@Overridepublic List<Account> findAll() {return accountDao.findAll();}@Overridepublic Account findById(Integer id) {return accountDao.findById(id);}
}
- resources下提供一个jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=password
- 创建相关配置类
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;@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}
}
public class MyBatisConfig {@Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setTypeAliasesPackage("com.yolo.pojo");sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}@Beanpublic 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 接口可以被自动注入到其他组件中,方便进行数据库操作。
@Configuration
@ComponentScan("com.yolo")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
public class SpringConfig {
}
- 编写Spring整合Junit的测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {@Autowiredprivate AccountService accountService;@Testpublic void testFindById() {Account byId = accountService.findById(2);System.out.println(byId);}@Testpublic void testFindAll(){List<Account> accountList = accountService.findAll();System.out.println(accountList);}
}
运行测试类,结果如下
功能开发
- 步骤一:开启SpringAOP的注解功能
在Spring的主配置文件SpringConfig类中添加注解
@EnableAspectJAutoProxy
- 步骤二:创建AOP的通知类
- 该类要被Spring管理,需要添加
@Component
- 要标识该类是一个AOP的切面类,需要添加
@Aspect
- 配置切入点表达式,需要添加一个方法,并添加
@Pointcut
- 该类要被Spring管理,需要添加
@Component
@Aspect
public class ProjectAdvice {@Pointcut("execution(* com.yolo.service.*Service(..))")public void servicePt() {}public void runSpeed() {}
}
- 步骤三:添加环绕通知
在runSpeed()方法上添加@Around
@Component
@Aspect
public class ProjectAdvice {@Pointcut("execution(* com.yolo.service.*Service.*(..))")public void servicePt() {}@Around("servicePt()")public void runSpeed(ProceedingJoinPoint point) {}
}
- 步骤四:完成核心业务,记录万次执行的时间
@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()
可以获取方法名
@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");}
}
再次运行程序,结果如下