【Spring教程18】Spring框架实战:利用Aop测定业务层接口执行效率代码实例详解
作者:mmseoamin日期:2023-12-11

欢迎大家回到《Java教程之Spring30天快速入门》,本教程所有示例均基于Maven实现,如果您对Maven还很陌生,请移步本人的博文《如何在windows11下安装Maven并配置以及 IDEA配置Maven环境》,本文的上一篇为《详解解读AOP通知类型的使用》

【Spring教程18】Spring框架实战:利用Aop测定业务层接口执行效率代码实例详解,在这里插入图片描述,第1张

1 需求分析

这个需求也比较简单,前面我们在介绍AOP的时候已经演示过:

  • 需求:任意业务层接口执行均可显示其执行效率(执行时长)

    这个案例的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化。

    具体实现的思路:

    (1) 开始执行方法之前记录一个时间

    (2) 执行方法

    (3) 执行完方法之后记录一个时间

    (4) 用后一个时间减去前一个时间的差值,就是我们需要的结果。

    所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知。

    说明:原始方法如果只执行一次,时间太快,两个时间差可能为0,所以我们要执行万次来计算时间差

    2 环境准备

    • 创建一个Maven项目
    • pom.xml添加Spring依赖
      
      	
      		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
      	
      
      
      • 添加AccountService、AccountServiceImpl、AccountDao与Account类
        public interface AccountService {
        	void save(Account account);
        	void delete(Integer id);
        	void update(Account account);
        	List findAll();
        	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方法省略
        }
        
        • resources下提供一个jdbc.properties
          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;
            	}
            }
            
            • 编写Spring整合Junit的测试类
              @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(){
              		List all = accountService.findAll();
              	}
              }
              

              最终创建好的项目结构如下:

              【Spring教程18】Spring框架实战:利用Aop测定业务层接口执行效率代码实例详解,在这里插入图片描述,第2张

              3 功能开发

              步骤1:开启SpringAOP的注解功能

              在Spring的主配置文件SpringConfig类中添加注解

              @EnableAspectJAutoProxy
              

              步骤2:创建AOP的通知类

              • 该类要被Spring管理,需要添加@Component
              • 要标识该类是一个AOP的切面类,需要添加@Aspect
              • 配置切入点表达式,需要添加一个方法,并添加@Pointcut
                @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:运行单元测试类

                【Spring教程18】Spring框架实战:利用Aop测定业务层接口执行效率代码实例详解,在这里插入图片描述,第3张

                注意:因为程序每次执行的时长是不一样的,所以运行多次最终的结果是不一样的。

                步骤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:运行单元测试类

                【Spring教程18】Spring框架实战:利用Aop测定业务层接口执行效率代码实例详解,在这里插入图片描述,第4张

                4 补充说明

                当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程。

                这块只是通过该案例把AOP的使用进行了学习,具体的实际值是有很多因素共同决定的。