依赖引入springboot引入aop依赖包后,一般来说是不需要再做其他配置了,在比较低的版本或者有其他配置影响了aop的相关功能,导致aop功能不生效,可以试试在启动类上增加@enableaspectjautoproxy来启用;
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid></dependency>
代码实现1、自定义注解@testaop
@target(elementtype.method)@retention(retentionpolicy.runtime)public @interface testaop {}
2、exampleaop .java
@component@aspect@slf4jpublic class exampleaop { //切入点:增强标有@testaop注解的方法 @pointcut(value = "@annotation(testaop)") //切入点签名 public void pointcut() { system.out.println("pointcut签名。。。"); } //前置通知 @before("pointcut()") public void debefore(joinpoint joinpoint) throws throwable { log.info("前置通知被执行"); //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作; } //返回通知 @afterreturning(returning = "ret", pointcut = "pointcut()") public void doafterreturning(object ret) throws throwable { log.info("返回通知被执行"); //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作; } //异常通知 @afterthrowing(throwing = "ex", pointcut = "pointcut()") public void throwss(joinpoint jp, exception ex) { log.info("异常通知被执行"); //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作; //可以从ex中获取到具体的异常信息 } //后置通知 @after("pointcut()") public void after(joinpoint jp) { log.info("后置通知被执行"); //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作; } @around("pointcut()") public object around(proceedingjoinpoint proceedingjoinpoint) throws throwable { log.info("------环绕通知 start"); string methodname = proceedingjoinpoint.getsignature().getname(); string classname = proceedingjoinpoint.gettarget().getclass().getname(); object[] args = proceedingjoinpoint.getargs(); string argsname = null; stringbuilder sb = new stringbuilder(); if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { sb.append(";").append(args[i].getclass().getname()); } } if (sb.tostring().length() > 0) { argsname = sb.tostring().substring(1); } } log.info("命中类:{},方法{},参数{};", classname, methodname, argsname); object proceed = proceedingjoinpoint.proceed(); log.info("------环绕通知 end"); return proceed; } }
核心注解和类1、aspect,表示当前类是一个切面类,简单理解就是切入点和通知的抽象封装,表述的是切入点和通知方法之间的对应关系;
@before注解标记的方法将在方法被切入执行前执行,称为前置通知
3、@after,后置通知,用于方法上,被@after注解标记的方法会在被切入方法执行之后执行;
4、@afterreturning,返回通知,用于方法上,被@afterreturning注解标记的方法会在被切入方法返回结果之后执行;
6、@afterthrowing:异常通知,用于方法上,被@afterthrowing注解标记的方法会在被切入方法抛出异常之后执行,一般用于有目的的获取异常信息;
7、@aroud:环绕通知,用于方法上,被@around注解标记的方法会在被切入方法执行前后执行;
8、@pointcut,切入点,标记在方法上,用于定义切入点,所谓的切入点是指对哪些连接点处进行切入增强的定义,在spring中具体就是指对哪些方法进行切入增强的定义;被@pointcut注解表示切入点的表达式有多种,最常用的是两种,execution表达式和注解;
9、jointpoint,连接点,所谓的连接点是指被aop切面切入的位置点,在spring中具体就是指被切入的方法;
10、pointcut,
11、advice,通知,所谓的通知是指对定义好的切入点进行拦截后,要具体做哪些操作的定义;在spring中就是指被@before、@after、@afterreturning、@afterthrowing、@around注解标记的方法;
标记切入点的常用方式1、execution表达式
表达式请法:访问修饰符 返回值 包名.包名...类名.方法(参数列表)
示例1:表示匹配所有com.fanfu包以及子包下的所有类中以add开头的方法,返回值、参数不限;
@pointcut("execution(* com.fanfu..*.*.add*(..))")
示例2:表示匹配所有com.fanfu包以及子包下的所有类中以add开头,参数类型是string的方法,返回值不限;
@pointcut("execution(* com.fanfu..*.*.add*(string))")
示例3:表示匹配com.fanfu包下任意类、任意方法、任意参数;
@pointcut("execution(* com.fanfu..*.*.*(string))")
execution()为表达式的主体;
第一个*表示返回值类型为任意,即不限制返回值类型;
包后的*表示当前包,包后面连续两个..表示当前包以及子包;
(..)表示任意参数;
最后的*.*(..)表示匹配任意类、任意方法、任意参数;
2、注解
注解语法:@annotation(自定义的注解)
示例:表示匹配所有标记@testaop注解的方法;
@pointcut("@annotation(com.fanfu.config.testaop)")
spring aop的小技巧每一个@pointcut可以使用execution或注解来定义切入点,多个切点之间还可以使用逻辑运算符,如&&、||、!运算;
1、point1()&&point2()表示命中point1和point2的所有切入点的交集;如示例:com.fanfu包以及下属所有子包的所有类中,方法名是以add开头,参数类型是string的所有方法,与com.fanfu.service包以及下属所有子包的所有类中,不限方法名和参数类型的所有方法取交集,即com.fanfu.service包以及下属所有子包的所有类中,方法或是add1或add2的方法,在调用前后都会执行环绕通知around()方法内的逻辑;
@component@aspect@slf4jpublic class exampleaop { @pointcut("execution(* com.fanfu..*.*.add*(string))") public void point1() { } @pointcut("execution(* com.fanfu.service..*.*(..))") public void point2() { } @pointcut("point1()&&point2()") public void point() { } @around("point()") public object around(proceedingjoinpoint proceedingjoinpoint) throws throwable { log.info("------around start"); object proceed = proceedingjoinpoint.proceed(); log.info("------around end"); return proceed; }}
2、point1()&&point2()表示命中point1和point2的所有切入点的并集;如示例:com.fanfu.service包以及下属所有子包的所有类中,方法名是add1,参数类型是string的所有方法,与com.fanfu.controller包以及下属所有子包的所有类中,方法名是add2,参数类型是string的所有方法取并集,即com.fanfu.service或com.fanfu.controller的包以及下属所有子包的所有类中,方法或是add1或add2的方法,在调用前后都会执行环绕通知around()方法内的逻辑;
@component@aspect@slf4jpublic class exampleaop { @pointcut("execution(* com.fanfu.service..*.add*(string))") public void point1() { } @pointcut("execution(* com.fanfu.controller..*.*.add*(string))") public void point2() { } @pointcut("point1()||point2()") public void point() { } @around("point()") public object around(proceedingjoinpoint proceedingjoinpoint) throws throwable { log.info("------around start"); object proceed = proceedingjoinpoint.proceed(); log.info("------around end"); return proceed; }}
3、!point()表示命中point()的所有切入点取反,如示例:com.fanfu.service包及下属所有子包的所有类中,不是以add开头的方法,在调用前后都会执行环绕通知around()方法内的逻
@component@aspect@slf4jpublic class exampleaop { @pointcut("execution(* com.fanfu.service..*.add*(string))") public void point() { } @around("!point()") public object around(proceedingjoinpoint proceedingjoinpoint) throws throwable { log.info("------around start"); object proceed = proceedingjoinpoint.proceed(); log.info("------around end"); return proceed; }}
spring aop注意事项1、与定义的切点匹配方法,如果在当前调用链中,方法在当前类是首次匹配则会命中,即执行相关的通知,如果当前的调用链没有结束,又在当前方法里调用了当前类的与其他切入点匹配方法,则不会再命中,即其他与切入点匹配的方法执行的时候不会再触发相关的通知;
如下示例:
当请求http://localhost:8080/example时,examplecontroller中的example方法被触发,examplecontroller#example()又调用了exampleservice#test(),在exampleservice#test()内部,又顺序调用了exampleservice#test1()和exampleservice#test2();在exampleaop中,按照execution中的配置,是可以匹配到test()、test1()、test2(),实际是命中的方法只有test();
@component@aspect@slf4jpublic class exampleaop { @pointcut("execution(* com.fanfu.service.impl.exampleserviceimpl.test*(..))") public void point2() { log.info("切入点匹配"); } @around("point()") public object around(proceedingjoinpoint proceedingjoinpoint) throws throwable { log.info("------around start"); string methodname = proceedingjoinpoint.getsignature().getname(); string classname = proceedingjoinpoint.gettarget().getclass().getname(); object[] args = proceedingjoinpoint.getargs(); string argsname=null; stringbuilder sb=new stringbuilder(); if (args!=null&&args.length>0) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { sb.append(";").append(args[i].getclass().getname()); } } if (sb.tostring().length()>0) { argsname=sb.tostring().substring(1); } } log.info("命中类:{},方法{},参数{};",classname,methodname,argsname); object proceed = proceedingjoinpoint.proceed(); log.info("------around end"); return proceed; }}
@service@slf4jpublic class exampleserviceimpl implements iexampleservice { @override public string test(string msg) { log.info("test方法被执行"); this.test1(msg); this.test2(msg); return msg; } public string test1(string msg) { log.info("test1方法被执行"); return "msg1"; } public string test2(string msg) { log.info("test2方法被执行"); return "msg2"; }}
public interface iexampleservice { public string test(string msg); public string test1(string msg); public string test2(string msg);}
@restcontroller@slf4jpublic class examplecontroller { @autowired private iexampleservice exampleservice; @getmapping("/example") public string example() { log.info("example start"); exampleservice.test(null); log.info("example end"); return string.valueof(system.currenttimemillis()); }}
2、对于上面的问题,如果把execution表达换成注解,会不会结果不一样?再把exampleaop中的@pointcut改成注解形式,再在exampleservice#test1()、exampleservice#test2()、exampleservice#test()添加注解@testaop,验证结果依然是一样的,只有test()会命中,其他不会!所以要注意呀。
@component@aspect@slf4jpublic class exampleaop { @pointcut("@annotation(testaop)") public void point() { } @around("point()") public object around(proceedingjoinpoint proceedingjoinpoint) throws throwable { log.info("------around start"); string methodname = proceedingjoinpoint.getsignature().getname(); string classname = proceedingjoinpoint.gettarget().getclass().getname(); object[] args = proceedingjoinpoint.getargs(); string argsname = null; stringbuilder sb = new stringbuilder(); if (args != null && args.length > 0) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { sb.append(";").append(args[i].getclass().getname()); } } if (sb.tostring().length() > 0) { argsname = sb.tostring().substring(1); } } log.info("命中类:{},方法{},参数{};", classname, methodname, argsname); object proceed = proceedingjoinpoint.proceed(); log.info("------around end"); return proceed; }}
@service@slf4jpublic class exampleserviceimpl implements iexampleservice { @override @testaop public string test(string msg) { log.info("test方法被执行"); this.test1(msg); this.test2(msg); return msg; } @override @testaop public string test1(string msg) { log.info("test1方法被执行"); return "msg1"; } @override @testaop public string test2(string msg) { log.info("test2方法被执行"); return "msg2"; } }
3、那什么情况下,exampleservice#test1()、exampleservice#test2()、exampleservice#test()会同时命中呢?让从examplecontroller#example()到exampleservice#test1()、exampleservice#test2()、exampleservice#test()分别在不同的调用链上,那么就可以同时命中了;
@restcontroller@slf4jpublic class examplecontroller { @autowired private iexampleservice exampleservice; @getmapping("/example") public string example() { log.info("example start"); exampleservice.test(null); exampleservice.test1(null); exampleservice.test2(null); log.info("example end"); return string.valueof(system.currenttimemillis()); }}
以上就是springboot项目如何快速实现aop功能的详细内容。