SpringAop日志找不到方法的处理
springaop日志找不到方法
错误截图:
显示没有找到该方法,于是我找到对应的类和对应的方法:
这里我用了反射来获取方法名和参数:
错误打印的结果显示方法名获取没有错误,于是我查看参数的类型是否有错
结果一个都对不上…
int类型反射得到的class:
integer反射得到的class:
…终于知道之前错误里的ljavexxxx是哪里来的了…
由于model是一个接口
model反射的class得到的是他的子类org.springframework.validation.support.bindingawaremodelmap:
所以参数类型对不上号导致了错误的产生
解决方法:
将int类型的参数改为integer(以后都写integer,舍弃int!),将参数里的model删去,将方法返回值改为modeandview,一样实现页面的跳转和参数的传递:
运行代码:
运行okkkk~
springboot用springaop实现日志记录功能
背景:
我需要在一个springboot的项目中的每个controller加入一个日志记录,记录关于请求的一些信息。
代码类似于:
logger.info(request.getrequesturl());
之类的。
代码不难,但由于controller的数量不少,干起来也是体力活。所以想到了用spring aop来解决这个问题。
首先,在pom中加入springaop的相关依赖:
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency>
上一篇我们说到,如果要直接用@aspect注解的话,要在spring的配置文件中加入
<aop:aspectj-autoproxy />
那么我们这里要不要在程序的主类中增加@enableaspectjautoproxy来启用呢? 实际并不需要,可以看下面关于aop的默认配置属性,其中spring.aop.auto属性默认是开启的,也就是说只要引入了aop依赖后,默认已经增加了
@enableaspectjautoproxy
好的也就是说,只要引入springaop相关的jar包依赖,我们就可以开始相关的aspet的编程了。
这里直接上代码,然后再做解释:
首先是包结构的图:
这里涉及到接收请求的controller的包有两个,com.stupayment.controller还有com.stupayment.uicontroller
然后看我们的切面类weblogaspect类的代码:
package com.stupayment.util; import java.util.arrays; import javax.servlet.http.httpservletrequest; import org.aspectj.lang.joinpoint; import org.aspectj.lang.proceedingjoinpoint; import org.aspectj.lang.annotation.after; import org.aspectj.lang.annotation.afterreturning; import org.aspectj.lang.annotation.around; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; import org.aspectj.lang.annotation.pointcut; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.stereotype.component; import org.springframework.web.context.request.requestattributes; import org.springframework.web.context.request.requestcontextholder; import org.springframework.web.context.request.servletrequestattributes; @aspect @component public class weblogaspect { private final logger logger = loggerfactory.getlogger(weblogaspect.class); @pointcut("execution(public * com.stupayment.controller..*.*(..))")//切入点描述 这个是controller包的切入点 public void controllerlog(){}//签名,可以理解成这个切入点的一个名称 @pointcut("execution(public * com.stupayment.uicontroller..*.*(..))")//切入点描述,这个是uicontroller包的切入点 public void uicontrollerlog(){} @before("controllerlog() || uicontrollerlog()") //在切入点的方法run之前要干的 public void logbeforecontroller(joinpoint joinpoint) { requestattributes requestattributes = requestcontextholder.getrequestattributes();//这个requestcontextholder是springmvc提供来获得请求的东西 httpservletrequest request = ((servletrequestattributes)requestattributes).getrequest(); // 记录下请求内容 logger.info("################url : " + request.getrequesturl().tostring()); logger.info("################http_method : " + request.getmethod()); logger.info("################ip : " + request.getremoteaddr()); logger.info("################the args of the controller : " + arrays.tostring(joinpoint.getargs())); //下面这个getsignature().getdeclaringtypename()是获取包+类名的 然后后面的joinpoint.getsignature.getname()获取了方法名 logger.info("################class_method : " + joinpoint.getsignature().getdeclaringtypename() + "." + joinpoint.getsignature().getname()); //logger.info("################target: " + joinpoint.gettarget());//返回的是需要加强的目标类的对象 //logger.info("################this: " + joinpoint.getthis());//返回的是经过加强后的代理类的对象 } }
针对这个切面类,来展开说明@aspect切面类的编程。
@aspect和@component
首先,这个@aspect注释告诉spring这是个切面类,然后@compoment将转换成spring容器中的bean或者是代理bean。 总之要写切面这两个注解一起用就是了。
既然是切面类,那么肯定是包含pointcut还有advice两个要素的,下面对几个注解展开讲来看看在@aspect中是怎么确定切入点(pointcut)和增强通知(advice)的。
@pointcut
这个注解包含两部分,pointcut表达式和pointcut签名。表达式是拿来确定切入点的位置的,说白了就是通过一些规则来确定,哪些方法是要增强的,也就是要拦截哪些方法。
@pointcut(...........)括号里面那些就是表达式。这里的execution是其中的一种匹配方式,还有:
execution
: 匹配连接点
within
: 某个类里面
this
: 指定aop代理类的类型
target
:指定目标对象的类型
args
: 指定参数的类型
bean
:指定特定的bean名称,可以使用通配符(spring自带的)
@target
: 带有指定注解的类型
@args
: 指定运行时传的参数带有指定的注解
@within
: 匹配使用指定注解的类
@annotation
:指定方法所应用的注解
注意,由于是动态代理的实现方法,所以不是所有的方法都能拦截得下来,对于jdk代理只有public的方法才能拦截得下来,对于cglib只有public和protected的方法才能拦截。
这里我们主要介绍execution的匹配方法,因为大多数时候都会用这个来定义pointcut:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))
除了返回类型,方法名还有参数之外,其他都是可选的
ret-type-pattern:可以为*表示任何返回值,全路径的类名等.
name-pattern:指定方法名,*代表所以,set*,代表以set开头的所有方法.
parameters pattern:指定方法参数(声明的类型), ()匹配没有参数; (..)代表任意多个参数; (*)代表一个参数,但可以是任意型; (*,string)代表第一个参数为任何值,第二个为string类型。
下面给几个例子:
1)execution(public * *(..))——表示匹配所有public方法
2)execution(* set*(..))——表示所有以“set”开头的方法
3)execution(* com.xyz.service.accountservice.*(..))——表示匹配所有accountservice接口的方法
4)execution(* com.xyz.service.*.*(..))——表示匹配service包下所有的方法
5)execution(* com.xyz.service..*.*(..))——表示匹配service包和它的子包下的方法
然后其他的匹配法要用的时候再百度吧~
然后是@pointcut的第二个部分,签名signature,也就是代码中的
@pointcut("execution(public * com.stupayment.uicontroller..*.*(..))")//切入点描述,这个是uicontroller包的切入点 public void uicontrollerlog(){}
像方法定义的这个public void uicontrollerlog(){}这个看起来像是方法定义的东西,就是签名,签名没有实际用处,只是用来标记一个pointcut,可以理解成这个切入点的一个记号。
@before
这个是决定advice在切入点方法的什么地方执行的标签,这个注解的意思是在切入点方法执行之前执行我们定义的advice。
@before("controllerlog() || uicontrollerlog()") //在切入点的方法run之前要干的 public void logbeforecontroller(joinpoint joinpoint) {
@before注解括号里面写的是一个切入点,这里看见切入点表达式可以用逻辑符号&&,||,!来描述。 括号里面也可以内置切点表达式,也就是直接写成:
@before("execution(public * com.stupayment.uicontroller..*.*(..))")
跟写成@before("uicontrollerlog()")的效果是一样的。
然后看到注解下面的方法,就是描述advice的,我们看到有个参数joinpoint,这个东西代表着织入增强处理的连接点。joinpoint包含了几个很有用的参数:
- object[] getargs:返回目标方法的参数
- signature getsignature:返回目标方法的签名
- object gettarget:返回被织入增强处理的目标对象
- object getthis:返回aop框架为目标对象生成的代理对象
除了注解@around的方法外,其他都可以加这个joinpoint作参数。@around注解的方法的参数一定要是proceedingjoinpoint,下面会介绍。
@after
这个注解就是在切入的方法运行完之后把我们的advice增强加进去。一样方法中可以添加joinpoint。
@around
这个注解可以简单地看作@before和@after的结合。这个注解和其他的比比较特别,它的方法的参数一定要是proceedingjoinpoint,这个对象是joinpoint的子类。我们可以把这个看作是切入点的那个方法的替身,这个proceedingjoinpoint有个proceed()方法,相当于就是那切入点的那个方法执行,简单地说就是让目标方法执行,然后这个方法会返回一个对象,这个对象就是那个切入点所在位置的方法所返回的对象。
除了这个proceed方法(很重要的方法),其他和那几个注解一样。
@afterreturning
顾名思义,这个注解是在目标方法正常完成后把增强处理织入。这个注解可以指定两个属性(之前的三个注解后面的括号只写一个@pointcut表达式,也就是只有一个属性),一个是和其他注解一样的pointcut表达式,也就是描述该advice在哪个接入点被织入;然后还可以有个returning属性,表明可以在advice的方法中有目标方法返回值的形参。
@afterreturning(returning = "returnob", pointcut = "controllerlog() || uicontrollerlog()") public void doafterreturning(joinpoint joinpoint, object returnob) { system.out.println("##################### the return of the method is : " + returnob); }
浏览器发出一个请求后,效果截图:
(这里是一个请求登录界面的请求,所以uicontroller返回一个string作为视图。)
@afterthrowing
异常抛出增强,在异常抛出后织入的增强。有点像上面的@afterreturning,这个注解也是有两个属性,pointcut和throwing。
用法也和刚刚的那个returning差不多:
@afterthrowing(pointcut = "controllerlog() || uicontrollerlog()", throwing = "ex") public void doafterthrowing(joinpoint joinpoint, exception ex) { string methodname = point.getsignature().getname(); list<object> args = arrays.aslist(point.getargs()); system.out.println("连接点方法为:" + methodname + ",参数为:" + args + ",异常为:" + ex); }
好了现在注解都介绍完了,这里还要提到上面用到的一个类:requestcontextholder
比如说,有个需求需要在service中获得request和response,我们一般会(我就是)直接在controller那把request或response作为参数传到service,这就很不美观。后来知道,原来springmvc提供了个很强大的类reqeustcontextholder,通过他你就可以获得request和response什么的。
//下面两个方法在没有使用jsf的项目中是没有区别的 requestattributes requestattributes = requestcontextholder.currentrequestattributes(); // requestcontextholder.getrequestattributes(); //从session里面获取对应的值 string str = (string) requestattributes.getattribute("name",requestattributes.scope_session); httpservletrequest request = ((servletrequestattributes)requestattributes).getrequest(); httpservletresponse response = ((servletrequestattributes)requestattributes).getresponse();
好了完成了这个切面的编程后,你就成功把日志功能切入到各个controller中了。看个效果图。
最后,再记录一下各个不同的advice的拦截顺序的问题。
情况一,只有一个aspect类:
无异常:@around(proceed()之前的部分) → @before → 方法执行 → @around(proceed()之后的部分) → @after → @afterreturning
有异常:@around(proceed(之前的部分)) → @before → 扔异常ing → @after → @afterthrowing (大概是因为方法没有跑完抛了异常,没有正确返回所有@around的proceed()之后的部分和@afterreturning两个注解的加强没有能够织入)
情况二,同一个方法有多个@aspect类拦截:
单个aspect肯定是和只有一个aspect的时候的情况是一样的,但不同的aspect里面的advice的顺序呢??答案是不一定,像是线程一样,没有谁先谁后,除非你给他们分配优先级,同样地,在这里你也可以为@aspect分配优先级,这样就可以决定谁先谁后了。
优先级有两种方式:
- 实现org.springframework.core.ordered接口,实现它的getorder()方法
- 给aspect添加@order注解,该注解全称为:org.springframework.core.annotation.order
不管是哪种,都是order的值越小越先执行:
@order(5) @component @aspect public class aspect1 { // ... } @order(6) @component @aspect public class aspect2 { // ... }
这样aspect1就永远比aspect2先执行了。
注意点:
- 如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的 advice(比如,定义了两个 @before),那么这两个 advice 的执行顺序是无法确定的,哪怕你给这两个 advice 添加了 @order 这个注解,也不行。这点切记。
- 对于@around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下 pjp.proceed();否则,controller 中的接口将没有机会被执行,从而也导致了 @before这个advice不会被触发。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。