Spring-AOP实践
Spring-AOP实践
公司的项目有的页面超级慢,20s以上,不知道用户会不会疯掉,于是老大说这个页面要性能优化。于是,首先就要搞清楚究竟是哪一步耗时太多。
我采用spring aop来统计各个阶段的用时,其中计时器工具为StopWatch。
文章结构:
其中,遇到的问题:
1.少包aspectjweaver
添加依赖后才可以使用@Aspect
2.环绕通知加入多个point
刚开使用&&连接多个point,傻傻的看不到调用,忽然看到要用||才对
3.监听时间工具StopWatch每次只能启动一个,一定要关闭后才能启动下一个。
而我想要测试controller->service->repository各个阶段用时显然很不适应。因为使用同一个stopwatch对象来保存时间,而stopwatch每次只能记录一个时间段作为总时间的一部分,不存在时间嵌套关系(这个以后可以考虑想想别的方案)。controller开始后执行部分验证逻辑或是其他,然后调用service,这时候service启动定时会失败,因为controller的计时器还没关,因此需要先关掉controller的计时器。这样会导致controller的计时部分仅仅是调用service之前的时间,service返回值之后,controller再进行某些处理的时间并没有统计。显然,我需要一个卡表的时间统计设计,即进入controller开始计时,调用service后开始service计时,以此类推,最后获得controller总时间Tc,service总时间Ts,repository总时间Tr.所以时间统计应该如图1:

图一 图二
这样的话,我应该分别获得Tc,Ts,Tr的时间,然后计算百分比或者别的统计工作。也就是说,log仅仅是log,记录而已,想要得到一个统计结果还需要针对数据二次开发,这就需要将时间存储起来或者进行日志挖掘,然而这统计的工作会越来越复杂,最终我都不知会加多少任务进去。
事实上,我们这个页面耗费时间主要是多个webservice调用产生的io耗时,也就是说其实统计一个层面的时间就差不多了。那么,忽略service返回后controller的其他时间,仅仅计算controller开始到调用service的时间为Tc,service开始到调用repository的时间为Ts,如图2,这样利用StopWatch就很容获取时间统计结果。
4.线程不安全
我居然使用单例的一个成员变量做状态存储,真是。待改进
1.搭建项目
采用spring-boot-aop的框架来搭建。最终结构图如下:

1.1在idea中,new project-》maven
1.2初始化pom,添加aop,web,test依赖:
1.3创建启动入口:com.test.spring.aop.Application.java
1.4发现我们需要一个service:
创建com.test.spring.aop.domain.service.IHelloService
创建com.test.spring.aop.domain.service.impl.HelloService
1.5发现我们需要一个属性配置文件:
创建application.yml
1.6这时候可以测试一下启动看看了
直接启动:运行com.test.spring.aop.Application#main()即可。
命令行启动:mvn spring-boot:run,
命令行bug启动:mvn spring-boot:run -Drun.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"
1.7创建测试com.test.spring.aop.ApplicationTest
2.AOP - HelloWorld
2.1创建com.test.spring.aop.monitor.ServiceMonitor
2.2测试
测试水平还没开始,所以只能手动测试了,运行com.test.spring.aop.ApplicationTest可以看到在HelloService调用前后插入了日志。
3.时间统计
最终,生成类图如下:

最终获得的时间统计如下:

2016-07-16 21:25:09.361 INFO 16824 --- [nio-8080-exec-4] com.test.spring.aop.monitor.UserMonitor : StopWatch 'controller': running time (millis) = 3218
-----------------------------------------
ms % Task name
-----------------------------------------
01001 031% List com.test.spring.aop.domain.service.IUserService.getAll()
02000 062% Object com.test.spring.aop.domain.repository.IConnector.getSthFromRemote()
00217 007% List com.test.spring.aop.domain.repository.IUserDao.getAll()

