Spring AOP之AopProxy代理对象

Spring AOP之AopProxy代理对象,第1张

在Spring的AOP模块,一个主要的部分是代理对象的生成,可以通过ProxyFactoryBean来完成,它封装了主要代理对象的生成过程。在这个生成过程中,可以使用JDK的Proxy和CGLIB两种生成情况。

Tip: 什么是匿名类

即没有名称的类,其名称由Java编译器给出,一般为:外部类名称+$+匿名类顺序,名称也就是其他地方不能引用,不能实例化,只用一次,当然也就不能有构造器。

在ProxyFactoryBean中,需要为target目标对象生成Proxy代理对象,从而为AOP横切面的编织做好准备。从FactoryBean中获取对象,是以getObject()方法作为入口完成的。在该方法中,首先对通知器链进行初始化,封装了一系列的拦截器,这些拦截器都要从配置中读取,然后为代理对象的生成做好准备。在生成代理对象时,因为Spring中有singleton类型和prototype类型这两种不同的Bean,所以要对代理对象的生成做一个区分。

首先为Proxy代理对象配置Advisor链,在initializeAdvisorChain()方法中执行。

在该方法中它会首先通过thisadvisorChainInitialized来判断通知器链是否已经初始化了,如果已经初始化了,就直接返回。其他情况下,通过 thisinterceptorNames 来要添加的通知器名,然后通过该名从IOC容器中取得的通知器加入到拦截器链中。

生成singleton的代理对象在getSingletonInstance()中完成

如果它还没有被创建,则lazily creating

在Spring代理目标target时,其实并不是直接创建一个目标target的对象实例的,而是通过一个TargetSource类型的对象对目标target进行封装,Spring Aop获取目标对象始终是通过 TargetSourcegetTarget() 方法进行的。

proxy(代理对象)代理的不是target,而是TargetSource

那么问题来了:为什么SpringAOP代理不直接代理target,而需要通过代理TargetSource(target的来源,其内部持有target),间接代理target呢

通常情况下,一个proxy(代理对象)只能代理一个target,每次方法调用的目标也是唯一固定的target。但是,如果让proxy代理TargetSource,可以使得每次方法调用的target实例都不同(当然也可以相同,这取决于TargetSource实现)。这种机制使得方法调用变得灵活,可以扩展出很多高级功能,如:target pool(目标对象池)、hot swap(运行时目标对象热替换),等等。

Spring内置了多种TargetSource

监听调用AdvisedSupportListener实现类的activated方法

具体的代理对象的生成,是在ProxyFactoryBean的基类AdvisedSupport的实现中借助AopProxyFactory完成的,这个代理对象要么从JDK中生成,要么借助CGLIB获得。

这个AopProxyFactory是在初始化函数中定义的,使用的是DefaultAopProxyFactor。

如果targetClass是接口类,使用JDK来生成Proxy

如果不是接口类要生成Proxy,那么使用CGLIB来生成。

接下来分别介绍两种不同的方式来产生AopProxy代理对象

首先从advised对象中取得代理对象的代理接口配置,然后调用Proxy的newProxyInstance方法,得到最终的Proxy代理对象。

在生成代理对象时,需要指明三个参数,类加载器,代理接口和Proxy回调方法所在的对象。

在回调方法所在对象中,需要实现InvocationHandler接口,它定义了invoke方法,

对于JdkDynamimcAopProxy,它本身实现了InvocationHandler接口和invoke方法,这个invoke方法是Proxy代理对象的回调方法。

在该篇文章中就不讲解了,感兴趣的可以百度搜索。

注:本文大多数是对《Spring技术内幕》的阅读整理。

扩展:

JDK生成代理类

ProxyClassFactory

CGLIB动态代理

JDK动态代理和Gglib动态代理的区别:

1JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。

2JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂, 生成代理类比JDK效率低

3JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

