精彩推荐 |【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日志并进行多维度分析。
相关文章
|
4天前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
31 11
|
14天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
50 7
|
14天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
1月前
|
JSON 前端开发 JavaScript
java-ajax技术详解!!!
本文介绍了Ajax技术及其工作原理,包括其核心XMLHttpRequest对象的属性和方法。Ajax通过异步通信技术,实现在不重新加载整个页面的情况下更新部分网页内容。文章还详细描述了使用原生JavaScript实现Ajax的基本步骤,以及利用jQuery简化Ajax操作的方法。最后,介绍了JSON作为轻量级数据交换格式在Ajax应用中的使用,包括Java中JSON与对象的相互转换。
45 1
|
1月前
|
Java 数据库连接 数据库
深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能
在Java应用开发中,数据库操作常成为性能瓶颈。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能。文章介绍了连接池的优势、选择和使用方法,以及优化配置的技巧。
37 1
|
28天前
|
Java
JAVA 静态代理 & 动态代理
【11月更文挑战第14天】静态代理是一种简单的代理模式实现,其中代理类和被代理类的关系在编译时已确定。代理类实现与被代理类相同的接口,并持有被代理类的实例,通过调用其方法实现功能增强。优点包括代码结构清晰,易于理解和实现;缺点是对于多个被代理类,需为每个类编写相应的代理类,导致代码量大增,维护成本高。动态代理则在运行时动态生成代理类,更加灵活,减少了代码冗余,但可能引入性能损耗和兼容性问题。
|
5天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
35 6
|
20天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
18天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####