欢迎大家回到《Java教程之Spring30天快速入门》,本教程所有示例均基于Maven实现,如果您对Maven还很陌生,请移步本人的博文《如何在windows11下安装Maven并配置以及 IDEA配置Maven环境》,本文的上一篇为《详解解读AOP通知类型的使用》
这个需求也比较简单,前面我们在介绍AOP的时候已经演示过:
这个案例的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化。
具体实现的思路:
(1) 开始执行方法之前记录一个时间
(2) 执行方法
(3) 执行完方法之后记录一个时间
(4) 用后一个时间减去前一个时间的差值,就是我们需要的结果。
所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知。
说明:原始方法如果只执行一次,时间太快,两个时间差可能为0,所以我们要执行万次来计算时间差
org.springframework spring-context 5.2.10.RELEASE org.springframework spring-jdbc 5.2.10.RELEASE org.springframework spring-test 5.2.10.RELEASE org.aspectj aspectjweaver 1.9.4 mysql mysql-connector-java 5.1.47 com.alibaba druid 1.1.16 org.mybatis mybatis 3.5.6 org.mybatis mybatis-spring 1.3.0 junit junit 4.12 test
public interface AccountService { void save(Account account); void delete(Integer id); void update(Account account); ListfindAll(); Account findById(Integer id); } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void save(Account account) { accountDao.save(account); } public void update(Account account){ accountDao.update(account); } public void delete(Integer id) { accountDao.delete(id); } public Account findById(Integer id) { return accountDao.findById(id); } public List findAll() { return accountDao.findAll(); } } 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} where id = #{id} ") void update(Account account); @Select("select * from tbl_account") List findAll(); @Select("select * from tbl_account where id = #{id} ") Account findById(Integer id); } public class Account implements Serializable { private Integer id; private String name; private Double money; //setter..getter..toString方法省略 }
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false jdbc.username=root jdbc.password=root
//Spring配置类:SpringConfig @Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) public class SpringConfig { } //JdbcConfig配置类 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 ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } } //MybatisConfig配置类 public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("com.itheima.domain"); ssfb.setDataSource(dataSource); return ssfb; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.itheima.dao"); return msc; } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTestCase { @Autowired private AccountService accountService; @Test public void testFindById(){ Account ac = accountService.findById(2); } @Test public void testFindAll(){ Listall = accountService.findAll(); } }
最终创建好的项目结构如下:
步骤1:开启SpringAOP的注解功能
在Spring的主配置文件SpringConfig类中添加注解
@EnableAspectJAutoProxy
步骤2:创建AOP的通知类
@Component @Aspect public class ProjectAdvice { //配置业务层的所有方法 @Pointcut("execution(* com.itheima.service.*Service.*(..))") private void servicePt(){} public void runSpeed(){ } }
步骤3:添加环绕通知
在runSpeed()方法上添加@Around
@Component @Aspect public class ProjectAdvice { //配置业务层的所有方法 @Pointcut("execution(* com.itheima.service.*Service.*(..))") private void servicePt(){} //@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式 @Around("servicePt()") public Object runSpeed(ProceedingJoinPoint pjp){ Object ret = pjp.proceed(); return ret; } }
注意: 目前并没有做任何增强
步骤4:完成核心业务,记录万次执行的时间
@Component @Aspect public class ProjectAdvice { //配置业务层的所有方法 @Pointcut("execution(* com.itheima.service.*Service.*(..))") private void servicePt(){} //@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式 @Around("servicePt()") public void runSpeed(ProceedingJoinPoint pjp){ long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { pjp.proceed(); } long end = System.currentTimeMillis(); System.out.println("业务层接口万次执行时间: "+(end-start)+"ms"); } }
步骤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:运行单元测试类
当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程。
这块只是通过该案例把AOP的使用进行了学习,具体的实际值是有很多因素共同决定的。