引言 AOP(Aspected Oriented Programming)是一种新兴的编程技术 它可以解决OOP和过程化方法不能够很好解决的横切(crosscut)问题 如 事务 安全 日志等横切关注 当未来系统变得越来越复杂 横切关注点就成为一个打问题的时候 AOP就可以很轻松的解决横切关注点这个问题 使得AOP编程成为 Spring 是基于J EE的轻量级开源开发框架 其中Spring AOP组件实现了面向方面编程 AOP 概述 面向方面编程 (AOP) 提供从另一个角度来考虑程序结构以完善面向对象编程(OOP) 面向对象将应用程序分解成各个层次的对象 而AOP将程序分解成各个方面或者说关注点 这使得可以模块化诸如事务管理等这些横切多个对象的关注点 AOP 基本概念方面(Aspect) 一个关注点的模块化 这个关注点实现可能另外横切多个对象 事务管理是J EE应用中一个很好的横切关注点例子 方面用Spring的 Advisor或拦截器实现 连接点(Joinpoint): 程序执行过程中明确的点 如方法的调 用或特定的异常被抛出 通知(Advice): 在特定的连接点 AOP框架执行的动作 各种类 型的通知包括 around before 和 throws 通知 通知类型将在下面讨论 许多AOP框架 包括Spring都是以拦截器做通知模型 维护一个 围绕 连接点的拦截器链 切入点(Pointcut): 指定一个通知将被引发的一系列连接点 的集合 AOP框架必须允许开发者指定切入点 例如 使用正则表达式 引入(Introduction): 添加方法或字段到被通知的类 Spring允许引入新的接口到任何被通知的对象 例如 你可以使用一个引入使任何对象实现 IsModified接口 来简化缓存 目标对象(Target Object): 包含连接点的对象 也被称作 被通知或被代理对象 AOP代理(AOP Proxy): AOP框架创建的对象 包含通知 在Spring中 AOP代理可以是JDK动态代理或者CGLIB代理 织入(Weaving): 组装方面来创建一个被通知对象 这可以在编译时 完成(例如使用AspectJ编译器) 也可以在运行时完成 Spring和其他纯Java AOP框架一样 在运行时完成织入 Spring AOP 介绍 Spring的一个关键组件就是AOP框架 Spring IoC容器(BeanFactory 和ApplicationContext)并不依赖于AOP 这意味着如果你不需要使用 AOP可以不用 AOP完善了Spring IoC 使之成为一个有效的中间件解决方案 Spring AOP 是Spring 框架的重要组成部分 它实现了AOP联盟约定的接口 Spring AOP 是由纯Java开发完成的 Spring AOP 只实现了方法级别的连接点 在J EE应用中 AOP拦截到方法级的 *** 作已经足够 OOP倡导的是基于setter/getter 的方法访问 而非直接访问域 而Spring 有足够理由仅仅提供方法级的连接点 为了使控制反转(IoC)很方便的使用到非常健壮 灵活的企业服务 则需要Spring AOP 的实现 Spring AOP 在运行时才创建Advice 对象 Spring AOP的优点如下 ·允许开发者使用声明式企业服务 比如事务服务 安全 ·开发者可以开发满足业务需求的自定义方面 ·开发Spring AOP Advice 很方便 可以借助代理类快速搭建Spring AOP 应用 使用Spring AOP松散耦合 创建通知为实现AOP 开发者需要开发AOP 通知(Advice) AOP 通知(Advice) 包含了方面(Aspect)的逻辑 当创建一个Advice 对象的时候 你就编写了实现横切(cross cutting)功能 Spring 的连接点是用方法拦截器实现的 这就意味着你编写的Spring AOP 通知将在方法调用的不同点组入进程序中 由于在调用一个方法时有几个不同的时间点 Spring 可以在不同的时间点组入进程序 Spring AOP中 提供了四种通知的接口 MethodBeforeAdvice 用于在目标方法调用前触发 AfterReturningAdvice 用于在目标方法调用后触发 ThrowsAdvice 用于在目标方法抛出异常时触发 MethodInterceptor 用于实现 Around 通知(Advice) 在目方法执行的前后触发 如果要实现相应功能 则需要实现上述对应的接口 例如 实现Before 通知(Advice)需要实现方法 void before(Method method Object[] args Object target) 实现 After 通知(Advice) 需要实现方法 void afterReturning (Method method Object[] args Object target) 在Spring 中定义切入点在不能明确调用方法的时候 通知就很不实用 切入点则可以决定特定的类 特定的方法是否匹配特定标准 如果某匹配 则通知将应用到此方法上 Spring 切入点允许用很灵活的方式将通知组织进我们的类中 Spring 中的切入点框架的核心是Pointcut接口 此接口允许我们定义组入通知中的类和方法 许多方面是通过一系列的通知和切入点组合来定义 在Spring中 一个advisor就是一个方面的完整的模块化表示 Spring提供了PointcutAdvisor接口把通知和切入点组合成一个对象 Spring中很多内建的切入点都有对应的PointcutAdvisor 这使得你可以很方便在一个地方管理切入点和通知 Spring中的切入点分为两类 静态和动态 因为静态切入点的性能要优于动态切入点 所以优先考虑使用 Spring 为我们提供创建静态切入点很实用的类StaticMethodMatherPointcut 在这个类中 我们只需要关心setMappedName和setMappedNams方法 你可以使用具体的类名 也可以使用通配符 如 设置mappedName属性为set 则匹配所有的set方法 Spring还提供了另通过正则表达式来创建静态切入点的实用类RegexpMethodPointcut 通过使用Perl样式的正则表达式来定义你感兴趣的方法 当切入点需要运行时参数值来执行通知时 这时就需要使用动态切入点 Spring提供了一个内建的动态切入点 ControlFlowPointcut 此切入点匹配基于当前线程的调用堆栈 我们可以在只有在当前线程执行的执行时找到特定的类和特定的方法才返回true 使用动态切入点有很大的性能损耗 大多数的切入点可以静态确定 我们很少有机会创建动态切入点 为了增加可切入点的可重用性 Spring 提供了切入点上的集合 *** 作——交集和合并 用ProxyFactoryBean创建AOP代理ProxyFactoryBean 和其他Spring的 FactoryBean实现一样 引入一个间接的层次 如果你定义一个名字为myfactory的ProxyFactoryBean 引用myfactory的对象所看到的不是ProxyFactoryBean 实例本身 而是由实现ProxyFactoryBean的类的 getObject()方法所创建的对象 这个方法将创建一个包装了目标对象 的AOP代理 使用ProxyFactoryBean或者其他IoC可知的类来创建AOP代理的最重要的优点之一是IoC可以管理通知和切入点 这是一个非常的强大的功能 能够实现其他AOP框架很难实现的特定的方法 例如 一个通知本身可以引用应用对象(除了目标对象 它在任何AOP框架中都可以引用应用对象) 这完全得益于依赖注入所提供的可插入性 通常 我们不需要ProxyFactoryBean的全部功能 因为我们常常只对一个方面感兴趣 例如 事务管理 当我们仅仅对一个特定的方面感兴趣时 我们可以使用许多便利的工厂来创建AOP代理 如 TransactionProxyFactoryBean 自动代理在应用较小时 只有很少类需要被通知的时 ProxyFactoryBean 可以很好的工作 当有许多类需要通知的时 显示的创建每个代理就显得很繁琐 幸运的是Spring提供了是使用自动通过容器来创建代理 这时 就只需要配置一个Bean来做繁琐的工作 Spring提供了两个类实现自动代理 BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator BeanNameAutoProxyCreator为匹配名字的Bean产生代理 它可以使用在将一个或者多个方面应用在命名相似的Bean中 自动代理框架假设代理将要暴露出什么接口 如果目标Bean没有实现任何接口 这时就会动态产生一个子类 而更强大的自动代理是DefaultAdvisorAutoProxyCreator 你所需要做的是在BeanFactory中包含它的配置 这个类的奇妙之处在于他使用实现了BeanPostProcessor接口 在Bean定义被加载倒Spring容器中后 DefaultAdvisorAutoProxyCreator将搜索上下文中的Advisor 最后它将Advisor应用到匹配Advisor切入点的Bean中 这个代理只对Advisor起作用 它需要通过Advisor来得到需要通知的Bean 元数据自动代理(MetaData AutoProxy) 元数据自动代理配置依赖于源代码属性而不是外部XML配置文件 这可以非常方便的使源代码和AOP元数据组织在同一个地方 元数据自动代理最常用的地方是用来声明事务 Spring提供了很强的框架来通过AOP框架来声明事务 这提供了在EJB使用声明式事务的相同功能 结论 AOP 是面向对象编程的有力补充 通过方面就可以聚合在应用中行为形成可重用模块 通过程序可以实现怎样和在什么地方来调用这些行为 这可以减少代码重复 并使你更加关注业务逻辑 Spring 提供了AOP框架来实现调用方法时加入方面 在AOP框架中可以很方便的使用预定义的静态切入点来定义被调用的类和方法 我们需要通过Spring提供的代理类来产生代理对象 可以使用ProxyFactoryBean也可以使用自动代理 Spring AOP 的编程方式模块化了横向关注点的实现 提供了一个更好更快的软件开发方式 在软件结构日益扩大 结构日益复杂的今天 Spring AOP 将会发挥越来越重要的作用 lishixinzhi/Article/program/Java/ky/201311/27960

 public void temp() throws Exception{

//1获得class

       //获得类的当前实例

       Actions a = this;

       Systemoutprintln(a);

       //输出 comtaobaotuisiActions$$EnhancerByCGLIB$$dff89711@c2ccac

       //获得指定类的新实例

       Actions b = ActionsclassnewInstance();

       Systemoutprintln(b);

       //输出 comtaobaotuisiActions@1e4fede

//

       //通过类型获得类

       Class boolType = Booleanclass;

       Systemoutprintln(boolType);

       //输出 class javalangBoolean

//

       //通过变量获得类

       String stringExample = “”;

       Class stringType = stringExamplegetClass();

       Systemoutprintln(stringType);

       //输出class javalangString

//

       //由名字获得类

       Class<> c = ClassforName(“comtaobaotuisiActions”);

       Systemoutprintln(c);

       //输出 class comtaobaotuisiActions

//

//2关于method

       //由函数名和参数类型得到函数

       Long userId = 9999l;

       Method method = ActionsclassgetDeclaredMethod(“tempMethod”, userIdgetClass());

       Systemoutprintln(method);

       //输出 public void comtaobaotuisiActionstempMethod(javalangLong)

//

       //通过类、参数值调用指定函数

       Actions actions = new Actions();

       Long args[] = new Long [1];

       args[0] = userId;

       methodinvoke(actions, args);

       //输出  我是JAVA反射测试方法,我被invoke了9999

    }

AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。

那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。

AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。。

1、定义接口和实现

[java] view plain copy print

package commeituanhyttest3service;

public interface UserService {

public String getName(int id);

public Integer getAge(int id);

}

[java] view plain copy print

package commeituanhyttest3serviceimpl;

import commeituanhyttest3serviceUserService;

public class UserServiceImpl implements UserService {

@Override

public String getName(int id) {

Systemoutprintln("------getName------");

return "Tom";

}

@Override

public Integer getAge(int id) {

Systemoutprintln("------getAge------");

return 10;

}

}

2、jdk动态代理实现

[java] view plain copy print

package commeituanhyttest3jdk;

import javalangreflectInvocationHandler;

import javalangreflectMethod;

public class MyInvocationHandler implements InvocationHandler {

private Object target;

MyInvocationHandler() {

super();

}

MyInvocationHandler(Object target) {

super();

thistarget = target;

}

@Override

public Object invoke(Object o, Method method, Object[] args) throws Throwable {

if("getName"equals(methodgetName())){

Systemoutprintln("++++++before " + methodgetName() + "++++++");

Object result = methodinvoke(target, args);

Systemoutprintln("++++++after " + methodgetName() + "++++++");

return result;

}else{

Object result = methodinvoke(target, args);

return result;

}

}

}