3.1需要设计一个controller
创建com.test.spring.aop.web.UserController:
3.2发现需要一个service
创建com.test.spring.aop.domain.service.IUserService
创建com.test.spring.aop.domain.service.impl.UserService
3.3发现需要一个repository
创建com.test.spring.aop.domain.repository.IUserDao
创建com.test.spring.aop.domain.repository.impl.UserDao
3.3.1临时添加一个远程调用
创建com.test.spring.aop.domain.repository.IConnector
创建com.test.spring.aop.domain.repository.impl.Connector
3.4发现需要一个实体类
创建com.test.spring.aop.domain.entiry.User
3.5完成
以上就基本把一个web接口搞定了,可以运行测试一下,使用浏览器或者postman访问localhost:8080/user/all就可以获得user列表了。
3.6加入user时间统计
创建com.test.spring.aop.monitor.UserMonitor
这里有几点问题:
1.StopWatch不能重复创建,如果想统计每次controller访问时间,必须在访问前初始化,访问后废除。
2.这里的时间统计仅仅针对controller入口的统计,也就是说,如果别的调用了service,这个时间是不统计的,因为StopWatch没有初始化。
3.StopWatch的prettyPrint是一个很好的东东
4.如果想要向开始那样,完全统计每层耗时,这个设计需要重新设计。当前有这样的想法。controller切点初始化三个或者多个StopWatch;service、repository等分别建立切点并计时;controller执行完毕之后的切点汇总统计数据,销毁各个StopWatch。这样就做到了每个层都统计了。然而问题来了,如果一个controller调用了多个service,显然需要统计所有service耗时和单个service耗时。


