一、需求分析
这个需求比较简单
需求:任意业务层接口执行均可显示其执行效率(执行时长)
这个的目的是查看每个业务层执行的时间,这样就可以监控出哪个业务比较耗时,将其查找出来方便优化。
具体实现的思路:
(1) 开始执行方法之前记录一个时间
(2) 执行方法
(3) 执行完方法之后记录一个时间
(4) 用后一个时间减去前一个时间的差值,就是我们需要的结果。
所以要在方法执行的前后添加业务,经过分析我们将采用环绕通知
。
说明:原始方法如果只执行一次,时间太快,两个时间差可能为0,所以我们要执行万次来计算时间差。
二、环境准备
创建一个Maven项目
pom.xml添加Spring依赖
- <dependencies>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-contextartifactId>
- <version>5.2.10.RELEASEversion>
- dependency>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-jdbcartifactId>
- <version>5.2.10.RELEASEversion>
- dependency>
- <dependency>
- <groupId>org.springframeworkgroupId>
- <artifactId>spring-testartifactId>
- <version>5.2.10.RELEASEversion>
- dependency>
- <dependency>
- <groupId>org.aspectjgroupId>
- <artifactId>aspectjweaverartifactId>
- <version>1.9.4version>
- dependency>
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <version>5.1.47version>
- dependency>
- <dependency>
- <groupId>com.alibabagroupId>
- <artifactId>druidartifactId>
- <version>1.1.16version>
- dependency>
- <dependency>
- <groupId>org.mybatisgroupId>
- <artifactId>mybatisartifactId>
- <version>3.5.6version>
- dependency>
- <dependency>
- <groupId>org.mybatisgroupId>
- <artifactId>mybatis-springartifactId>
- <version>1.3.0version>
- dependency>
- <dependency>
- <groupId>junitgroupId>
- <artifactId>junitartifactId>
- <version>4.12version>
- <scope>testscope>
- dependency>
- dependencies>
添加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(); - }
-
- }
最终创建好的项目结构如下:
三、功能开发
步骤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:运行单元测试类