在当今复杂多变的软件开发领域,Spring 框架无疑是一颗璀璨的明星。而其中的 Spring AOP(面向切面编程)更是以其独特的魅力和强大的功能,为开发者们打开了一扇通往全新编程境界的大门。
当我们深入探索 Spring AOP 的世界,就仿佛置身于一个充满无限可能的编程宇宙之中。它犹如一把神奇的钥匙,能够巧妙地解开代码结构中的复杂症结,让我们可以从全新的切面视角去审视和构建软件。无论是实现系统级的横切关注点,如日志记录、事务管理、安全控制等,还是对业务逻辑进行精细化的分离与整合,Spring AOP 都展现出了无与伦比的适应性和灵活性。它并非仅仅是一种技术手段,更是一种编程理念的革新,为我们带来了高效、简洁且极具扩展性的开发方式。让我们一同踏上这趟精彩的 Spring AOP 之旅,去领略它所蕴含的奥秘与力量。
AOP(Aspect-Oriented Programming:面向切面编程),它实际做的就是将业务和一些非业务进行拆解,降低彼此业务模块与非业务模块的耦合度,便于后续的扩展维护。例如权限校验、日志管理、事务处理等都可以使用AOP实现。而Spring就是基于动态代理实现AOP的。如果被代理的类有实现接口的话,就会基于JDK Proxy完成代理的创建,反之就是通过Cglib完成代理创建,当然你也可以强制使用Cglib。
AOP中有很多核心术语,分别是:
目标(Target): 这就被代理的对象,例如我们希望对UserService每个方法进行增强(在不动它的代码情况下增加一些非业务的动作),那么这个UserService就是目标。
代理(Proxy): 就是给你被代理后的对象的厂商,例如我们上面说过希望对UserService每个方法进行增强,那么给用户返回增强后的对象的类就是代理类。
连接点(JoinPoint):目标对象,每一个可能可以被增强的方法都可以称为连接点,尽管它最后可能不会被增强。
切入点(Pointcut): 连接点中即能够应用通知的位置。
通知(Advice): 不要被表面的语义误导,通知并不是告知某人的意思,通知的意思是拦截对象后,做的增强操作,也就是拦截后要执行什么代码。
切面(Aspect): 切入点(Pointcut)+通知(Advice)。
织入(Weaving):把通知的动作融入到对象中,生成代理对象的过程就叫做织入。
Spring AOP属于运行时增强,基于代理(Proxying)实现的。而AspectJ AOP属于编译时增强,基于字节码操作(Bytecode Manipulation)实现的。
在《精通spring4.x》一书中,我们可以知道,jdk生成的代理对象性能远远差于cglib生成代理对象,但cglib创建代理对象花费的时间却远远高于jdk代理创建的对象。所以在spring框架的使用中,如果是单例的bean需要实现aop等操作,我们建议是使用cglib动态代理技术:
Before(前置通知): 目标对象方法调用前触发增强。
After (后置通知):目标对象方法调用后进行增强。
AfterReturning(返回通知):目标对象方法执行结束,返回值时进行增强。
AfterThrowing(异常通知):目标对象方法执行报错并抛出时做的增强。
Around(环绕通知):这个比较常用了,目标对象方法调用前后我们可以做各种增强操作,甚至不调用对象的方法都能做到。
答: 有两种方式:
注解法:使用@Order注解来决定切面bean的执行顺序。
// 值越小优先级越高@Order(1)@Component @AspectpublicclassLoggingAspectimplementsOrdered{
继承接口法:implements Ordered接口
@Component @AspectpublicclassLoggingAspectimplementsOrdered{// ....@OverridepublicintgetOrder(){// 返回值越小优先级越高return1;}}
在bean初始化前后也就我们常说的BPP阶段完成AOP类的缓存以及通知器创建。在bean初始化后,根据需要结合通知器完成代理类的改造。
是在运行期间,创建目标对象的代理对象,目标对象不变,我们通过对方法动态拦截,进行前置或者后置等各种增强操作。AOP中就有CGLIB动态代理和JDK动态代理技术。
AOP提供了一个默认工厂根据类是否有继承接口或者是否就是目标类决定创建的策略。然后根据不同的策略决定代理类的创建。
@OverridepublicAopProxycreateAopProxy(AdvisedSupport config)throws AopConfigException{if(config.isOptimize()||config.isProxyTargetClass()||hasNoUserSuppliedProxyInterfaces(config)){Class<?>targetClass=config.getTargetClass();//如果是接口则走JdkDynamicAopProxy反之走ObjenesisCglibAopProxyif(targetClass.isInterface()||Proxy.isProxyClass(targetClass)){returnnewJdkDynamicAopProxy(config);}returnnewObjenesisCglibAopProxy(config);}else{returnnewJdkDynamicAopProxy(config);}}
以下便是jdk代理的创建策略:
@OverridepublicObjectgetProxy(@Nullable ClassLoader classLoader){.........//获取被代理的类的接口Class<?>[]proxiedInterfaces=AopProxyUtils.completeProxiedInterfaces(this.advised,true);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);//生成代理对象并返回returnProxy.newProxyInstance(classLoader,proxiedInterfaces,this);}
以下便是cglib的创建策略:
@OverridepublicObjectgetProxy(@Nullable ClassLoader classLoader){.......try{.......//将当前类信息通过enhancer 生成代理对象Enhancer enhancer=createEnhancer();if(classLoader!=null){enhancer.setClassLoader(classLoader);if(classLoaderinstanceofSmartClassLoader&&((SmartClassLoader)classLoader).isClassReloadable(proxySuperClass)){enhancer.setUseCache(false);}}enhancer.setSuperclass(proxySuperClass);enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(newClassLoaderAwareGeneratorStrategy(classLoader));Callback[]callbacks=getCallbacks(rootClass);Class<?>[]types=newClass<?>[callbacks.length];for(int x=0;x<types.length;x++){types[x]=callbacks[x].getClass();}//返回最终生成的代理对象returncreateProxyClassAndInstance(enhancer,callbacks);}........}catch(Throwable ex){......}}
CGLIB是一个强大、高性能的代码生成包。使用ASM操作字节码,动态生成代理,对方法进行增强。,它广泛的被许多AOP框架使用,为他们提供方法的拦截。
例如我们希望对某个service进行日志打印,基于CGLIB我们可以这样实现:
前置步骤引入依赖:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency>
首先创建用户类
publicclassUser{privateString name;privateint age;publicStringgetName(){returnname;}publicvoidsetName(String name){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(int age){this.age=age;}publicUser(){}publicUser(String name,int age){this.name=name;this.age=age;}@OverridepublicStringtoString(){return"User{"+"name='"+name+'\''+", age="+age+'}';}}
service类
publicclassUserServiceImpl{publicList<User>findUserList(){returnCollections.singletonList(newUser("xiaoming",18));}}
代理类
publicclassCglibProxy<T>implementsMethodInterceptor{privatestaticLogger logger=LoggerFactory.getLogger(CglibProxy.class);privateObject target;publicTgetTargetClass(Object target){//设置被代理的目标类this.target=target;// 创建加强器设置代理类以及回调,当代理类被调用时,callback就会去调用interceptEnhancer enhancer=newEnhancer();enhancer.setSuperclass(this.target.getClass());enhancer.setCallback(this);//返回代理类return(T)enhancer.create();}@OverridepublicObjectintercept(Object o,Method method,Object[]args,MethodProxy methodProxy)throws Throwable{logger.info("调用被代理对象的方法,代理对象:[{}],代理方法:[{}]",o.getClass().getName(),method.getName());Object result=methodProxy.invokeSuper(o,args);logger.info("代理调用结束,返回结果:[{}]",String.valueOf(result));returnnull;}}
测试代码
publicclassMain{publicstaticvoidmain(String[]args){CglibProxy<UserServiceImpl>cglibProxy=newCglibProxy();UserServiceImpl targetClass=cglibProxy.getTargetClass(newUserServiceImpl());targetClass.findUserList();}}
如下图所示,整体来说就是基于enhancer去配置被代理类的各种参数,然后生成代理类:
注意:final方法无法被代理,因为它不可被子类覆盖。
源码如下,我们可以看出和我们写的实例代码是差不多的。
@OverridepublicObjectgetProxy(@Nullable ClassLoader classLoader){.....try{//获取当前类信息获取生成代理对象Enhancer enhancer=createEnhancer();if(classLoader!=null){enhancer.setClassLoader(classLoader);if(classLoaderinstanceofSmartClassLoader&&((SmartClassLoader)classLoader).isClassReloadable(proxySuperClass)){enhancer.setUseCache(false);}}enhancer.setSuperclass(proxySuperClass);enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(newClassLoaderAwareGeneratorStrategy(classLoader));// 获取当前类中的方法Callback[]callbacks=getCallbacks(rootClass);Class<?>[]types=newClass<?>[callbacks.length];for(int x=0;x<types.length;x++){types[x]=callbacks[x].getClass();}enhancer.setCallbackFilter(newProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(),this.fixedInterceptorMap,this.fixedInterceptorOffset));enhancer.setCallbackTypes(types);// 生成代理对象returncreateProxyClassAndInstance(enhancer,callbacks);}catch(CodeGenerationException|IllegalArgumentException ex){.....}catch(Throwable ex){.....}}
答:这个是jdk自带的一种代理,我们只需继承InvocationHandler即可实现。但是前提是这个类必须继承某些接口才能使用jdk代理。
首先我们定义接口,User类沿用上述的:
publicinterfaceUserService{List<User>findUserList();}
修改UserServiceImpl:
publicclassUserServiceImplimplementsUserService{@OverridepublicList<User>findUserList(){returnCollections.singletonList(newUser("xiaoming",18));}}
jdk代理类:
publicclassJDKProxy<T>{privatestaticLogger logger=LoggerFactory.getLogger(JDKProxy.class);privateObject target;publicJDKProxy(Object target){this.target=target;}publicTgetTargetObj(){UserService proxy;ClassLoader loader=target.getClass().getClassLoader();Class[]interfaces=newClass[]{UserService.class};InvocationHandler handler=(p,method,args)->{logger.info("代理方法被调用,类名称[{}],方法名称[{}]",target.getClass().getName(),method.getName());Object result=method.invoke(target,args);logger.info("代理方法调用结束,返回结果:[{}]",String.valueOf(result));returnresult;};proxy=(UserService)Proxy.newProxyInstance(loader,interfaces,handler);return(T)proxy;}}
测试代码:
publicclassMain{publicstaticvoidmain(String[]args){JDKProxy<UserService>jdkProxy=newJDKProxy<>(newUserServiceImpl());UserService userService=jdkProxy.getTargetObj();System.out.println(userService.findUserList());}}
我们不妨在jvm在下面这样一段参数,或者在上述main方法加这个代码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
然后我们通过debug可以发现它回步入这段代码,其中他会生成一个ClassFile,方法名为generateClassFile:
publicstaticbyte[]generateProxyClass(final String name,Class<?>[]interfaces,int accessFlags){ProxyGenerator gen=newProxyGenerator(name,interfaces,accessFlags);final byte[]classFile=gen.generateClassFile();...}
而代理方法做的事情,整体如下所示,可以看到它整体做的就是拿着被代理类的 各种方法封装成ProxyMethod方法,然后写入class文件中:
/** * Generate a class file for the proxy class. This method drives the * class file generation process. */privatebyte[]generateClassFile(){/* 第一步:将所有方法包装成ProxyMethod对象 */// 将Object类中hashCode、equals、toString方法包装成ProxyMethod对象addProxyMethod(hashCodeMethod,Object.class);addProxyMethod(equalsMethod,Object.class);addProxyMethod(toStringMethod,Object.class);// 将代理类接口方法包装成ProxyMethod对象for(Class<?>intf:interfaces){for(Method m:intf.getMethods()){addProxyMethod(m,intf);}}//....../* 第二步:为代理类组装字段,构造函数,方法,static初始化块等 */try{// 添加构造函数,参数是InvocationHandlermethods.add(generateConstructor());// 代理方法for(List<ProxyMethod>sigmethods:proxyMethods.values()){for(ProxyMethod pm:sigmethods){// 字段fields.add(newFieldInfo(pm.methodFieldName,"Ljava/lang/reflect/Method;",ACC_PRIVATE|ACC_STATIC));// 上述ProxyMethod中的方法methods.add(pm.generateMethod());}}// static初始化块methods.add(generateStaticInitializer());}catch(IOException e){thrownewInternalError("unexpected I/O Exception",e);}//....../* 第三步:写入class文件 *///......try{//......dout.writeShort(0);// (no ClassFile attributes for proxy classes)}catch(IOException e){thrownewInternalError("unexpected I/O Exception",e);}returnbout.toByteArray();}
看看上文命令下创建class,可以看到它 implements UserService 以及通过我们的的代理类的InvocationHandler 调用这些方法:
publicfinalclass$Proxy0extendsProxyimplementsUserService{privatestaticMethod m1;privatestaticMethod m3;privatestaticMethod m2;privatestaticMethod m0;public$Proxy0(InvocationHandler var1)throws{super(var1);}//......//动态代理了findUserList方法,后续调用时本质上是通过代理类的method对原有方法进行调用,即我们的InvocationHandler所实现的逻辑publicfinal ListfindUserList()throws{try{return(List)super.h.invoke(this,m3,(Object[])null);}catch(RuntimeException|Error var2){throwvar2;}catch(Throwable var3){thrownewUndeclaredThrowableException(var3);}}//......static{try{m1=Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));//初始化我们的代理方法类methodm3=Class.forName("com.pdai.aop.jdkProxy.UserService").getMethod("findUserList");//......}catch(NoSuchMethodException var2){thrownewNoSuchMethodError(var2.getMessage());}catch(ClassNotFoundException var3){thrownewNoClassDefFoundError(var3.getMessage());}}}
JdkDynamicAopProxy源码如下,可以看到本质上也是通过传入:
类加载器
接口类型,通过proxiedInterfaces获取对应接口到代理缓存中获取要生成的代理类型
对应的InvocationHandler 进行逻辑增强。
@OverridepublicObjectgetProxy(@Nullable ClassLoader classLoader){//......returnProxy.newProxyInstance(classLoader,proxiedInterfaces,this);}