Boo! Big Bug!!!
今早起来接着看,忽然想起spring bean管理是单例的,而且必须是单例的才可以使用共同的成员变量,但问题来了,spring的controller是多线程的。也就是说,这个切面进入不是排队的,第一个请求过程中第二个请求也是可以进来的。那么,共享成员变量实在是愚蠢的决定。然而选择创建一个类来管理对应一个请求的计时器也不好的,如果并发量超级大岂不是每个人都会产生一个类?
因此,这个计时器必须是一个过程量,可以在指定区间(即controller执行期间)生存,而后销毁。对应的,显然是每个并发请求会至少产生一个类。也就是说,我需要在controller请求的时候new 一个StopWatch的id,然后在接下来的一系列调用中都使用这个id对应的计时器,最后销毁。如果几百万个并发量,那么就会产生几百万个类实例。懵逼。
最终选择
通过一个实例来保存一次请求状态太消耗性能,而如果不通过一个类实例保存状态就无法汇总所有的时间。所以这个方案不合适。那么,剩下日志和传入数据库。当下就先记录日志好了。记录想要记录的节点的时间:
压力测试
1.建立test case
首先,测试controller:创建com.test.spring.aop.web.UserControllerTest
不知道为啥,我的new Thread并没有并发执行,而是按顺序执行的。也就是说,虽然我new 10 个Thread,但还是一个接一个的运行。
改进
于是,我想采用线程池:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Test public void testUsersConcurrent() throws Exception{ ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5)); //不能超过15 for (int i = 0; i < 15; i++) { executor.execute(()->{ try { mockMvc.perform(MockMvcRequestBuilders.get("/user/all")).andExpect(MockMvcResultMatchers.status().isOk()); } catch (Exception e) { e.printStackTrace(); } }); } //等待其他线程执行,方便查看控制台打印结果 Thread.sleep(100000); } |
这下就可以看到controller是多线程并发的了,因为瞬间就打印了10条controller访问日志,正好是线程池的最大容量:
2.采用Apache 的ab进行并发访问
2.1下载Apache
参考:使用Apache Server 的ab进行web请求压力测试
2.2测试
可以看到服务端是多线程的:
项目源码:https://github.com/chenxing12/spring-aop
Spring-AOP实践的更多相关文章
- spring aop 实践
之前用的ssm框架,大部分只是利用spring的IOC特性,很明显这能够为我们开发人员在对象的创建上面节省大部分时间.当相似得到业务越来越多,很多代码也是越来越重复,轮子是越来越重复,比如验证用户登录 ...
- Spring Boot实践——Spring AOP实现之动态代理
Spring AOP 介绍 AOP的介绍可以查看 Spring Boot实践——AOP实现 与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改 ...
- Spring Boot实践——AOP实现
借鉴:http://www.cnblogs.com/xrq730/p/4919025.html https://blog.csdn.net/zhaokejin521/article/detai ...
- Spring基础系列--AOP实践
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9615720.html 本文目的是简单讲解下Spring AOP的使用. 推荐使用IDEA ...
- 【AOP】Spring AOP基础 + 实践 完整记录
Spring AOP的基础概念 ============================================================= AOP(Aspect-Oriented Pr ...
- [转]彻底征服 Spring AOP 之 实战篇
Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体 ...
- Spring AOP 实现原理与 CGLIB 应用
https://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/ AOP(Aspect Orient Programming),也就是面向 ...
- 161110、彻底征服 Spring AOP 之 实战篇
Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体 ...
- Spring AOP: Spring之面向方面编程
Spring AOP: Spring之面向方面编程 面向方面编程 (AOP) 提供从另一个角度来考虑程序结构以完善面向对象编程(OOP). 面向对象将应用程序分解成 各个层次的对象,而AOP将程序分解 ...
- Spring AOP 实现原理与 CGLIB 应用--转
AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如事务管理.安全检查.缓存.对象池管理等.AOP 实现的关键就在于 ...
随机推荐
- ORA-01652 错误中报出的不是Temp表空间的情况。
ORA-01652 unable to extend temp segment by %s in tablespace %s 注意这里的temp segment并不一定就是指临时表空间, 也可能是其 ...
- 灵光一闪-VS设计界面能访问到private修饰的各种控件
大家都知道,用VS设计界面时,VS默认控件的访问修饰符为private,但是我就很奇怪,private修饰的字段不是只有类内部才能访问吗? 好神奇的VS,这到底是怎么实现的?难道就是类似文本编辑器的作 ...
- svn服务器配置小记
在这里/opt/svndata/repos1创建svn版本库svnadmin create /opt/svndata/repos1 创建成功后会在/opt/svndata/repos1目录下生成con ...
- struts2在result中使用el表达式碰到的问题
<result name="success">/html/portlet/ext/trainingmanagement/download_file.jsp?path=$ ...
- Atom power-mode
最近看到很多大牛都在用这个酷炫狂拽掉渣天的插件,于是就默默地git了一波.实际结果就是机械键盘加上砰砰砰砰,根本停不下来有没有. 建议(ˉ(∞)ˉ) :视力2.0的同学不建议使用,眼瞎的同学用就用吧, ...
- 测试通用的InsertOrUpdate
- head first 设计模式读书笔记 之 策略模式
作为一个php开发者,深知曾经很多程序员都鄙视php,为什么呢?因为他们认为php的语法是dirty的,并且由于开发者水平参差不齐导致php的代码更加乱上加乱,维护起来简直一坨shit一样.随着php ...
- C语言学习 —— 字符串的学习(一)
这是本人在学习 C语言有关 字符串内容 时的相关笔记 由于本人技术有限,如有错误,还望指正 C语言中数据类型中只有 字符型(char),而 char型 变量一次只能存储一个字符,在日常工作中经常需要定 ...
- Tempo 2.0
Tempo 2.0 Tempo is an easy, intuitive JavaScript rendering engine that enables you to craft data tem ...
- 解决:未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0
开发环境:Microsoft Visual Studio 2013 MVC4.0 使用SignalR的过程中随着版本的升级,其它程序集也跟着升级,如Newtonsoft.Json,一般编译的时候,如果 ...