精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(中)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 经历了上一篇文章内容:《精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)》,相信您对于Java原生的动态代理技术应该有了一定的认识和了解了,那么我们先来回顾一下对应的技术要点,看看您是否真正的认识了对应的技术原理了?

前提介绍

经历了上一篇文章内容:《精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(上)》,相信您对于Java原生的动态代理技术应该有了一定的认识和了解了,那么我们先来回顾一下对应的技术要点,看看您是否真正的认识了对应的技术原理了?


技术回顾

要回顾Java动态代理,需要考虑以下几个关键点:
在这里插入图片描述

  • 动态代理的工作原理:动态代理通过在运行时动态创建代理对象来实现对目标对象的增强或修改。代理对象会拦截目标对象的所有方法调用,并在调用前后执行特定的逻辑。

  • 接口的重要性:动态代理要求目标对象和代理对象都实现一个或多个相同的接口。这些接口定义了代理对象和目标对象可以调用的方法。通过这种方式,客户端代码可以像调用普通接口一样调用代理对象,而无需关心其实现细节。

  • 实现动态代理的步骤:创建目标对象实现的所有接口的类,作为代理类的基础;在代理类中,使用Proxy.newProxyInstance()方法动态创建代理对象;在代理类中,重写目标对象实现的所有接口的方法,以便在调用这些方法时执行自定义逻辑。

  • 代理模式的应用场景:动态代理适用于需要对目标对象进行增强或修改的场景。例如,在不修改目标对象的源代码的情况下,为对象添加日志记录、性能监控、事务处理等功能。

注意:使用动态代理时,需要确保目标对象和代理对象都实现了相同的接口,否则客户端代码将无法正确调用代理对象。另外,动态代理不适用于非接口方法,因为Java不支持多重继承,代理对象只能继承自一个类

回顾问题分析

代理对象实现了什么接口

目标对象实现的接口即为所实现的接口,这与静态代理模式中代理对象实现的接口是相同的。此外,代理对象和目标对象都共有一个共同的接口,即它们都继承自同一个接口。因此,Proxy.newProxyInstance()方法返回的类型就是该接口类型。

代理对象的方法体是什么

代理对象的方法体是拦截器中invoke方法的实现,用于拦截并处理代理对象的所有逻辑,控制是否执行目标对象的目标方法。这个方法通常包括对请求的处理、对目标方法的调用以及对结果的返回等操作。

通过代理对象的方法体,可以实现方法级别的拦截和过滤,以及对目标方法的调用进行扩展和增强。同时,代理对象的方法体还可以用于实现事务管理、日志记录、安全控制等高级功能。


Java动态代理为我们提供了非常灵活的代理机制,但Java动态代理是基于接口的,如果对象没有实现接口我们该如何代理呢

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它具备强大的运行时字节码修改和动态生成能力。

CGLIB的原理

CGLIB需要指定父类和回调方法。当然cglib也可以与Java动态代理一样面向接口,因为本质是继承

CGLIB通过继承方式实现代理,能够动态地生成子类,覆盖并重写目标对象的方法,这种能力使得CGLIB成为Spring框架中实现AOP(面向切面编程)的关键组件。

继承方式

这种动态生成和修改字节码的能力使得CGLIB在许多高级应用场景中,如AOP(面向切面编程)编程、动态代理、方法拦截等,都展现出了卓越的性能和灵活性。

通过使用CGLIB,开发人员可以在运行时动态地定义横切关注点,例如日志记录、事务管理、安全控制等,而无需修改原有的业务逻辑代码。

为什么要用CGLIB

选择CGLIB来实现动态代理的主要原因之一是Spring框架的使用。Spring框架广泛应用于企业级应用程序的开发,而CGLIB作为基于ASM的字节码生成库,与Spring框架紧密集成,提供了强大的运行时字节码修改和动态生成能力,以下是使用CGLIB实现动态代理的简单示例代码:

建立被代理的类

首先,创建一个简单的代理对象,并不需要实现复杂的接口,那么可以直接创建一个实现类对象作为目标类,然后通过CGLIB或其他字节码库动态地修改这个类的字节码,生成代理类。

/** 
 * 被代理的类 
 * 目标对象类 
 */  
public class TargetObject {
   
     
    /** 
     * 目标方法(即目标操作) 
     */  
    public void exec() {
   
     
        System.out.println("exec");  
    }  
}

cglib拦截器类

接下来,我们将实现一个MethodInterceptor。通过该实现,所有的方法调用将被转发到intercept()方法进行处理。

在这里插入图片描述
这种方法拦截器提供了一种机制,可以在方法调用之前和之后执行自定义逻辑,从而实现更高级的功能,如日志记录、事务管理、安全控制等。

