1. 需求分析
这个需求也比较简单,前面我们在介绍AOP的时候已经介绍过:
- 需求:任意业务层接口执行均可显示其执行效率(执行时长)
这个案例的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化。
具体实现的思路:
(1) 开始执行方法之前记录一个时间
(2) 执行方法
(3) 执行完方法之后记录一个时间
(4) 用后一个时间减去前一个时间的差值,就是我们需要的结果。
所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知
。
**说明:**原始方法如果只执行一次,时间太快,两个时间差可能为0,所以我们要执行万次来计算时间差。
2. 环境准备
-
创建一个Maven项目
-
pom.xml添加Spring依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.15.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.15.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
-
添加Usert类
public class User implements Serializable { private Integer userId; private String userName; private String email; private String branchName; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getBranchName() { return branchName; } public void setBranchName(String branchName) { this.branchName = branchName; } @Override public String toString() { return "User{" + "userId=" + userId + ", userName='" + userName + '\'' + ", email='" + email + '\'' + ", branchName='" + branchName + '\'' + '}'; } }
-
添加UserDao类
public interface UserDao { @Select("select user_id userId, email, user_name userName, branch_name branchName from platform_user ") public List<User> findAll(); @Select("select user_id userId, email, user_name userName, branch_name branchName from platform_user where user_id = #{id}") public User findByID(Integer id); @Insert("insert into platform_user(user_id,email,user_name,branch_name)values(#{userId},#{email},#{userName},#{branchName})") public Integer insertUser(User user); @Update("update platform_user set user_name = #{userName} , email = #{email} , branch_name = #{branchName} where user_id = #{userId}") public Integer updateUser(User user); @Delete("delete from platform_user where user_id = #{id}") public Integer deleleById(Integer id); }
-
添加UserService、UserServiceImpl类
public interface UserService { public List<User> findAll(); public User findByID(Integer id); public Integer insertUser(User user); public Integer updateUser(User user); public Integer deleleById(Integer id); } @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; public List<User> findAll() { return userDao.findAll(); } public User findByID(Integer id) { return userDao.findByID(id); } public Integer insertUser(User user) { return userDao.insertUser(user); } public Integer updateUser(User user) { return userDao.updateUser(user); } public Integer deleleById(Integer id) { return userDao.deleleById(id); } }
-
resources下提供一个jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false jdbc.username=root jdbc.password=root
-
创建JdbcConfig配置类
// jdbc 配置类 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; } }
-
创建MybatisConfig配置类
// Mybatis 配置类 public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){ SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setTypeAliasesPackage("com.dcxuexi.domain"); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.dcxuexi.dao"); return mapperScannerConfigurer; } }
-
创建SpringConfig配置类
@Configuration @ComponentScan("com.dcxuexi") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { }
-
编写Spring整合Junit的测试类
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class UserServiceTest { @Autowired private UserService userService; @Test public void testFindAll(){ List<User> userList = userService.findAll(); System.out.println(userList); } }
最终创建好的项目结构如下:
3. 功能开发
步骤1:开启SpringAOP的注解功能
在Spring的主配置文件SpringConfig类中添加注解
@EnableAspectJAutoProxy
步骤2:创建AOP的通知类
-
该类要被Spring管理,需要添加@Component
-
要标识该类是一个AOP的切面类,需要添加@Aspect
-
配置切入点表达式,需要添加一个方法,并添加@Pointcut
@Component
@Aspect
public class UserAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.dcxuexi.service.*Service.*(..))")
public void ptservice(){}
public void speed(){
}
}
步骤3:添加环绕通知
在speed()方法上添加@Around
@Component
@Aspect
public class UserAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.dcxuexi.service.*Service.*(..))")
public void ptservice(){}
@Around("ptservice()")
public Object speed(ProceedingJoinPoint proceedingJoinPoint){
Object proceed = null;
try {
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
**注意:**目前并没有做任何增强
步骤4:完成核心业务,记录万次执行的时间
@Component
@Aspect
public class UserAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.dcxuexi.service.*Service.*(..))")
public void ptservice(){}
@Around("ptservice()")
public Object speed(ProceedingJoinPoint proceedingJoinPoint){
Object proceed = null;
try {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
proceed = proceedingJoinPoint.proceed();
}
long end = System.currentTimeMillis();
System.out.println("方法执行一万次用时 = "+ (end-start) +"ms");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
步骤5:运行单元测试类
**注意:**因为程序每次执行的时长是不一样的,所以运行多次最终的结果是不一样的。
步骤6:程序优化
目前程序所面临的问题是,多个方法一起执行测试的时候,控制台都打印的是:
业务层接口万次执行时间:xxxms
我们没有办法区分到底是哪个接口的哪个方法执行的具体时间,具体如何优化?
@Component
@Aspect
public class ProjectAdvice {
//配置业务层的所有方法
@Pointcut("execution(* com.itheima.service.*Service.*(..))")
private void servicePt(){}
//@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
@Around("servicePt()")
public void runSpeed(ProceedingJoinPoint pjp){
//获取执行签名信息
Signature signature = pjp.getSignature();
//通过签名获取执行操作名称(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
}
步骤7:运行单元测试类
补充说明
当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程。具体的实际值是有很多因素共同决定的。