首页 > 自考资讯 > 自考知识

aspects of,aspective

头条共创 2024-06-27

1. 关于代理

23种Java设计模式之一称为代理模式,这种类型的代理称为静态代理。提供了访问目标对象的附加方法,即通过代理对象。这提供了附加的功能操作来扩展目标对象的功能而不改变原始目标对象。 Spring AOP 通常被称为动态代理。 AOP(面向切面编程)分为静态代理和动态代理(JDK和CGLIB动态代理)。

d658182e46d642c3875901406473e74a~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720041875&x-signature=dgdmbPeF%2BMbpV3uq%2F%2FGLAuNsDto%3D 静态代理:编译时实现,编译后代理类成为实际的类文件。动态代理:运行时动态生成。这意味着编译后并没有实际的类文件,而是在运行时动态生成类的字节码并加载到JVM 中。代理方式

达到

优势

有缺点

特征

JDK静态代理

代理类和委托类实现相同的接口,并且该接口必须硬编码到代理类中。

易于实施和理解

代理类需要硬编码接口,这可能会导致重复编码、浪费存储空间并降低实际应用程序的效率。

好像没有什么特色

JDK动态代理

代理类和委托类实现相同的接口。代理类实现InvocableHandler 并重写invoke 方法来执行动态代理。

无需硬编码接口,代码复用率高

只有实现该接口的委托类才能被代理

底层使用反射机制来调用方法。

CGLIB动态代理

代理类将委托类作为自己的父类,并为非最终委托方法创建两个方法。一个方法与委托方法具有相同的签名,另一个方法通过super 调用委托方法。委托类有自己的方法。代理方法判断是否有实现MethodInterceptor接口的对象,如果有,则调用拦截方法来代理委托方法。

类或接口可以在运行时扩展,并且委托类不需要实现该接口。

Final类和final方法不能被代理

底层将所有方法存储在一个数组中,并通过数组索引直接调用方法。

1.1 静态代理

用法: 1. 创建一个接口,然后创建一个实现该接口的代理类,并在该接口上实现抽象方法。

