Spring系列(四):Spring AOP详解
一、aop是什么
aop(面向切面编程),可以说是一种编程思想,其中的spring aop和aspectj都是现实了这种编程思想。相对oop(面向过程编程)来说,提供了另外一种编程方式,对于oop过程中产生的横切性问题,这些横切性与业务无关,可以通过预编译方式和运行期动态代理来实现。比如可以应用在:日志记录、性能监控、事务管理等。
二、aop的基本概念
aspect(切面):通常来说是一个类,里面定义了切点和通知,spring aop中可以用@aspectj来标注这个类是切面;
join point(连接点):可以理解成目标对象中的方法,该方法是要被增强的方法,也就是我们要作用的一个切入点;
pointcut(切点):切点可以理解成连接点的集合;
target object(目标对象):被代理的对象,也就是目标对象;
aop proxy(代理对象):把被代理的对象织入了增强后的对象;
weaving(织入):把增强也就是代理逻辑加入到目标对象上的过程;
advice(通知):用于指定在特定连接点上的增强的位置;
① before advice(前置通知):在目标方法被调用之前调用通知;
② after returning advice(返回通知):在目标方法成功执行之后调用通知;
③ after throwing advice(异常通知):在目标方法抛出异常后调用通知;
④ after (finally) advice(后置通知):在目标方法完成之后调用通知(不论是否出现异常都会执行,finally中调用);
⑤ around advice(环绕通知):围绕连接点(目标方法)的通知。可以在方法调用前后执行自定义行为;
三、代理的实现方式
我们知道aop可以通过预编译的方式和运行期动态代理来实现,那么代理的实现方式有哪些呢?
我们定义一个接口类:userservice
package com.toby.service; /** * @desc: user 业务接口类 * @author: toby * @date: 2019/8/4 23:28 */ public interface userservice { /** * 添加 */ void add(); /** * say hello * @param name * @return */ string say(string name); }
在定义一个实现类userserviceimpl
package com.toby.service.impl; import com.toby.service.userservice; import org.springframework.stereotype.service; /** * @desc: user业务的实现类 * @author: toby * @date: 2019/8/4 23:29 */ @service public class userserviceimpl implements userservice { @override public void add() { system.out.println("执行userserviceimpl的add方法"); } @override public string say(string name) { system.out.println("执行userserviceimpl的say方法 args = " + name); return "hello " + name; } }
第一种:静态代理
定义一个静态代理类,需要实现userservice接口:
package com.toby.proxy; import com.toby.service.userservice; /** * @desc: 静态代理 * @author: toby * @date: 2019/8/4 23:30 */ public class staticproxy implements userservice { private userservice userservice; public staticproxy(userservice userservice){ this.userservice = userservice; } @override public void add() { system.out.println("添加日志开始"); userservice.add(); system.out.println("添加日志结束"); } @override public string say(string name) { return ""; } }
第二种:jdk动态代理
package com.toby.proxy; import java.lang.reflect.invocationhandler; import java.lang.reflect.method; import java.lang.reflect.proxy; /** * @desc: jdk动态代理 实现一个接口:invocationhandler * jdk的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现jdk的动态代理 * @author: toby * @date: 2019/8/4 23:34 */ public class jdkdynamicproxy implements invocationhandler { /** * 目标对象 */ private object targetobject; public object createjdkproxy(final object targetobject){ this.targetobject = targetobject; return proxy.newproxyinstance(this.targetobject.getclass().getclassloader(), this.targetobject.getclass().getinterfaces(),this); } @override public object invoke(object proxy, method method, object[] args) throws throwable { //写对应的增强代码 system.out.println("jdk日志记录开始"); //调用真正的业务方法 object obj = method.invoke(this.targetobject,args); system.out.println("jdk日志记录结束"); return obj; } }
第三种:cglib动态代理
package com.toby.proxy; import org.springframework.cglib.proxy.enhancer; import org.springframework.cglib.proxy.methodinterceptor; import org.springframework.cglib.proxy.methodproxy; import java.lang.reflect.method; /** * @desc: cglib动态代理 cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类, * 并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理 * @author: toby * @date: 2019/8/4 23:43 */ public class cglibdynamicproxy implements methodinterceptor { /** * 目标对象 */ private object targetobject; /** * 创建cglib动态代理 * @param targetobject * @return */ public object createcglibdynamicproxy(final object targetobject){ this.targetobject = targetobject; //cglib中的核心对象,该类用于生成代理对象 enhancer enhancer = new enhancer(); //指定委托类也就是目标对象为父类 enhancer.setsuperclass(this.targetobject.getclass()); //使用代理,需要一个对应的代理对象 enhancer.setcallback(this); return enhancer.create(); } @override public object intercept(object o, method method, object[] args, methodproxy methodproxy) throws throwable { system.out.println("cglib日志记录开始"); //委托类变成了父类。调用真正的服务提供者 object obj = methodproxy.invoke(this.targetobject,args); system.out.println("cglib日志记录结束"); return obj; } }
jdk代理的实现方式是基于接口实现,代理类继承proxy,实现接口。而cglib继承被代理的类来实现;这就是为什么jdk动态代理需要实现接口的原因?java是单继承
下面定义一个字节码生成器bytecodegenerator来一看究竟:
package com.toby.proxy.generator; import com.toby.service.impl.userserviceimpl; import org.springframework.cglib.proxy.enhancer; import org.springframework.cglib.proxy.methodinterceptor; import sun.misc.proxygenerator; import java.io.file; import java.io.fileoutputstream; import java.lang.reflect.modifier; import java.nio.file.files; /** * @desc: 字节码生成器 * @author: toby * @date: 2019/8/5 0:05 */ public class bytecodegenerator { /** * 根据目标对象生成字节码(jdk) * @param target * @param <t> * @return */ public static <t> byte[] generatorbytecodebyjdkproxy(t target){ int accessflags = modifier.public | modifier.final; byte [] codes = proxygenerator.generateproxyclass("proxy$"+target.getclass().getname(), target.getclass().getinterfaces(),accessflags); return codes; } /** * 根据目标对象生成字节码(cglib) * @param target * @param <t> * @return * @throws exception */ public static <t> byte[] generatorbytecodebycglib(final t target) throws exception { enhancer enhancer = new enhancer(); enhancer.setsuperclass(target.getclass()); enhancer.setcallback((methodinterceptor) (o, method, objects, methodproxy) -> methodproxy.invoke(target,objects)); enhancer.create(); byte [] codes = enhancer.getstrategy().generate(enhancer); return codes; } public static void main(string[] args) { /** * 测试jdk */ try { byte [] codes = bytecodegenerator.generatorbytecodebyjdkproxy(new userserviceimpl()); file file = new file(system.getproperty("user.dir")+"/spring-aop/target/proxy$userserviceimpl.class"); files.write(file.topath(),codes); } catch (exception e) { e.printstacktrace(); } /** * 测试cglib */ try { fileoutputstream out = new fileoutputstream(system.getproperty("user.dir")+"/spring-aop/target/cglib$userserviceimpl.class"); out.write(bytecodegenerator.generatorbytecodebycglib(new userserviceimpl())); out.flush(); out.close(); } catch (exception e) { e.printstacktrace(); } } }
jdk生成的动态代理字节码:
// // source code recreated from a .class file by intellij idea // (powered by fernflower decompiler) // package proxy$com.toby.service.impl; import com.toby.service.userservice; import java.lang.reflect.invocationhandler; import java.lang.reflect.method; import java.lang.reflect.proxy; import java.lang.reflect.undeclaredthrowableexception; public final class userserviceimpl extends proxy implements userservice { private static method m1; private static method m2; private static method m3; private static method m0; public userserviceimpl(invocationhandler var1) throws { super(var1); } public final boolean equals(object var1) throws { try { return (boolean)super.h.invoke(this, m1, new object[]{var1}); } catch (runtimeexception | error var3) { throw var3; } catch (throwable var4) { throw new undeclaredthrowableexception(var4); } } public final string tostring() throws { try { return (string)super.h.invoke(this, m2, (object[])null); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } public final void add() throws { try { super.h.invoke(this, m3, (object[])null); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } public final int hashcode() throws { try { return (integer)super.h.invoke(this, m0, (object[])null); } catch (runtimeexception | error var2) { throw var2; } catch (throwable var3) { throw new undeclaredthrowableexception(var3); } } static { try { m1 = class.forname("java.lang.object").getmethod("equals", class.forname("java.lang.object")); m2 = class.forname("java.lang.object").getmethod("tostring"); m3 = class.forname("com.toby.service.userservice").getmethod("add"); m0 = class.forname("java.lang.object").getmethod("hashcode"); } catch (nosuchmethodexception var2) { throw new nosuchmethoderror(var2.getmessage()); } catch (classnotfoundexception var3) { throw new noclassdeffounderror(var3.getmessage()); } } }
cglib生成动态代理的字节码:
// // source code recreated from a .class file by intellij idea // (powered by fernflower decompiler) // package com.toby.service.impl; import java.lang.reflect.method; import org.springframework.cglib.core.reflectutils; import org.springframework.cglib.core.signature; import org.springframework.cglib.proxy.callback; import org.springframework.cglib.proxy.factory; import org.springframework.cglib.proxy.methodinterceptor; import org.springframework.cglib.proxy.methodproxy; public class userserviceimpl$$enhancerbycglib$$b26297df extends userserviceimpl implements factory { private boolean cglib$bound; public static object cglib$factory_data; private static final threadlocal cglib$thread_callbacks; private static final callback[] cglib$static_callbacks; private methodinterceptor cglib$callback_0; private static object cglib$callback_filter; private static final method cglib$add$0$method; private static final methodproxy cglib$add$0$proxy; private static final object[] cglib$emptyargs; private static final method cglib$equals$1$method; private static final methodproxy cglib$equals$1$proxy; private static final method cglib$tostring$2$method; private static final methodproxy cglib$tostring$2$proxy; private static final method cglib$hashcode$3$method; private static final methodproxy cglib$hashcode$3$proxy; private static final method cglib$clone$4$method; private static final methodproxy cglib$clone$4$proxy; static void cglib$statichook2() { cglib$thread_callbacks = new threadlocal(); cglib$emptyargs = new object[0]; class var0 = class.forname("com.toby.service.impl.userserviceimpl$$enhancerbycglib$$b26297df"); class var1; method[] var10000 = reflectutils.findmethods(new string[]{"equals", "(ljava/lang/object;)z", "tostring", "()ljava/lang/string;", "hashcode", "()i", "clone", "()ljava/lang/object;"}, (var1 = class.forname("java.lang.object")).getdeclaredmethods()); cglib$equals$1$method = var10000[0]; cglib$equals$1$proxy = methodproxy.create(var1, var0, "(ljava/lang/object;)z", "equals", "cglib$equals$1"); cglib$tostring$2$method = var10000[1]; cglib$tostring$2$proxy = methodproxy.create(var1, var0, "()ljava/lang/string;", "tostring", "cglib$tostring$2"); cglib$hashcode$3$method = var10000[2]; cglib$hashcode$3$proxy = methodproxy.create(var1, var0, "()i", "hashcode", "cglib$hashcode$3"); cglib$clone$4$method = var10000[3]; cglib$clone$4$proxy = methodproxy.create(var1, var0, "()ljava/lang/object;", "clone", "cglib$clone$4"); cglib$add$0$method = reflectutils.findmethods(new string[]{"add", "()v"}, (var1 = class.forname("com.toby.service.impl.userserviceimpl")).getdeclaredmethods())[0]; cglib$add$0$proxy = methodproxy.create(var1, var0, "()v", "add", "cglib$add$0"); } final void cglib$add$0() { super.add(); } public final void add() { methodinterceptor var10000 = this.cglib$callback_0; if (var10000 == null) { cglib$bind_callbacks(this); var10000 = this.cglib$callback_0; } if (var10000 != null) { var10000.intercept(this, cglib$add$0$method, cglib$emptyargs, cglib$add$0$proxy); } else { super.add(); } } final boolean cglib$equals$1(object var1) { return super.equals(var1); } public final boolean equals(object var1) { methodinterceptor var10000 = this.cglib$callback_0; if (var10000 == null) { cglib$bind_callbacks(this); var10000 = this.cglib$callback_0; } if (var10000 != null) { object var2 = var10000.intercept(this, cglib$equals$1$method, new object[]{var1}, cglib$equals$1$proxy); return var2 == null ? false : (boolean)var2; } else { return super.equals(var1); } } final string cglib$tostring$2() { return super.tostring(); } public final string tostring() { methodinterceptor var10000 = this.cglib$callback_0; if (var10000 == null) { cglib$bind_callbacks(this); var10000 = this.cglib$callback_0; } return var10000 != null ? (string)var10000.intercept(this, cglib$tostring$2$method, cglib$emptyargs, cglib$tostring$2$proxy) : super.tostring(); } final int cglib$hashcode$3() { return super.hashcode(); } public final int hashcode() { methodinterceptor var10000 = this.cglib$callback_0; if (var10000 == null) { cglib$bind_callbacks(this); var10000 = this.cglib$callback_0; } if (var10000 != null) { object var1 = var10000.intercept(this, cglib$hashcode$3$method, cglib$emptyargs, cglib$hashcode$3$proxy); return var1 == null ? 0 : ((number)var1).intvalue(); } else { return super.hashcode(); } } final object cglib$clone$4() throws clonenotsupportedexception { return super.clone(); } protected final object clone() throws clonenotsupportedexception { methodinterceptor var10000 = this.cglib$callback_0; if (var10000 == null) { cglib$bind_callbacks(this); var10000 = this.cglib$callback_0; } return var10000 != null ? var10000.intercept(this, cglib$clone$4$method, cglib$emptyargs, cglib$clone$4$proxy) : super.clone(); } public static methodproxy cglib$findmethodproxy(signature var0) { string var10000 = var0.tostring(); switch(var10000.hashcode()) { case -1422568652: if (var10000.equals("add()v")) { return cglib$add$0$proxy; } break; case -508378822: if (var10000.equals("clone()ljava/lang/object;")) { return cglib$clone$4$proxy; } break; case 1826985398: if (var10000.equals("equals(ljava/lang/object;)z")) { return cglib$equals$1$proxy; } break; case 1913648695: if (var10000.equals("tostring()ljava/lang/string;")) { return cglib$tostring$2$proxy; } break; case 1984935277: if (var10000.equals("hashcode()i")) { return cglib$hashcode$3$proxy; } } return null; } public userserviceimpl$$enhancerbycglib$$b26297df() { cglib$bind_callbacks(this); } public static void cglib$set_thread_callbacks(callback[] var0) { cglib$thread_callbacks.set(var0); } public static void cglib$set_static_callbacks(callback[] var0) { cglib$static_callbacks = var0; } private static final void cglib$bind_callbacks(object var0) { userserviceimpl$$enhancerbycglib$$b26297df var1 = (userserviceimpl$$enhancerbycglib$$b26297df)var0; if (!var1.cglib$bound) { var1.cglib$bound = true; object var10000 = cglib$thread_callbacks.get(); if (var10000 == null) { var10000 = cglib$static_callbacks; if (var10000 == null) { return; } } var1.cglib$callback_0 = (methodinterceptor)((callback[])var10000)[0]; } } public object newinstance(callback[] var1) { cglib$set_thread_callbacks(var1); userserviceimpl$$enhancerbycglib$$b26297df var10000 = new userserviceimpl$$enhancerbycglib$$b26297df(); cglib$set_thread_callbacks((callback[])null); return var10000; } public object newinstance(callback var1) { cglib$set_thread_callbacks(new callback[]{var1}); userserviceimpl$$enhancerbycglib$$b26297df var10000 = new userserviceimpl$$enhancerbycglib$$b26297df(); cglib$set_thread_callbacks((callback[])null); return var10000; } public object newinstance(class[] var1, object[] var2, callback[] var3) { cglib$set_thread_callbacks(var3); userserviceimpl$$enhancerbycglib$$b26297df var10000 = new userserviceimpl$$enhancerbycglib$$b26297df; switch(var1.length) { case 0: var10000.<init>(); cglib$set_thread_callbacks((callback[])null); return var10000; default: throw new illegalargumentexception("constructor not found"); } } public callback getcallback(int var1) { cglib$bind_callbacks(this); methodinterceptor var10000; switch(var1) { case 0: var10000 = this.cglib$callback_0; break; default: var10000 = null; } return var10000; } public void setcallback(int var1, callback var2) { switch(var1) { case 0: this.cglib$callback_0 = (methodinterceptor)var2; default: } } public callback[] getcallbacks() { cglib$bind_callbacks(this); return new callback[]{this.cglib$callback_0}; } public void setcallbacks(callback[] var1) { this.cglib$callback_0 = (methodinterceptor)var1[0]; } static { cglib$statichook2(); } }
第四种:javassist动态代理(在动态字节码插桩详解)
① 定义一个javassistdynamicproxy实现javassist动态代理:
package com.toby.proxy; import javassist.util.proxy.proxyfactory; import javassist.util.proxy.proxyobject; /** * @desc: javassist * @author: toby * @date: 2019/8/15 20:22 */ public class javassistdynamicproxy { /** * 创建javassist动态代理 * @param targetobject * @throws exception * @return */ public object createjavassistdynamicproxy(final object targetobject)throws exception { proxyfactory factory = new proxyfactory(); factory.setinterfaces(targetobject.getclass().getinterfaces()); class<?> proxyclass = factory.createclass(); object javassistproxy = proxyclass.newinstance(); ((proxyobject)javassistproxy).sethandler((self,thismethod,proceed,args)-> { //写对应的增强代码 system.out.println("javassist日志记录开始"); //调用真正的业务方法 object obj = thismethod.invoke(targetobject,args); system.out.println("javassist日志记录结束"); return obj; }); return javassistproxy; } }
② 定义一个javassistbytecodedynamicproxy实现javassist动态代理:
package com.toby.proxy; import javassist.*; import java.lang.reflect.field; /** * @desc: javassist 字节码动态代理 * @author: toby * @date: 2019/8/15 20:42 */ public class javassistbytecodedynamicproxy { /** * 创建javassist字节码动态代理 * @param targetobject * @return * @throws exception */ public static object createjavassistbytecodedynamicproxy(final object targetobject) throws exception { classpool pool = classpool.getdefault(); ctclass proxyclass = pool.makeclass("javassistproxy" + "&" +targetobject.getclass().getname()); proxyclass.addinterface(pool.get(targetobject.getclass().getinterfaces()[0].getname())); proxyclass.addconstructor(ctnewconstructor.defaultconstructor(proxyclass)); proxyclass.addfield(ctfield.make("private " + targetobject.getclass().getname() + " targetobject;", proxyclass)); proxyclass.addmethod(ctnewmethod.make("public void add() { \n" + "system.out.println(\"javassist字节码日志记录开始\");\n" + "targetobject.add();\n" + "system.out.println(\"javassist字节码日志记录结束\");\n"+ "}", proxyclass)); class<?> clazz = proxyclass.toclass(); object bytecodeproxy = clazz.newinstance(); field field = bytecodeproxy.getclass().getdeclaredfield("targetobject"); field.setaccessible(true); field.set(bytecodeproxy,targetobject); return bytecodeproxy; } /** * 创建javassist字节码动态代理2 * @param targetobject * @return * @throws exception */ public static object createjavassistbytecodedynamicproxy2(final object targetobject) throws exception { classpool pool = classpool.getdefault(); pool.appendsystempath(); ctclass ctl = pool.get(targetobject.getclass().getname()); ctl.setname("javassistproxy" + "&" + targetobject.getclass().getname()); ctmethod ctmethod = ctl.getdeclaredmethod("add"); ctmethod.insertbefore("system.out.println(\"javassist字节码2日志记录开始\");"); ctmethod.insertafter("system.out.println(\"javassist字节码2日志记录结束\");"); class<?> clazz = ctl.toclass(); object bytecodeproxy = clazz.newinstance(); return bytecodeproxy; } }
四、spring aop
spring aop提供两种编程风格,详细用法见spring官网:
① @aspectj support(利用aspectj的注解)
② schema-based aop support(基于xml aop:config命名空间)
启用@aspectj支持
① 使用java configuration启用@aspectj支持:要使用java @configuration启用@aspectj支持,要添加@enableaspectjautoproxy注释:
@configuration @enableaspectjautoproxy public class appconfig { }
② 使用xml配置启用@aspectj支持:要使用基于xml的配置启用@aspectj支持,可以使用aop:aspectj-autoproxy元素
<aop:aspectj-autoproxy/>
声明一个aspect
@aspect @component public class useraspect { }
声明一个pointcut
@pointcut("execution(* com.toby.service.userservice.*(..))")//the pointcut expression
public void pointcutexecution(){}//the pointcut signature
spring aop支持的9种切入点表达式
① execution:execution用于匹配方法执行 join points连接点,最小粒度方法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) 这里问号表示当前项可以有也可以没有,其中各项的语义如下 modifiers-pattern:方法的可见性,如public,protected; ret-type-pattern:方法的返回值类型,如int,void等; declaring-type-pattern:方法所在类的全路径名,如com.spring.aspect; name-pattern:方法名类型,如buisinessservice(); param-pattern:方法的参数类型,如java.lang.string; throws-pattern:方法抛出的异常类型,如java.lang.exception; example: @pointcut("execution(* com.toby.dao.*.*(..))")//匹配com.toby.dao包下的任意接口和类的任意方法 @pointcut("execution(public * com.toby.dao.*.*(..))")//匹配com.toby.dao包下的任意接口和类的public方法 @pointcut("execution(public * com.toby.dao.*.*())")//匹配com.toby.dao包下的任意接口和类的public 无方法参数的方法 @pointcut("execution(* com.toby.dao.*.*(java.lang.string, ..))")//匹配com.toby.dao包下的任意接口和类的第一个参数为string类型的方法 @pointcut("execution(* com.toby.dao.*.*(java.lang.string))")//匹配com.toby.dao包下的任意接口和类的只有一个参数,且参数为string类型的方法 @pointcut("execution(* com.toby.dao.*.*(java.lang.string))")//匹配com.toby.dao包下的任意接口和类的只有一个参数,且参数为string类型的方法 @pointcut("execution(public * *(..))")//匹配任意的public方法 @pointcut("execution(* te*(..))")//匹配任意的以te开头的方法 @pointcut("execution(* com.toby.dao.indexdao.*(..))")//匹配com.toby.dao.indexdao接口中任意的方法 @pointcut("execution(* com.toby.dao..*.*(..))")//匹配com.toby.dao包及其子包中任意的方法 关于这个表达式的详细写法,可以参考官网:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
定义一个aopconfig配置类:
package com.toby.config; import org.springframework.context.annotation.componentscan; import org.springframework.context.annotation.configuration; import org.springframework.context.annotation.enableaspectjautoproxy; /** * @desc: aop配置类 * @author: toby * @date: 2019/8/5 23:48 */ @configuration /** * 此处需要注意的是,如果配置设置proxytargetclass=false,或默认为false,则是用jdk代理,否则使用的是cglib代理 * jdk代理的实现方式是基于接口实现,代理类继承proxy,实现接口。而cglib继承被代理的类来实现。 * 所以使用target会保证目标不变,匹配目标对象不会受到这个设置的影响。 * 但是使用this时,会根据该选项的设置,cglib this可以代理原因是继承了被代理的对象也就是目标对象,jdk的this就不能被代理了。 */ @enableaspectjautoproxy(proxytargetclass = true) @componentscan(basepackages="com.toby") public class aopconfig { }
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * for matching method execution join points. this is the primary pointcut designator to use when working with spring aop. * execution用于匹配方法执行 join points连接点,最小粒度方法 * 详细用法参考:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples */ @pointcut("execution(* com.toby.service.userservice.*(..))") public void pointcutexecution(){} @before("pointcutexecution()") public void before(){ system.out.println("--------before--------"); } @after("pointcutexecution()") public void after(){ system.out.println("--------after--------"); } }
定义一个启动测试类aopmain(下同):
package com.toby; import com.toby.config.aopconfig; import com.toby.service.userservice; import org.springframework.context.annotation.annotationconfigapplicationcontext; /** * @desc: aop启动类 * @author: toby * @date: 2019/8/5 23:50 */ public class aopmain { public static void main(string[] args) { annotationconfigapplicationcontext context = new annotationconfigapplicationcontext(aopconfig.class); userservice userservice = context.getbean(userservice.class); userservice.say("toby"); } }
运行结果如下:
② within:用于匹配指定类型内的方法执行, within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等。
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * limits matching to join points within certain types (the execution of a method declared within a matching type when using spring aop). * 用于匹配指定类型内的方法执行, within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等 */ @pointcut("within(com.toby.service.impl.userserviceimpl)") public void pointcutwithin(){} @before("pointcutwithin()") public void before(){ system.out.println("--------before--------"); } @after("pointcutwithin()") public void after(){ system.out.println("--------after--------"); } }
运行结果如下:
③ this:用于匹配当前aop代理对象类型的执行方法;注意是aop代理对象的类型匹配
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * limits matching to join points (the execution of methods when using spring aop) where the bean reference (spring aop proxy) is an instance of the given type. * 用于匹配当前aop代理对象类型的执行方法;注意是aop代理对象的类型匹配 */ @pointcut("this(com.toby.service.impl.userserviceimpl)") public void pointcutthis(){} @before("pointcutthis()") public void before(){ system.out.println("--------before--------"); } @after("pointcutthis()") public void after(){ system.out.println("--------after--------"); } }
@enableaspectjautoproxy(proxytargetclass = true),则用cglib代理,而cglib继承被代理的类来实现,所以this能匹配到,运行结果:
@enableaspectjautoproxy,默认proxytargetclass = false,如果基于接口则用jdk代理,所以this匹配不到,运行结果:
④ target:用于匹配当前目标对象类型的执行方法
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * limits matching to join points (the execution of methods when using spring aop) where the target object (application object being proxied) is an instance of the given type. * 用于匹配当前目标对象类型的执行方法 */ @pointcut("target(com.toby.service.impl.userserviceimpl)") public void pointcuttarget(){} @before("pointcuttarget()") public void before(){ system.out.println("--------before--------"); } @after("pointcuttarget()") public void after(){ system.out.println("--------after--------"); } }
运行结果如下:
⑤ args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * limits matching to join points (the execution of methods when using spring aop) where the arguments are instances of the given types. * 用于匹配当前执行的方法传入的参数为指定类型的执行方法 */ @pointcut("args(java.lang.string)") public void pointcutargs(){} @before("pointcutargs()") public void before(){ system.out.println("--------before--------"); } @after("pointcutargs()") public void after(){ system.out.println("--------after--------"); } }
定义一个启动测试类aopmain:
package com.toby; import com.toby.config.aopconfig; import com.toby.service.userservice; import org.springframework.context.annotation.annotationconfigapplicationcontext; /** * @desc: aop启动类 * @author: toby * @date: 2019/8/5 23:50 */ public class aopmain { public static void main(string[] args) { annotationconfigapplicationcontext context = new annotationconfigapplicationcontext(aopconfig.class); userservice userservice = context.getbean(userservice.class); //不能被增强 userservice.add(); //能被增强 匹配到了参数string类型 userservice.say("toby"); } }
运行结果如下:
⑥ @target:匹配目标对象类型是否有指定的注解
定义目标对象userserviceimpl如下:
package com.toby.service.impl; import com.toby.anno.toby; import com.toby.service.userservice; import org.springframework.stereotype.service; /** * @desc: user业务的实现类 * @author: toby * @date: 2019/8/4 23:29 */ @service @toby public class userserviceimpl implements userservice { @override public void add() { system.out.println("执行userserviceimpl的add方法"); } @override public string say(string name) { system.out.println("执行userserviceimpl的say方法 args = " + name); return "hello " + name; } }
定义一个注解log:
package com.toby.anno; import java.lang.annotation.elementtype; import java.lang.annotation.retention; import java.lang.annotation.retentionpolicy; import java.lang.annotation.target; /** * @desc: 日志注解 * @author: toby * @date: 2019/8/5 23:06 */ @target({elementtype.type,elementtype.method}) @retention(retentionpolicy.runtime) public @interface log { }
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * limits matching to join points (the execution of methods when using spring aop) where the class of the executing object has an annotation of the given type. * 用于匹配目标对象类型是否有指定的注解 */ @pointcut("@target(com.toby.anno.log)") public void pointcuttargetanno(){} @before("pointcuttargetanno()") public void before(){ system.out.println("--------before--------"); } @after("pointcuttargetanno()") public void after(){ system.out.println("--------after--------"); } }
运行结果如下:
⑦ @args:匹配方法参数所属的类型上有指定的注解(例子略)
⑧ @within:用于匹配所持有指定注解类型内的所以连接点也就是方法(例子略)
⑨ @annotation:用于匹配当前执行方法持有指定注解的方法(注解作用在方法上面)
定义目标对象userserviceimpl如下:
package com.toby.service.impl; import com.toby.anno.log; import com.toby.service.userservice; import org.springframework.stereotype.service; /** * @desc: user业务的实现类 * @author: toby * @date: 2019/8/4 23:29 */ @service public class userserviceimpl implements userservice { @override public void add() { system.out.println("执行userserviceimpl的add方法"); } @override @log public string say(string name) { system.out.println("执行userserviceimpl的say方法 args = " + name); return "hello " + name; } }
定义一个useraspect:
package com.toby.aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.component; /** * @desc: 用户切面,该切面一定要交给spring容器管理 * @author: toby * @date: 2019/8/5 22:56 */ @aspect @component public class useraspect { /** * limits matching to join points where the subject of the join point (the method being executed in spring aop) has the given annotation. * 用于匹配当前执行方法持有指定注解的方法(注解作用在方法上面); */ @pointcut("@annotation(com.toby.anno.log)") public void pointcutanno(){} @before("pointcutanno()") public void before(){ system.out.println("--------before--------"); } @after("pointcutanno()") public void after(){ system.out.println("--------after--------"); } }
运行结果发现:userservice.add();//不能被增强 userservice.say("toby");//能被增强,因为say方法上有@log注解;
总结:本章讲解了spring aop的核心概念和应用场景,spring aop可以帮我们解决编程过程中的一些横切性问题,比如我们要记录日志,事务管理,性能监控,权限认证等。使的这些问题能和我们业务逻辑分开,达到了解耦的目的,代码的重用性更高。如何声明一个aspect,声明一个pointcut以及spring aop支持的9种切入点表达式。spring aop的代理方式有2种,一个是jdk一个cglib,如果配置设置proxytargetclass=false,或默认为false,则是用jdk代理,否则使用的是cglib代理,注意jdk动态代理必须实现接口,否则还是会走cglib动态代理。(后续的源码解析会分析到原因),spring系列完整代码在码云:
上一篇: laravel 初探 数据填充