Java開發學習(十七)—-AOP案例之測量業務層介面執行效率

一、需求分析

這個需求比較簡單

  • 需求:任意業務層介面執行均可顯示其執行效率(執行時長)

這個的目的是查看每個業務層執行的時間,這樣就可以監控出哪個業務比較耗時,將其查找出來方便優化。

具體實現的思路:

(1) 開始執行方法之前記錄一個時間

(2) 執行方法

(3) 執行完方法之後記錄一個時間

(4) 用後一個時間減去前一個時間的差值,就是我們需要的結果。

所以要在方法執行的前後添加業務,經過分析我們將採用環繞通知

說明:原始方法如果只執行一次,時間太快,兩個時間差可能為0,所以我們要執行萬次來計算時間差。

二、環境準備

  • 創建一個Maven項目

  • pom.xml添加Spring依賴

    <dependencies>
        <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>5.1.47</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>
      </dependencies>
  • 添加AccountService、AccountServiceImpl、AccountDao與Account類

    public interface AccountService {
        void save(Account account);
        void delete(Integer id);
        void update(Account account);
        List<Account> 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<Account> 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<Account> 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<Account> all = accountService.findAll();
        }
    ​
    }

最終創建好的項目結構如下:

三、功能開發

步驟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:運行單元測試類

注意:因為程式每次執行的時長是不一樣的,所以運行多次最終的結果是不一樣的。

步驟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:運行單元測試類