/** 
 * 动态代理-拦截器 
 */  
public class MyInterceptor implements MethodInterceptor {
   
     

    private Object target;//目标类  

    public MyInterceptor(Object target) {
   
     
        this.target = target;  
    }  

    /** 
     * 返回代理对象 
     * 具体实现,暂时先不追究。 
     */  
    public Object createProxy() {
   
     
        Enhancer enhancer = new Enhancer();  
        enhancer.setCallback(this);//回调方法 拦截器  
        //设置代理对象的父类,可以看到代理对象是目标对象的子类。所以这个接口类就可以省略了。  
        enhancer.setSuperclass(this.target.getClass());  
        return enhancer.create();  
    }  

}

上述代码中,我们通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑。

  /** 
     * args 目标方法的参数 
     * method 目标方法 
     */  
    @Override  
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
        throws Throwable {
   
     
        System.out.println("Before method invocation");  
        //调用目标类的目标方法  
        method.invokeSuper(this.target, objects);
        System.out.println("After method invocation");  
        return null;  
   }

通过调用 MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是TargetObject 的具体方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

测试类

在需要使用TargetObject时,我们可以通过CGLIB的动态代理机制来获取一个代理对象。通过这种方式,我们可以在运行时动态地创建代理对象,而无需修改TargetObject的源代码。
在这里插入图片描述
这个代理对象将继承自TargetObject,并且所有的方法调用都会被拦截并转发到指定的处理逻辑中。这种机制提供了极大的灵活性和可扩展性,使得我们可以在不改变原有业务逻辑的情况下,实现各种附加功能。

public class MainTest {
   
     
    public static void main(String[] args) {
   
     
        //目标对象  
        TargetObject target = new TargetObject();  
        //拦截器  
        MyInterceptor myInterceptor = new MyInterceptor(target);  
        //代理对象,调用cglib系统方法自动生成  
        //注意:代理类是目标类的子类。  
        TargetObject proxyObj = (TargetObject) myInterceptor.createProxy();  
        proxyObj.exec();  
    }  
}

我们分析了一下,从文件数上来说,cglib比jdk实现的少了个接口类。因为cglib返回的代理对象是目标对象的子类。而jdk产生的代理对象和目标对象都实现了一个公共接口。

易错点:CGLIB的invoke和invokeSuper的区分

在CGLIB中,MethodProxy是一个非常重要的接口,它提供了对被代理方法的低级别访问。MethodProxy的invoke和invokeSuper方法都是用于调用目标方法的。

invoke方法

当你调用代理对象的某个方法时,这个方法会被转发到MethodProxy的invoke方法,invoke方法接受四个参数:被代理对象、被代理方法、被代理方法的参数以及一个MethodProxy对象。

在invoke方法中,你可以使用MethodProxy的getMethod方法来获取被代理方法的详细信息(如方法名、参数类型等),然后你可以决定是否转发该方法调用或者执行其他逻辑。

invokeSuper方法:

invokeSuper方法是MethodProxy接口中的一个重要方法,用于执行被代理方法的调用,这个方法的调用会触发对目标对象的实际方法调用,从而使得我们可以对目标方法进行拦截和增强。

使用invokeSuper,我们可以调用目标对象上的原始方法,并且可以在这个调用前后添加额外的逻辑。在CGLIB的动态代理中,通常在拦截器的intercept方法中使用MethodProxy.invokeSuper来调用目标方法,从而实现方法的拦截和增强。

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理

TargetObject代理对象的类型信息

如果对CGLIB代理之后的对象类型进行深挖,可以看到如下信息:

class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete

 interfaces: 
 interface net.sf.cglib.proxy.Factory
 invocationHandler=not java proxy class
  • 看到使用CGLIB代理之后的对象类型是cglib.TargetObject$$EnhancerByCGLIB$$e3734e52,这是CGLIB动态生成的类型;父类是TargetObject,印证了CGLIB是通过继承实现代理;

  • 同时实现了net.sf.cglib.proxy.Factory接口,这个接口是CGLIB自己加入的,包含一些工具方法。

final控制以及限制

既然是继承就不得不考虑final的问题。我们知道final类型不能有子类,所以CGLIB不能代理final类型,遇到这种情况会抛出类似如下异常:

java.lang.IllegalArgumentException: Cannot subclass final class cglib.TargetObject

同样的,final方法是不能重载的,所以也不能通过CGLIB代理,遇到这种情况不会抛异常,而是会跳过final方法只代理其他方法。如果你还对代理类cglib.TargetObject$$EnhancerByCGLIB$$e3734e52具体实现感兴趣,它大致长这个样子:

