Java Spring 切面 aop 超时?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Spring 切面 aop 超时?相关的知识,希望对你有一定的参考价值。
我想要设置一个全局超时切面,利用future.get(3,TimeUnit.SECONDS)设置方法执行3s超时,但是我用Jmeter压测时,切面里面打印方法耗时大概是3s,但是Jmeter会显示4s 5s,这就与我想要3s超时违背了。我想请教的是,切面里打印方法耗时3s,剩下的一两秒被什么占用了?如何确保3s超时而不会执行4s 5s呢?@Aspect......@Around......ExecutorService executorService= Executors.newCachedThreadExecutor(10); long startTime = System.currentTImeMillis(); Future<Object> future=executorService.submit(() -> return proceedingJoinPoint.proceed(); ); try String result=future.get(3,TimeUnit.SECONDS); catch (TimeoutException e) long endTime = System.currentTImeMillis(); System.out.println("超时了!耗时:"+endTime-startTime );
参考技术A 我觉得是不可能确保3秒的,先不说程序本身,从接收请求,到进入切面,到切面处理,最后返回数据这一过程,运行完就需要耗费一定的时间。而且我看你的线程池深度只有10,高并发下,很容易出现资源抢夺。当某个线程获取不到资源时,就会导致阻塞,甚至死锁。
我觉得你可以试一下,只用单线程发送10次请求,看一下结果会不会接近你的预期追问
首先感谢您的回答,newCachedThreadExecutor应该是不限制线程数量的。单线程跑会接近预期。我是不明白除了切面里的方法调用大约耗时的3s,剩下一两秒这么长时间是被谁消耗了呢?像微服务里的熔断器设置的超时也只是这个方法的耗时吧,不包括进入方法前和之后的耗时,那么有没有能限制总调用耗时大约3秒多的思路呢?
追答newCachedThreadExecutor(10)是哪个jdk版本里的方法啊,jdk1.8里好像没有这个线程池啊,类似的只有Executors.newCachedThreadPool()。我还是觉得影响大的是你的线程并发数,高压情况下线程堵塞经常出现,甚至出现直接堵死导致错误率99%以上。你图里Jmeter的参数是多少能发一下吗?
另外还是扯一下线程池的问题,假如真的是我说的高压导致线程阻塞的情况,或许对线程池调优可以缓解,针对你机器的CUP对线程池的参数进行优化,合理地设置线程池参数可以充分利用CPU,增加线程池的处理效率。
另外看到楼下的说法,我甚至有一个大胆的想法。既然你观察出有些线程会多执行一两秒,那么有没有可能,你在Sleep线程的时候,你从1、2、3里随机获取一个数字,让一部分线程Sleep1秒,一部分Sleep2秒,一部分Sleep3秒,是不是理论上平均耗时就降下来了?
1. 为什么胃酸不消化胃?既然胃酸这么可怕,那么同为有机质的胃壁为什么没有被胃液消化,这也是初期科学家质疑胃酸存在的原因之一。
2. 为什么胃酸能消灭一切进入胃中的微生物,胃炎、胃溃疡等疾病还会不断发生,它们是什么引起的呢?
问题一,随着解剖学的发展,人们发现,人的新陈代谢需要与外环境进行物质交换,而我们的整个消化道,从口腔到肛门在医学和生物学中都是外环境,消化道直接暴露在外环境中,而为了抵御外环境的直接损伤,我们的消化道细胞会分泌一层紧密脂类蛋白——粘膜,将消化道细胞与外环境隔绝开来,而为了抵御胃酸,胃部的粘膜更为厚实,有足足一毫米厚 这层粘膜保护胃壁不被氢离子的酸性腐蚀,隔绝了外环境中的胃酸腐蚀胃。
问题二,根据上一问题,胃部的疾病一般也是这层粘膜的不稳定造成的,一些外界的因素导致了这层粘膜的缺失,胃液就会接触到胃壁细胞,导致破损引发胃炎,并导致胃溃疡等一系列问题。 参考技术C 我觉得是不可能确保3秒的,先不说程序本身,从接收请求,到进入切面,到切面处理,最后返回数据这一过程,运行完就需要耗费一定的时间。而且我看你的线程池深度只 参考技术D 关于Java中spring AOP的一些基本概念,你能给我一份你的PPT吗?其中很多我都不懂。非常感谢。有时效果不是很好。当我学习的时候,我看到了马兵的视频。电驴资源齐全。PPT不是很好。下一个文档是最好的示例。百度图书馆直接搜索“;春季AOP“有很多。 第5个回答 2022-08-17 AOP的底层本身利用了java的反射,要消耗一定的时间,你这个3秒从何得出
加上网络方面的延迟,这个3秒不一定可靠啊追问
感谢您的回答,我其实是想问有没有能大约限制总调用时长在3秒多的思路,因为压测甚至会达到6s的时间,差距太大了。这个方法耗时约3s我能控制住,其他耗时我不知道哪里发生的。
Java实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)
三、Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)
1、AOP概念及原理
1.1、什么是AOP
OOP:Object Oriented Programming面向对象编程
AOP:Aspect Oriented Programming面向切面编程
1.2、代理
充分理解:间接
主要作用:拦截被代理对象执行的方法,同时对方法进行增强。
1.2.1、静态代理
特点:代理类是一个真实存在的类。装饰者模式就是静态代理的一种体现形式。
1.2.2、动态代理
特点:字节码是随用随创建,随用随加载。是在运行期间生成的一个类。
a、基于接口的动态代理
提供者:JDK官方的Proxy类。
要求:被代理类必须实现一个或多个接口。
b、基于子类的动态代理
提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。
要求:被代理类必须是一个子类(不能用final修饰的(最终类),其余类都没问题,因为都是Object的子类)。
注意:在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
1.3、通过动态代理技术实现AOP思想
1.3.1、回顾转账案例事务支持的四个版本
a、不使用事务
b、持久层介入了业务层处理(越权操作)
c、业务层控制事务,但是出现了持久层的接口,类和相关代码
d、使用ThreadLocal进行解耦
e、借助动态代理技术,实现面向切面编程思想来控制事务
2、Spring中的AOP
2.1、基本概念
连接点(Joinpoint):连接点指类中的核心业务方法
切入点(Pointcut):
连接点不一定是切入点,但切入点一定是连接点。
通知(Advice)
通知就是指那些有公用代码的类。
通知中有好多的方法:通知类型。前置通知|后置通知|异常通知|最终通知|环绕通知
目标对象(Target Object)
AOP代理(AOP Proxy)
织入(Weaving):把通知运行期间插入到核心代码中运行,过程叫做织入
切面(Aspect):公用代码所关心的某一方面(事务是一个方面;日志也是一个方面)
2.2、具体配置
2.2.1、Spring中的AOP
a、开发阶段(我们做的)
1) 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
2) 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
3) 在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
b、运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
2.2.2、基于XML的配置
2.2.2.1、AOP的基本配置
a、导入AOP相关的jar包(Spring的AOP是基于IoC的,所以IoC的jar也必须存在)
AOP有关的jar包:4个
b、引入aop名称空间
c、配置切面
1 <!-- AOP的配置 --> 2 <aop:config> 3 <!-- 配置所有切面通用的切入点表达式 --> 4 <aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt1"/> 5 6 <!-- 配置切面:切面就是关心的某一个方面 7 id:指定的是通知的唯一标识 8 ref:指定的通知的bean的id 9 --> 10 <aop:aspect id="loggerAdvice" ref="logger"> 11 <!-- 抽取当前切面的通用切入点表达式 12 expression属性:指定的就是切入点表达式 13 id属性:是把切入点表达式赋予唯一标识 14 15 <aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt1"/> 16 --> 17 <!-- 配置通知的类型:指的就是在业务核心方法的什么时候执行 18 aop:before:指定的是前置通知 19 20 method属性:指的是通知里面的哪个方法执行 21 pointcut属性:指定的是切入点表达式。表明从哪里切入业务主线。 22 把哪些连接点变成切入点。 23 切入点表达式: 24 关键字:execution(表达式) 25 表达式语法: 26 访问修饰符 返回值 包名.类名.方法名(参数列表) 27 全匹配方式: 28 execution(public void cn.itcast.service.impl.UserServiceImpl.saveUser()) 29 访问修饰符可以不写: 30 execution(void cn.itcast.service.impl.UserServiceImpl.saveUser()) 31 返回值可以使用通配符:通配符* 32 execution(* cn.itcast.service.impl.UserServiceImpl.saveUser()) 33 包名可以使用通配符:当使用通配符时,有几级包,写几个* 34 execution(* *.*.*.*.UserServiceImpl.saveUser()) 35 包名称可以使用..,表明的是当前包及其子包 36 execution(* cn..UserServiceImpl.saveUser()) 37 类名可以使用通配符: 38 execution(* cn..*.saveUser()) 39 方法名可以使用通配符: 40 execution(* cn..*.*()) 41 参数列表可以指定类型:注意的是,如果是基本类型直接写类型名称,如果是引用类型,需要些包名.类名。lang也必须写。例如:java.lang.String 42 execution(* *..*.*(int)) 43 参数列表可以使用通配符:注意:不能表示有无参数均可。只能表示参数的数据类型是任意的 44 execution(* *..*.*(*)) 45 有无参数均可,可以使用..来表示: 46 execution(* *..*.*(..)) 47 最简版: 48 execution(* *..*.*(..)) 49 建议的写法: 50 切到业务层具体实现类即可: 51 execution(* cn.itcast.service.impl.*.*(..)) 52 53 pointcut-ref:指定的是通用切入点表达式的唯一标识 54 --> 55 <aop:before method="printLog" pointcut-ref="pt1"/> 56 </aop:aspect> 57 </aop:config>
2.2.2.2、切入点表达式
execution:匹配方法的执行(常用)execution(public * *(..))
基本语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
within:匹配包或子包中的方法(了解) within(cn.itcast.aop..*)
this:匹配实现接口的代理对象中的方法(了解) this(cn.itcast.aop.user.UserDAO)
target:匹配实现接口的目标对象中的方法(了解) target(cn.itcast.aop.user.UserDAO)
args:匹配参数格式符合标准的方法(了解) args(int,int)
Spring支持使用如下三个逻辑运算符来组合切入点表达式:
&&:要求连接点同时匹配两个切点表达式
||:要求连接点匹配至少一个切入点表达式
!:要求连接点不匹配指定的切入点表达式
2.2.2.3、切入点的配置方式
方式1:
方式2:
方式3:
1 <aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt1"/> 2 <aop:before method="printLog" pointcut-ref="pt1"/>
2.2.2.4、通知的类型
1 <!-- 配置所有切面通用的切入点表达式 --> 2 <aop:pointcut expression="execution(* cn.itcast.service.impl.*.*(..))" id="pt1"/> 3 <aop:aspect id="loggerAdvice" ref="logger"> 4 <!-- 指定通知的类型--> 5 <!-- 前置通知:永远在切入点方法执行之前执行 6 它可以获取切入点方法的参数 7 8 <aop:before method="beforePrintLog" pointcut-ref="pt1"/> --> 9 10 <!-- 后置通知:切入点方法正确执行之后执行。它和例外通知只会有一个执行 11 它可以获取切入点方法的返回值 12 除了在通知的方法中提供一个参数之外,还需要在配置文件中提供一个属性 13 returning属性:指定的是后置通知中方法参数的变量名称,严格区分大小写。 14 15 <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1" returning="rtValue"/>--> 16 17 <!-- 例外通知:切入点方法执行时产生异常后执行。它和后置通知只会有一个执行 18 除了在通知的方法中提供一个Throwable类型的参数之外,还需要在配置文件中提供一个属性 19 throwing属性:指定的是例外通知中方法参数的变量名称。严格区分大小写。 20 --> 21 <aop:after-throwing method="afterThrowingprintLog" pointcut-ref="pt1" throwing="th"/> 22 23 <!-- 最终通知:无论切入点方法是否正确执行了,它都会在最后执行 24 <aop:after method="printLog" pointcut-ref="pt1"/>--> 25 26 <!-- 环绕通知:详情请见Logger类中的注释 27 <aop:around method="aroundPrintLog" pointcut-ref="pt1"/>--> 28 </aop:aspect>
1 /** 2 * 前置通知:它可以获取切入点方法的参数 3 * Spring框架给我们提供了一个接口:JoinPoint 4 * 在程序执行到前置通知方法时,spring框架会为我们注入该接口的实现类。 5 * 该接口中有一个方法: 6 * Object[] args = jp.getArgs(); 7 */ 8 public static void beforePrintLog(JoinPoint jp){ 9 Object[] args = jp.getArgs(); 10 System.out.println(Arrays.toString(args)); 11 System.out.println("Logger的beforePrintLog方法开始记录日志。。。。。。。"); 12 } 13 14 /** 15 * 后置通知:它可以获取切入点方法的返回值 16 * 需要我们在后置通知的方法中提供一个参数,参数的类型是Object 17 * 参数的变量名称必须和配置文件中的returning属性保持一致 18 */ 19 public static void afterReturningPrintLog(Object rtValue){ 20 System.out.println(rtValue); 21 System.out.println("Logger的afterReturningPrintLog方法开始记录日志。。。。。。。"); 22 } 23 24 /** 25 * 例外通知:它可以获取切入点方法的异常对象 26 * 需要我们在例外通知的方法中提供一个参数,参数的类型是Throwable 27 * 参数的变量名称必须和配置文件中的throwing属性保持一致 28 */ 29 public static void afterThrowingprintLog(Throwable th){ 30 System.out.println("异常信息是:"+th); 31 System.out.println("Logger的afterThrowingprintLog方法开始记录日志。。。。。。。"); 32 } 33 34 /** 35 * 环绕通知详细说明: 36 * 它不是在配置切入点方法的前后执行,而是spring给我们提供的一种方式。让我们自己决定该通知是前置还是后者,例外还是最终。 37 * 问题: 38 * 当我们执行时,只会看见通知方法的语句输出,而看不到业务核心方法的输出语句。 39 * 分析原因: 40 * 在我们使用动态代理控制事务时,有明确的一句话调用业务核心方法:method.invoke(obj,args); 41 * 而此时我们没有明确的调用业务核心方法。 42 * 所以只能看到通知的语句输出,而无法看到业务核心方法的语句输出。 43 * 解决办法: 44 * Spring给我们提供的一个接口:ProceedingJoinPoint 45 * 当Spring框架在执行通知时,会为我们提供该接口的实现类,我们在使用时,直接用即可。无须关心谁给我们提供。 46 * ProceedingJoinPoint接口有一个方法:proceed(),它就相当于method的invoke方法。 47 * pjp.proceed(); ======== method.invoke(obj,args); 48 * 49 */ 50 public static void aroundPrintLog(ProceedingJoinPoint pjp){ 51 try { 52 // System.out.println("Logger的aroundPringLog方法开始记录日志。。。。。。。"); 53 pjp.proceed(); 54 // System.out.println("Logger的aroundPringLog方法开始记录日志。。。。。。。"); 55 } catch (Throwable e) { 56 // System.out.println("Logger的aroundPringLog方法开始记录日志。。。。。。。"); 57 e.printStackTrace(); 58 }finally{ 59 System.out.println("Logger的aroundPringLog方法开始记录日志。。。。。。。"); 60 } 61 62 }
注意:相同类型的通知,执行的先后顺序是由配置文件的前后顺序决定的。
2.2.2.5、前置通知获取参数
2.2.2.6、后置通知获取返回值
2.2.2.7、异常通知获取异常对象
2.2.3、基于注解的配置
2.2.3.1、使用Spring注解进行AOP配置的前提
a、资源交给Spring管理(核心业务对象,通知对象)
注意:代码中请使用@Compoment注解。
b、AOP有关的注解配置
开启Spring对@AspectJ注解的支持
c、常用的AOP注解
@Aspect 配置切面
@Before:前置通知
@AfterReturning:后置通知
@AfterThrowing:异常通知
@After:最终通知
@Around:环绕通知
d、配置一个前置通知的例子
2.2.3.2、切入点的配置
a、局部的:只能当前方法使用
b、使用@Pointcut:配置公用的切入点
要求:声明在无返回值方法上方,方法名即切入点名称,方法最好声明为private
c、配置全局的
2.2.3.3、前置通知获取参数
2.2.3.4、后置通知获取返回值
2.2.3.5、异常通知获取异常对象
2.2.3.6、多个相同类型的通知执行顺序
核心业务代码:
以上是关于Java Spring 切面 aop 超时?的主要内容,如果未能解决你的问题,请参考以下文章
Java实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)