public Interface ProxyDao { voidrentHouse();}@Slf4jpublic class ProxyDaoImplements ProxyDao { @Override public voidrentHouse() { log.info('House Rental'); 2. 创建代理类并实现该接口。代理类保存对被代理对象的引用,并且代理类的方法调用该对象的方法。

public class Transactionhandlerimplements ProxyDao { //目标代理对象private ProxyDao proxyDao; //构造代理对象时传递目标对象public Transactionhandler(ProxyDao proxyDao) { this.proxyDao=proxyDao } @Override public voidrentHouse(){ //调用前的处理目标方法System.out.println('启用事务控制.'); //调用目标对象的方法proxyDao.rentHouse() //调用目标方法后的处理println('关闭事务.控制.'); 创建客户端测试。

public class Client {public static void main(String[] args) { //创建一个新的目标对象ProxyDaoImpl target=new ProxyDaoImpl() //创建一个代理对象并使用接口ProxyDao proxyDao=new Transactionhandler(target); 引用它; //在接口上调用proxyDao.rentHouse() 。 }} JDK静态代理可以让你轻松完成类的代理操作。然而,JDK静态代理的缺点也变得明显。一个代理只能服务一个类,所以如果你有很多类需要代理,你就必须创建很多代理类,这会变得更加麻烦。

1.2 动态代理

从实现原理来看,动态代理可以分为两大类。

编译时和运行时增强

1.2.1 编译时增强

编译时增强,与Lombok 类似。也就是说,代理类是在以后运行时的编译生成阶段直接生成的。 AspectJ 是一个编译时扩展工具。

AspectJ的正式名称为Eclipse AspectJ,其位置如下:

一种基于Java语言的面向方面的编程语言。与Java兼容。易于学习和使用。要使用AspectJ,必须使用特殊的编译器ajc。

1.2.2 运行时增强

运行时扩展是指借助JDK动态代理或CGLIB动态代理在内存中临时生成AOP动态代理类。 Spring AOP中经常引用的动态代理通常就是指这个运行时扩展。

2. AspectJ 和 Spring AOP

AspectJ实际上是AOP的实现,但它是编译时扩展。

AspectJ 的一些优点:

Spring AOP生成动态代理类,因此某些静态或最后修改的方法无法被代理。这是因为这些方法不能被覆盖,并且最后修改的类不能继承。不过,AspectJ不需要动态生成代理类,一切都是在编译时完成的,所以这个问题在AspectJ中自然就解决了。 Spring AOP 有局限性。即只能在Spring容器管理的bean中使用;其他类没有这个限制。 Spring AOP只能在运行时扩展,而AspectJ支持编译时、编译后和运行时扩展。 Spring AOP支持扩展方法,AspectJ支持扩展方法、属性、构造函数、静态对象、final类/方法等。由于AspectJ 是一个编译时扩展,因此它的运行效率也比Spring AOP 更高。

2.1 Spring AOP

开发Spring AOP时,实际使用了AspectJ注解。我们平时使用的@Aspect、@Around、@Pointcut等都是AspectJ提供的,但是Spring AOP并没有利用AspectJ编译。不使用AspectJ的编译器和编织器,但Spring AOP仍然使用运行时扩展。要启用Spring对@AspectJ配置的支持,以便Spring容器中的目标bean自动扩展一个或多个方面,您需要将配置添加到Spring配置文件aop:aspectj-autoproxy/.aop:aspectj-autoproxy/.要使用Spring AOP通过CGLIB生成代理,只需将其引入到您的Spring配置文件中:aop:aspectj-autoproxy proxy-target-class='true'/

运行时扩展可以使用JDK 动态代理或CGLIB 动态代理来实现。

2.1.1 JDK 动态代理

JDK 动态代理有要求。也就是说,代理对象必须有一个接口。 CGLIB动态代理没有这个要求。

jdk实现的动态代理由两个重要成员组成:Proxy和InitationHandler。

Proxy:所有动态代理的父类,提供类对象和用于创建动态代理实例的静态方法。 InvocalHandler:每个动态代理实例都有一个关联的InvocalHandler。当在代理实例上调用方法时,该方法调用将转发到InvocableHandler 的调用方法。1b825742588149ba9f6728efc9cf51cc~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720041875&x-signature=D18Ob4thYDla01eDyZOfLUG2dTM%3D@Slf4jpublic class MyInitationHandlerimplements InvocableHandler { //目标对象private Object target; public MyInitationHandler(Object target) { this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //调用目标方法前的处理log.info('------插入提前通知代码-----') //调用目标对象的方法Object rs=method.invoke (target , args); //调用目标方法后的处理log.info('-----通知后插入代码-----');

@Slf4jpublic class Client { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InitationTargetException, InstantiationException { //====================第一个类型====================log.info('第一种类型:===================================如下:'); //1. 生成$Proxy0的类文件。 System.getProperties().put('sum.misc.ProxyGenerator.saveGeneeratedFiles', true); //2. 获取动态代理类。 Class proxyClazz=Proxy.getProxyClass(ProxyDao.class.getClassLoader(), ProxyDao.class); //3. 获取代理类构造函数,并将参数类型传递给InvocableHandler.class 构造函数。 constructor=proxyClazz.getConstructor(InvocalHandler.class); //4.通过构造函数类创建动态代理对象,并将自定义的InvocableHandler 实例传递给ProxyDao proxyDao=(ProxyDao)constructor.newInstance(new MyInitationHandler(new ProxyDaoImpl( ))) ; //5.通过代理对象调用目标方法proxyDao.rentHouse()。 //===================2nd==================/** * 代理类有, 还有也是一个简单的方法,封装了创建动态代理对象的步骤2-4。 * 其方法签名为: newProxyInstance(classLoaderloader,class. [] instance,InitationHandler h) * Loader : 类加载器。用于加载代理对象。 * Interface : 代理类实现的一些接口* h : 实现InvocationHandler接口的对象*/log.info('第二个方法1:(内部类)===================================类似: ') ProxyDao proxyDao1=(ProxyDao) Proxy.newProxyInstance(ProxyDao.class.getClassLoader( ), new Class[] { ProxyDao.class}, new InvocalHandler() { @Override public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable { //调用目标方法前的处理log.info ('-------- Insert提前通知代码------'); //调用目标对象的方法Object invoke=method.invoke(new ProxyDaoImpl(), args); //调用目标方法后Process log.info(' - --- --插入发布通知代码-----') } }); 第二种方法:===================================如下:'); ProxyDao proxyDao2=(ProxyDao) Proxy.newProxyInstance(ProxyDao.class.getClassLoader( ),//接口加载new Class[]{ProxyDao.class}的类加载器,//接口集new MyInvocalHandler(new ProxyDaoImpl()));//自定义InitationHandler proxyDao2.rentHouse() }} run;结果:

57006a2c644049e09647a12e5b2a26bf~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720041875&x-signature=eL%2FsU4EP1wQPRf7tgH1Da%2BqUDfk%3D

2..2 CGLIB 动态代理

从SpringBoot2开始,AOP默认使用的动态代理是CGLIB动态代理。与JDK动态代理相比,CGLIB动态代理支持类代理。如果用户想使用JDK动态代理,必须手动配置。 spring.aop.proxy-target-class=false。

CGLIB(代码生成库)包的底层使用ASM这种小型且快速的字节码处理框架来转换字节码并生成新的类。这是一个基于ASM的字节码生成库,允许您在运行时修改和动态生成字节码。 CGLIB通过继承来实现代理。

CGLIB 实现还包含两个重要成员:Enhancer 和MethodInterceptor。其实这两个的用法和JDK实现的动态代理Proxy和InitationHandler非常相似。

增强器:指定要代理的目标对象,对该对象的所有非最终方法调用都会检索实际处理代理逻辑的对象。 MethodInterceptor:动态代理对象方法调用被转发到拦截方法进行扩展。47d014c23a1f46348edee796b70cc2a5~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720041875&x-signature=LFhiFfPQ6qTFWfhTJlIxY9n8T8k%3DCGLIB代理的实现如下:

实现MethodInterceptor 将方法调用转发到类的拦截()方法。如果需要,通过CGLIB动态代理获取代理对象。这是代码:它是一个实现类,没有接口。

@Slf4jpublic class HelloService { public HelloService(){ log.info('--------HelloServiceconstructor------') } /** * 该方法不能被子类重写,cglib 是最终代理不能被执行。 */final public String SayOthes(String name){ log.info('-----HelloService:sayOthes------'+name); } public void SayHello(){ log .info(' ----- -HelloService:sayHello------'); } public voidsayHello2(){ log.info('-----HelloService:sayHello2------'); 实现拦截方法。对于MethodInterceptor 接口:

@Slf4jpublic class MyMethodInterceptorimplements MethodInterceptor { @Override public ObjectIntercept(Object o, Method method, Object[] events, MethodProxy methodProxy) throws Throwable { //调用目标方法前的处理log.info('-------- Insert advance notification code ------'); //调用目标对象的方法Object result=methodProxy.invokeSuper(o,objects) //调用目标方法后的处理log.info('---) -- -插入post通知代码------') 测试类:

public class CGLIBClient { public static void main(String[] args) { //将agent类文件保存到本地,方便编译查看源码。 //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 'D:\) \log'); //通过CGLIB动态代理获取agent i对象的过程Enhancerextender=newEnhancer() //设置enhancer对象的父类Setextender.setSuperclass (HelloService.class); //设置增强器回调对象enhancer.setCallback(new MyMethodInterceptor() );

创建代理对象 HelloService helloService = (HelloService) enhancer.create(); // 通过代理对象调用目标方法 helloService.sayHello(); helloService.sayHello2(); }}运行结果: dc0fd7476c9a4c5ab8af438e9923d099~noop.image?_iz=58558&from=article.pc_detail&lk3s=953192f4&x-expires=1720041875&x-signature=1BUGHfjHMUwhNqgaYpUkscA0aR8%3D

3. 小结

JDK 动态代理要求被代理的类必须实现接口,有很强的局限性。而 CGLIB 动态代理则没有此类强制性要求。简单的说,CGLIB 会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。 CGLIB 在进行代理时的工作: 生成的代理类继承被代理类。在这里我们需要注意一点:如果委托类被 final 修饰,那么它不可被继承,即不可被代理;同样,如果委托类中存在 final 修饰的方法,那么该方法也不可被代理。代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,它在方法中会通过 super 调用委托方法;另一个是代理类独有的方法。当执行代理对象的方法时,会首先判断一下是否存在实现了 MethodInterceptor 接口的 CGLIB$CALLBACK_0;,如果存在,则将调用 MethodInterceptor 中的 intercept 方法。在 intercept 方法中,我们除了会调用委托方法,还会进行一些增强操作。在 Spring AOP 中,典型的应用场景就是在某些敏感方法执行前后进行操作日志记录。 在 CGLIB 中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用:通过 FastClass 机制对 Class 对象进行特别的处理,比如将会用数组保存 method 的引用,每次调用方法的时候都是通过一个 index 下标来保持对方法的引用。 FastClass 机制:CGLIB 采用了 FastClass 的机制来实现对被拦截方法的调用。 FastClass 机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。 public class FastTest { /** * tt 看作目标对象,fc 看作是代理对象;首先根据代理对象的 getIndex 方法获得目标方法的索引, * 在调用代理对象的 invoke 方法就可以直接调用目标类的方法,避免了反射。 * * @param args */ public static void main(String[] args) { Test tt = new Test(); Test2 fc = new Test2(); int index = fc.getIndex("f()v"); fc.invoke(index, tt, null); }}/** * 模拟代理类 如:HelloService$EnhancerByCGLIB$1552dfb0 */@Slf4jclass Test { public void f() { log.info("------f method------"); } public void g() { log.info("------g method------"); }}/** * 模拟代理类FastClass 如:HelloService$EnhancerByCGLIB$1552dfb0$FastClassByCGLIB$8d34995d */class Test2 { public Object invoke(int index, Object o, Object[] ol) { Test test = (Test) o; switch (index) { case 1: test.f(); return null; case 2: test.g(); return null; } return null; } // 这个方法对 Test 类中的方法建立索引 public int getIndex(String signature) { switch (signature.hashCode()) { case 3078479: return 1; case 3108270: return 2; } return -1; }}上例中,Test2 是 Test 的 Fastclass,在 Test2 中有两个方法 getIndex 和 invoke。在 getIndex 方法中对 Test 的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。Invoke 根据指定的索引,以 ol 为入参调用对象 O 的方法。这样就避免了反射调用,提高了效率。 1.CGlib 比 JDK 快? CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类, 在 jdk6 之前比使用 Java 反射效率要高。唯一需要注意的是,CGLib 不能对声明为 final 的方法进行代理, 因为 CGLib 原理是动态生成被代理类的子类。 在 jdk6、jdk7、jdk8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLIB 代理效率。只有当进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,但是到 jdk8 的时候,jdk 代理效率高于 CGLIB 代理,总之,每一次 jdk 版本升级,jdk 代理效率都得到提升,而 CGLIB 代理消息确有点跟不上步伐。 版权声明:本文转载于今日头条,版权归作者所有,如果侵权,请联系本站编辑删除

猜你喜欢