CGLIB代理类具体实现

public class TargetObject{
   
   mathJaxContainer[3]}e3734e52
  extends TargetObject
  implements Factory
{
   
   
  ...
  private MethodInterceptor CGLIB$CALLBACK_0; // ~~
  ...

  public final String exec()
  {
   
   
    ...
    MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
   
   
      // 将请求转发给MethodInterceptor.intercept()方法。
      return (String)tmp17_14.intercept(this, 
              CGLIB$exec$0$Method, 
              new Object[] {
   
    }, 
              CGLIB$exec$0$Proxy);
    }
    return super.exec();
  }
  ...
}

上述代码我们看到,当调用代理对象的exec()方法时,首先会尝试转发给MethodInterceptor.intercept()方法,如果没有MethodInterceptor就执行父类的exec()。这些逻辑没什么复杂之处,但是他们是在运行时动态产生的,无需我们手动编写。

未完待续

由于篇幅过程,小编,会在下一章深入分一下cglib的底层实现以及生产详细的class类的内容,以及底层的细节原理,请《精彩推荐 |【Java技术专题】「重塑技术功底」攻破Java技术盲点之剖析动态代理的实现原理和开发指南(下)》


归纳总结

动态代理是一种技术,它可以在运行时动态地创建代理对象,拦截对目标对象的调用并执行一些额外的逻辑。动态代理主要分为两种实现方式:JDK的动态代理和CGLIB的动态代理。
在这里插入图片描述

JDK的动态代理

  • 代理对象和目标对象需要实现共同的接口。
  • 拦截器(或处理器)必须实现InvocationHandler接口。
  • JDK的动态代理主要适用于目标对象实现了某个接口的情况。

    CGLIB的动态代理

  • 代理对象是目标对象的子类。

  • 拦截器必须实现MethodInterceptor接口。
  • CGLIB不仅支持基于接口的代理,还支持基于类的代理。这意味着即使目标对象没有实现接口,仍然可以使用CGLIB创建代理对象。

如何选择JDK原生还是CGLIB动态代理

在实际应用中,选择JDK的动态代理还是CGLIB的动态代理取决于具体的需求和上下文。例如,如果目标对象已经有一个明确的接口定义,那么使用JDK的动态代理可能更加合适。而如果需要更灵活的字节码操作,或者目标对象没有明确的接口,那么CGLIB可能是一个更好的选择。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
9天前
|
JSON 前端开发 JavaScript
java-ajax技术详解!!!
本文介绍了Ajax技术及其工作原理,包括其核心XMLHttpRequest对象的属性和方法。Ajax通过异步通信技术,实现在不重新加载整个页面的情况下更新部分网页内容。文章还详细描述了使用原生JavaScript实现Ajax的基本步骤,以及利用jQuery简化Ajax操作的方法。最后,介绍了JSON作为轻量级数据交换格式在Ajax应用中的使用,包括Java中JSON与对象的相互转换。
18 1
|
14天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
30 3
|
14天前
|
SQL 监控 Java
Java连接池技术的最新发展,包括高性能与低延迟、智能化管理与监控、扩展性与兼容性等方面
本文探讨了Java连接池技术的最新发展,包括高性能与低延迟、智能化管理与监控、扩展性与兼容性等方面。同时,结合最佳实践,介绍了如何选择合适的连接池库、合理配置参数、使用监控工具及优化数据库操作,以实现高效稳定的数据库访问。示例代码展示了如何使用HikariCP连接池。
9 2
|
16天前
|
Java 数据库连接 数据库
优化之路:Java连接池技术助力数据库性能飞跃
在Java应用开发中,数据库操作常成为性能瓶颈。频繁的数据库连接建立和断开增加了系统开销,导致性能下降。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接,显著减少连接开销,提升系统性能。文章详细介绍了连接池的优势、选择标准、使用方法及优化策略,帮助开发者实现数据库性能的飞跃。
23 4
|
14天前
|
Java 数据库连接 数据库
深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能
在Java应用开发中,数据库操作常成为性能瓶颈。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能。文章介绍了连接池的优势、选择和使用方法,以及优化配置的技巧。
16 1
|
14天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
29 1
|
16天前
|
SQL Java 数据库连接
打破瓶颈:利用Java连接池技术提升数据库访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,避免了频繁的连接建立和断开,显著提升了数据库访问效率。常见的连接池库包括HikariCP、C3P0和DBCP,它们提供了丰富的配置选项和强大的功能,帮助优化应用性能。
34 2
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
2天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
15 9
|
5天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####