[java] view plain copy print

package commeituanhyttest3jdk;

import commeituanhyttest3serviceUserService;

import commeituanhyttest3serviceimplUserServiceImpl;

import javalangreflectInvocationHandler;

import javalangreflectProxy;

public class Main1 {

public static void main(String[] args) {

UserService userService = new UserServiceImpl();

InvocationHandler invocationHandler = new MyInvocationHandler(userService);

UserService userServiceProxy = (UserService)ProxynewProxyInstance(userServicegetClass()getClassLoader(),

userServicegetClass()getInterfaces(), invocationHandler);

Systemoutprintln(userServiceProxygetName(1));

Systemoutprintln(userServiceProxygetAge(1));

}

}

运行结果

++++++before getName++++++

------getName------

++++++after getName++++++

Tom

------getAge------

10

3、cglib动态代理实现

Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:

CGLIB的核心类:

netsfcglibproxyEnhancer – 主要的增强类

netsfcglibproxyMethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现

netsfcglibproxyMethodProxy – JDK的javalangreflectMethod类的代理类,可以方便的实现对源对象方法的调用,如使用:

Object o = methodProxyinvokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。

netsfcglibproxyMethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法

public Object intercept(Object object, javalangreflectMethod method,

Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用javalangreflectMethod对象的一般反射调用,或者使用 netsfcglibproxyMethodProxy对象调用。netsfcglibproxyMethodProxy通常被首选使用,因为它更快。

[java] view plain copy print

package commeituanhyttest3cglib;

import netsfcglibproxyMethodInterceptor;

import netsfcglibproxyMethodProxy;

import javalangreflectMethod;

public class CglibProxy implements MethodInterceptor {

@Override

public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

Systemoutprintln("++++++before " + methodProxygetSuperName() + "++++++");

Systemoutprintln(methodgetName());

Object o1 = methodProxyinvokeSuper(o, args);

Systemoutprintln("++++++before " + methodProxygetSuperName() + "++++++");

return o1;

}

}

[java] view plain copy print

package commeituanhyttest3cglib;

import commeituanhyttest3serviceUserService;

import commeituanhyttest3serviceimplUserServiceImpl;

import netsfcglibproxyEnhancer;

public class Main2 {

public static void main(String[] args) {

CglibProxy cglibProxy = new CglibProxy();

Enhancer enhancer = new Enhancer();

enhancersetSuperclass(UserServiceImplclass);

enhancersetCallback(cglibProxy);

UserService o = (UserService)enhancercreate();

ogetName(1);

ogetAge(1);

}

}

运行结果:

++++++before CGLIB$getName$0++++++

getName

------getName------

++++++before CGLIB$getName$0++++++

++++++before CGLIB$getAge$1++++++

getAge

------getAge------

++++++before CGLIB$getAge$1++++++

递归过程的局部变量过多、递归深度过大,是造成系统栈溢出的原因,特别是递归列循环时肯定会发生系统栈溢出。

解决方法如下:

不静态分配,用new动态创建,从堆中分配的,堆的空间足够大。

不过记得写析构函数,delete你申请的堆空间。其实这样也挺方便,类结束的时候会自动调用析构函数释放空间。养成“不在栈上定义大数组/大对象”的好习惯很重要,否则再大的栈也会被撑爆的。

当然,如果你不喜欢new,delete的话,还是静态分配(毕竟静态分配有很多好处),那么可以通过改变默认栈空间来解决。

如果你用类名来调用B的静态方法不管怎么代理都无效的。CGlib也只是动态生成一个原有类型的子类型。并不是在原有类上直接做文章,可以考虑aspectj,做静态横切 ,如果是通过对象来调用,那只要像拦截普通方法一样就可以了

以上就是关于Spring AOP之AopProxy代理对象全部的内容,包括:Spring AOP之AopProxy代理对象、JDK代理和CGLIB动态代理生成class文件、用Spring AOP实现开发中松散耦合等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/10174999.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-06
下一篇 2023-05-06

发表评论

登录后才能评论

评论列表(0条)

保存