图文并茂带你理解Java的代理模式

目录

  • Java的代理模式
    • 1、什么是代理模式?
    • 2、静态代理和动态代理
    • 3、JDK动态代理的局限性
    • 4、使用CGLIB代理机制完成未实现接口的类的代理
    • 5、JDK动态代理和CGLIB动态代理对比
    • 6、JDK动态代理为什么只能代理实现接口的类?

Java的代理模式

1、什么是代理模式?

代理模式( Proxy ),给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 通常会通过代理对象来为原对象添加额外的功能。
代理模式属于结构型模式主要用于处理类或对象的组合。

上面是比较正式的书面释义,举个通俗点的例子来帮助理解:
周末你躺在床上饿了想吃火鸡面,但是你有点懒,你找你女朋友帮你泡好并端到你面前喂你吃,最后你把火鸡面吃完了,说了句宝宝你真好~
这个例子中:你吃了一包火鸡面,是你做的动作。 但是你请了你的女朋友帮你泡火鸡面,你的女朋友就可以理解为是你的代理,此时你女朋友这个代理帮你泡了火鸡面(代理对象添加了额外的功能)。为了完成你吃火鸡面这件事 ,最后你女朋友还得端到你面前喂你吃(这里的喂你吃火鸡面就相当于 (代理对象控制了原对象的引用) )。

2、静态代理和动态代理

根据名称我们可以猜测,静态的代理一定不够灵活,运用到代码中一定是耦合的。
而动态代理顾名思义是动态的,灵活的,运用到代码中是解耦的。

静态代理:
在静态代理中,代理类是在编译时就确定的,即在代码中显式地定义了代理类(需要手动编写一个代理类)。
代理类通常与被代理类实现相同的接口,并且在代理类的方法中调用被代理类的方法。
静态代理的一个缺点是每次添加一个新的功能,都需要创建一个代理类,这样会导致类的数量增加,并且会造成代码的冗余。
代理类通常需要直接引用被代理类,因此代理类对被代理类有一定的依赖关系。如果被代理类的接口发生变化,代理类也需要相应地进行修改,这增加了代码的耦合性。

静态代理实现步骤:

  • ①、定义一个接口用于被代理类实现,定义一个被代理类实现前面定义的接口;
  • ②、自定义一个代理类实现前面定义的接口 ,并重写接口中需要被代理的方法,在重写的方法中调用原方法(被代理类的方法)并自定义一些处理逻辑;
  • ③、通过代理类创建代理对象,使用代理对象替换原对象调用方法;

静态代理图示:
在这里插入图片描述

静态代理的代码示例:

public class TestStaticProxy {
    public static void main(String[] args) {

        YourBehavior you = new You();
        // 不使用代理
        you.eat("火鸡面");

        System.out.println("========================");

        // 使用代理
        YourBehavior yourGirlfriend = new YourGirlfriend(you);
        yourGirlfriend.eat("火鸡面");
        
    }
}


/**
 * 行为接口 (代理类和被代理类都实现这个接口)
 * */
interface YourBehavior {
    /**
     * 吃
     * @param something 吃的东西
     */
    void eat(String something);
}

/**
 * 这个类代表你 (被代理类)
 * */
class You implements YourBehavior{

    @Override
    public void eat(String something) {
        System.out.println("你吃:" + something);
    }
}


/**
 * 这个类代表你女友 (代理类)
 * */
class YourGirlfriend implements YourBehavior{

    private You you;

    public YourGirlfriend() {
    }

    // 静态代理 必须依赖被代理类
    public YourGirlfriend(You you) {
        // 说明你的女朋友心里有你~
        this.you = you;
    }

    @Override
    public void eat(String something) {
        System.out.println("帮你泡火鸡面,带到你面前");
        // 代理对象调用 原对象的方法
        you.eat("火鸡面");
        System.out.println("你说:谢谢宝~");
    }
}

运行结果:

吃:火鸡面
========================
帮你泡火鸡面,带到你面前
吃:火鸡面
你说:谢谢宝~

动态代理:
动态代理是在运行时生成的代理类,而不是在编译时确定的。Java中的动态代理机制主要依靠Java反射机制实现。
动态代理更加灵活,因为它可以在运行时决定要代理的对象及其行为,而不需要显式地为每个类编写代理类。
动态代理通常用于实现横切关注点(cross-cutting concerns AOP相关概念后续在Spring框架相关博客会详细介绍)的功能,
例如日志记录、性能监控、事务管理等。因为动态代理可以在运行时将这些功能动态地添加到方法调用中,而不需要修改原始类的代码。这就达到了解耦的目的。

Java中的动态代理主要是通过java.lang.reflect.Proxy类 和 InvocationHandler 接口实现的。
动态代理的代码示例:

JDK动态代理实现的步骤:

  • ①、定义一个接口用于被代理类实现,定义一个被代理类实现前面定义的接口;
  • ②、自定义一个类实现 InvocationHandler接口 并重写invoke方法,在 invoke 方法中调用原方法(被代理类的方法)并自定义一些处理逻辑;
  • ③、通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象,使用代理对象调用方法。 参数说明: loader 目标对象的类加载器,interfaces目标对象实现的全部接口,h 自定义的InvocationHandler实例;

JDK动态代理图示:
在这里插入图片描述

JDK动态代理代码示例:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TestDynamicProxy {

    public static void main(String[] args) {
        You you = new You();
        you.eat("火鸡面");
        System.out.println("========================");
        // 获取代理对象
        YourBehavior proxyInstance = (YourBehavior)ProxyFactory.getProxyObject(you);
        // 使用代理对象 调用方法
        proxyInstance.eat("火鸡面");
    }

}


/**
 * 行为接口 (代理类和被代理类都实现这个接口)
 */
interface YourBehavior {
    /**
     * 吃
     *
     * @param something 吃的东西
     */
    void eat(String something);
}

/**
 * 这个类代表你 (被代理类)
 */
class You implements YourBehavior {

    @Override
    public void eat(String something) {
        System.out.println("你吃:" + something);
    }
}


class YouInvocation implements InvocationHandler {
    private Object targetObj;

    public YouInvocation() {

    }

    public YouInvocation(Object targetObj) {
        this.targetObj = targetObj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("帮你泡火鸡面,带到你面前");
        // 调用原对象的方法
        Object result = method.invoke(targetObj, args);
        System.out.println("你说:谢谢宝~");
        return result;
    }
}

// 代理工厂
class ProxyFactory {
    /**
     * 根据原对象生成代理对象
     *
     * @param targetObj 原对象
     * @return 代理对象
     */
    public static Object getProxyObject(Object targetObj){
       return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),
               targetObj.getClass().getInterfaces(), new YouInvocation(targetObj));
    }
}

运行结果:

你吃:火鸡面
========================
帮你泡火鸡面,带到你面前
你吃:火鸡面
你说:谢谢宝~

3、JDK动态代理的局限性

  • ①、只能代理实现了接口的类: JDK动态代理的机制要求被代理的类必须实现至少一个接口,因为它是基于接口来生成代理类的。这意味着如果目标类没有实现接口,就无法使用JDK动态代理。

  • ②、无法直接代理类的方法: JDK动态代理只能代理接口中定义的方法,无法直接代理类中的方法。如果需要代理类中的方法,就需要使用CGLIB等其他代理机制。

  • ③、性能相对较低: 由于JDK动态代理是基于Java反射机制实现的,相比较于静态代理或者其他代理方式,它的性能会相对较低一些。这是因为在运行时生成代理类和方法调用的过程中,需要进行额外的反射操作,会带来一定的性能开销。

4、使用CGLIB代理机制完成未实现接口的类的代理

CGLIB(Code Generation Library)是一个Java字节码生成库,它被广泛用于在运行时动态生成新的类以实现代理、混入(Mixin)和其他类似的功能。
CGLIB通过生成目标类的子类,并在子类中重写需要代理的方法来实现代理功能,因此它可以代理那些没有实现接口的类。
CGLIB通常与其他代理机制(如JDK动态代理)相比具有更高的性能,因为它不需要依赖于接口,而且可以代理类中的方法,但是不包括final方法,因为被final修饰的方法无法被子类重写。

Spring框架就集成了CGLIB,默认情况下Spring 的AOP功能实现代理的方式:如果目标对象实现了接口,默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

CGLIB中实现动态代理的关键是 MethodInterceptor 接口和 Enhancer 类。
MethodInterceptor接口中的intercept方法 用来拦截增强被代理类的方法 ,Enhancer类的create方法用来创建代理对象。

CGLIB动态代理的实现步骤:

  • ①、引入CGLIB坐标, 目前最新版本为3.3.0
    https://central.sonatype.com/artifact/cglib/cglib/versions
    在这里插入图片描述
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

选择目标类:选择需要代理的目标类,这个目标类可以是任意的普通类,不一定需要实现接口;

  • ②、实现 MethodInterceptor接口,重写 intercept 方法用于拦截增强被代理类的方法;
  • ③、创建Enhancer对象设置相应参数,调用 create()方法创建代理类;

CGLIB动态代理图示:
在这里插入图片描述

CGLIB动态代理代码示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TestDynamicProxy {

    public static void main(String[] args) {
        You you = new You();
        Class<? extends You> youClass = you.getClass();
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(youClass.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(youClass);
        // 设置自定义的代理类拦截器
        enhancer.setCallback(new YourGirlFriend());
        // 创建代理类
        You youProxy = (You) enhancer.create();

        you.eat("火鸡面");
        System.out.println("================");
        youProxy.eat("火鸡面");

    }
}


/**
 * 这个类代表你 (被代理类)
 */
class You {
    public void eat(String something) {
        System.out.println("你吃:" + something);
    }

    public void sleep() {
        System.out.println("你睡觉了");
    }
}

/**
 * 代理类 实现MethodInterceptor 重写 intercept
 */
class YourGirlFriend implements MethodInterceptor {

    /**
     * @param o           被代理对象
     * @param method      被代理方法
     * @param objects     方法入参
     * @param methodProxy 用来调用原始方法
     * @return 方法返回值
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if(method.getName().equals("eat")) {
            System.out.println("帮你泡火鸡面,带到你面前");
        } else if(method.getName().equals("sleep")) {
            System.out.println("帮你盖好被子,给你讲个故事");
        }
        Object object = methodProxy.invokeSuper(o, objects);
        if(method.getName().equals("eat")) {
            System.out.println("你说:谢谢宝~");
        } else if(method.getName().equals("sleep")) {
            System.out.println("你说:晚安~");
        }
        return object;
    }
}

5、JDK动态代理和CGLIB动态代理对比

①、原理

  • JDK动态代理:
    JDK动态代理的原理主要基于Java的反射机制和java.lang.reflect.Proxy类与java.lang.reflect.InvocationHandler接口。
    当通过代理对象调用方法时,实际上是调用了InvocationHandler的invoke()方法。这个方法内部会通过反射调用实际被代理对象的对应方法,并可以在此前后添加额外的处理逻辑。
    在调用Proxy.newProxyInstance()时,如果代理类还没有被创建,JVM会动态地生成一个实现上述指定接口的代理类的字节码,并加载到JVM中。这个过程是透明的,开发者无需关心具体的生成细节。

  • CGLIB动态代理:
    CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用。
    CGLIB动态代理的核心在于通过继承和字节码操作技术,在运行时动态生成目标类的子类,并在子类中插入自定义的拦截逻辑,以此来达到在不修改目标类源码的情况下增强或控制其行为的目的。

②、使用场景

  • JDK动态代理:
    目标类实现了接口,简单的功能增强

  • CGLIB动态代理:
    无接口的类,复杂的AOP逻辑

③、性能(下面的说法有待考证)

  • JDK动态代理:
    在早期JDK版本中,JDK动态代理的性能通常被认为低于CGLIB,主要是因为每次方法调用都需要通过反射(Method.invoke())来完成,反射操作相对较慢。
    但从JDK 1.8开始,尤其是随着后续版本的不断优化,JDK动态代理的性能有了显著提升。特别是在某些场景下,其性能已经与CGLIB相当,甚至有所超越。
    JDK动态代理在创建代理对象时的开销较小,因为它基于接口实现,不需要生成大量的字节码。

  • CGLIB动态代理:
    CGLIB通过字节码技术生成目标类的子类,这种方式在创建代理实例时的开销较大,因为需要生成新的字节码。
    一旦代理对象创建完成,CGLIB的直接方法调用(通过子类覆盖父类方法)在运行时通常比JDK动态代理的反射调用更快,特别是在频繁调用方法的场景下(这个在JDK8版本的测试下,优势并不明显,也许是方法调用的次数还不够多)。
    CGLIB能够代理没有实现接口的类,提供了更广泛的适用范围,但这也意味着在运行时对类进行了修改,增加了潜在的风险。

简单测试下(使用StopWatch计算,JDK8版本):
分别使用JDK和CGLIB的动态代理去测试创建对象的速度,和调用代理方法的速度,分别进行创建50000次对象的时间比较和使用创建好的代理对象调用50000次代理方法的比较。
PS:被代理类是一样的,只是放在了不同的包下,其中JDK的被代理类多实现了一个接口。

测试代码如下:


import com.kinggm.testb.ProxyFactory;
import com.kinggm.testb.YourBehavior;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.util.StopWatch;

import java.lang.reflect.Method;

public class TestDynamicProxy {

    public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch("测试CGLIB和JDK动态代理创建50000次代理对象的时间占比");
        stopWatch.start("CGLIB创建代理对象50000次");
        for (int i = 0; i < 50000; i++) {
            You you = new You();
            Class<? extends You> youClass = you.getClass();
            // 创建动态代理增强类
            Enhancer enhancer = new Enhancer();
            // 设置类加载器
            enhancer.setClassLoader(youClass.getClassLoader());
            // 设置被代理类
            enhancer.setSuperclass(youClass);
            // 设置自定义的代理类拦截器
            enhancer.setCallback(new YourGirlFriend());
            // 创建代理类
            You youProxy = (You) enhancer.create();
        }
        stopWatch.stop();

        stopWatch.start("JDK创建代理对象50000次");
        for (int i = 0; i < 50000; i++) {
            com.kinggm.testb.You you = new com.kinggm.testb.You();
            // 获取代理对象
            com.kinggm.testb.YourBehavior proxyInstance = (YourBehavior) ProxyFactory.getProxyObject(you);

        }
        stopWatch.stop();

// ============================================================================================================
        You you = new You();
        Class<? extends You> youClass = you.getClass();
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(youClass.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(youClass);
        // 设置自定义的代理类拦截器
        enhancer.setCallback(new YourGirlFriend());
        // 创建代理类
        You youProxy = (You) enhancer.create();

        com.kinggm.testb.You you1 = new com.kinggm.testb.You();
        // 获取代理对象
        com.kinggm.testb.YourBehavior proxyInstance = (YourBehavior) ProxyFactory.getProxyObject(you1);


        StopWatch stopWatch1 = new StopWatch("测试CGLIB和JDK动态代理使用代理对象调用50000次代理方法的时间占比");
        stopWatch1.start("CGLIB代理调用方法50000次");

        for (int i = 0; i < 50000; i++) {
            youProxy.eat("火鸡面");
        }
        stopWatch1.stop();

        stopWatch1.start("JDK代理调用方法50000次");
        for (int i = 0; i < 50000; i++) {
            // 使用代理对象 调用方法
            proxyInstance.eat("火鸡面");

        }
        stopWatch1.stop();


        System.out.println(stopWatch.prettyPrint());

        System.out.println("========================================");

        System.out.println(stopWatch1.prettyPrint());
    }
}


/**
 * 这个类代表你 (被代理类)
 */
class You {
    public void eat(String something) {
        System.out.println("你吃:" + something);
    }

    public void sleep() {
        System.out.println("你睡觉了");
    }
}

/**
 * 代理类 实现MethodInterceptor 重写 intercept
 */
class YourGirlFriend implements MethodInterceptor {

    /**
     * @param o           被代理对象
     * @param method      被代理方法
     * @param objects     方法入参
     * @param methodProxy 用来调用原始方法
     * @return 方法返回值
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("帮你泡火鸡面,带到你面前");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("你说:谢谢宝~");
        return object;
    }
}

运行三次结果如下:

第一次:
StopWatch '测试CGLIBJDK动态代理创建50000次代理对象的时间占比': running time = 250062000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
183527300  073%  CGLIB创建代理对象50000066534700  027%  JDK创建代理对象50000========================================
StopWatch '测试CGLIBJDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1168326000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
583097900  050%  CGLIB代理调用方法50000585228100  050%  JDK代理调用方法50000次

第二次:
StopWatch '测试CGLIBJDK动态代理创建50000次代理对象的时间占比': running time = 270423500 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
198542800  073%  CGLIB创建代理对象50000071880700  027%  JDK创建代理对象50000========================================
StopWatch '测试CGLIBJDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1194475300 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
616564500  052%  CGLIB代理调用方法50000577910800  048%  JDK代理调用方法50000次


第三次:
StopWatch '测试CGLIBJDK动态代理创建50000次代理对象的时间占比': running time = 243452600 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
184046700  076%  CGLIB创建代理对象50000059405900  024%  JDK创建代理对象50000========================================
StopWatch '测试CGLIBJDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1227753400 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
572891200  047%  CGLIB代理调用方法50000654862200  053%  JDK代理调用方法50000

从结果可以看出基本符合上面的性能总结:
JDK的动态代理创建代理对象的速度比CGLIB快的多,代理对象调用方法的速度不相上下。

6、JDK动态代理为什么只能代理实现接口的类?

如果别人问我这个问题,我会这样回答:
因为JDK提供的创建代理对象的API

Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),
               targetObj.getClass().getInterfaces(), new YouInvocation(targetObj));

第二个参数就是要传被代理对象实现的接口,这是王八的屁股,规定!

网上还有一种说法是:
因为Java语言的类是单继承的,JDK自动生成的代理类已经继承了Proxy类,要想保证代理类和被代理类有一致的行为(方法),显然不能通过继承重写方法来实现了,因为JDK自动生成的代理类已经继承了Proxy类;那么只好让被代理类再实现一个接口,JDK自动生成的代理类也去实现这个接口,就能实现代理类和被代理类有一致的行为(方法)了。那么在代理类中重写被代理类的方法就能完成代理(功能增强)。

**对于任何网上的说法持怀疑态度 **
有些比较模糊的观点最好还是去比较权威的书上查证 ,或者去看源码查证

下面就去看看JDK动态代理相关的源码,我们来考证下网上的说法对不对。

进入Proxy.newProxyInstance方法中
有个getProxyClass0方法 (注释:Look up or generate the designated proxy class —— 查找或者生成指定的代理类)
该方法返回生成的代理类的class对象
在这里插入图片描述
再往下看
在这里插入图片描述

再看下getProxyClass0方法内部:(英语真的挺重要,尤其是看源码注释的时候)
在这里插入图片描述
再继续看 ProxyClassFactory
ProxyClassFactory是个静态的内部工厂类
作用就是根据给定的类加载器和接口数组生成、定义并返回代理类
生成的代理类的前缀是$Proxy
在这里插入图片描述
再往下看 代理类包名和类名的生成规则
在这里插入图片描述
答案已经近在眼前了
进入ProxyGenerator.generateProxyClass方法
在这里插入图片描述

那我们就在JVM启动的时候 加上 启动参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

通过IDEA设置JVM启动参数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个时候再执行下代码 查看结果:

public static void main(String[] args) throws Exception{
        YourBehavior you = new You();
        Class<?> youClass = you.getClass();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(you);
        YourBehavior proxyInstance = (YourBehavior)Proxy.newProxyInstance(youClass.getClassLoader(),youClass.getInterfaces(),myInvocationHandler);
        proxyInstance.eat("火鸡面");
        System.out.println(proxyInstance.getClass());
    }

结果:

帮你泡火鸡面,带到你面前
你吃:火鸡面
你说:谢谢宝~
class com.kinggm.test.$Proxy0

可以看到生成的代理类全限定名为: com.kinggm.test.$Proxy0

生成的文件在 项目根目录下:
在这里插入图片描述
由于我的接口

interface YourBehavior{
    void eat(String something);
}

并没有加public
所以生成的代理类包名 并不是默认的 com.sun.proxy 而是和我的接口包名相同
并且继承了 Proxy类 和被代理类实现了相同的接口YourBehavior
在这里插入图片描述
最后再看下 JDK自动生成的代理类结构:
(实际上生成的是.class文件,IDEA通过 FernFlower decompiler 插件把 .class字节码文件反编译成我们能看懂的.java类文件)

我们主要看三个地方$Proxy0的构造方法,初始化,重写的eat方法

①、构造方法

 public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

实际上执行的是父类 Proxy的构造方法,把我们自定义的InvocationHandler 传给父类Proxy中的InvocationHandler h

  protected InvocationHandler h;
  
  protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

②、静态代码块初始化
其中m3 就是我们实现的接口中的目标方法eat

private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

 static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.kinggm.YourBehavior").getMethod("eat", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

③、重写的目标(eat)方法

public static void main(String[] args) {
        YourBehavior you = new You();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(you);
        YourBehavior proxyInstance = (YourBehavior) Proxy.newProxyInstance(you.getClass().getClassLoader(),
                you.getClass().getInterfaces(),
                myInvocationHandler);
        proxyInstance.eat("火鸡面");
    }

当我们执行代理类的eat方法时实际上执行的是 $Proxy0中的 eat方法

public final void eat(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

$Proxy0中的 eat方法调用的是父类中初始化的 MyInvocationHandler 中的invoke方法

class MyInvocationHandler implements InvocationHandler {
    private YourBehavior targetObj;
    public MyInvocationHandler() {
    }
    public MyInvocationHandler(YourBehavior targetObj) {
        this.targetObj = targetObj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("帮你泡火鸡面,带到你面前");
        Object result = method.invoke(targetObj,args);
        System.out.println("你说:谢谢宝~");
        return result;
    }
}

所以在执行目标方法eat前后,你女朋友帮你泡火鸡面,带到你面前,你对你女朋友说了 谢谢宝~

分析至此, 发现网上说的好像也有点道理, JDK自动生成的代理类利用继承Proxy来初始化InvocationHandler ,并处理代理对象的生成逻辑,Java又是单继承的语言,想保证代理类和被代理类有一致的行为(方法),就只好让被代理类再实现一个接口,JDK自动生成的代理类也去实现这个接口,就能实现代理类和被代理类有一致的行为(方法)了。最后在代理类中重写被代理类的方法通过调用父类Proxy中初始化的自定义InvocationHandler 中的invoke方法,就能执行到我们自己写的增强逻辑了,同时invoke方法中又去调用了原方法,就完成了整个代理过程喽。

最后再画个图总结下吧:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/668599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Git】git合并分支指定内容到主分支

git合并分支指定内容到主分支 在现实开发中&#xff0c;往往需要合并分支内容&#xff0c;如下图&#xff1a; 我们平时在其他分支修改了部分代码&#xff0c;如何将分支部分代码合并到主分支上面呢&#xff1f; 合并步骤&#xff1a; 1、切换当前到主分支 git checkout m…

Java-----String类

1.String类的重要性 经过了C语言的学习&#xff0c;我们认识了字符串&#xff0c;但在C语言中&#xff0c;我们表示字符串进行操作的话需要通过字符指针或者字符数组&#xff0c;可以使用标准库中提供的一系列方法对字符串的内容进行操作&#xff0c;但这种表达和操作数据的方…

函数的创建和调用

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 提到函数&#xff0c;大家会想到数学函数吧&#xff0c;函数是数学最重要的一个模块&#xff0c;贯穿整个数学学习过程。在Python中&#xff0c;函数…

Flutter开发效率提升1000%,Flutter Quick教程之对组件进行拖拽与接收

1&#xff0c;首先&#xff0c;所有可以选择的组件&#xff0c;都在左边的组件面板里。从里面点击任何一个&#xff0c;按住左键&#xff0c;向右边的手机面板上进行拖拽即可。 2&#xff0c;拖拽后&#xff0c;我们要选择一个接收组件。什么时候可以接收组件&#xff0c;就是当…

小柴带你学AutoSar系列一、基础知识篇(4)编译

小柴带你学AutoSar总目录https://blog.csdn.net/qianshang52013/article/details/138140235?spm1001.2014.3001.5501 Flechazohttps://www.zhihu.com/people/jiu_sheng 编译真的很重要&#xff01;了解一下机器是如何工作的吧。当然啦&#xff01;通过学习这篇文章还可以学习…

【Go语言精进之路】构建高效Go程序:掌握变量、常量声明法则与iota在枚举中的奥秘

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 引言一、变量1.1 基础知识1.2 包级变量的声明形式深入解析&#x1f4cc; 声明并同时显式初始化&#x1f4cc; 声明但延迟初始化&#x1f4cc; 声明聚类与就近原则 1.3 局部变量的声明形式深入探讨&#x1f4cc; 延迟初始化的…

【原创教程】MES服务器与成品打标机控制说明

1 实现的功能及应用的场合 MES即制造执行系统(manufacturing execution system,简称MES),即在加强MRP计划的执行功能,把MRP计划同车间作业现场控制,通过执行系统联系起来。 MES是一个生产管理智能化的一个系统,是用于生产时记录数据、产量等信息的智能管理系统。 该项…

go语言基于Gin集成后台管理系统开发定时任务管理cron/v3好用又好看

系统目前是支持两种定时类型&#xff0c;一种是函数类型&#xff0c;一种是接口类型&#xff0c;来支持多样的业务&#xff1b;时间周期可视化选择&#xff0c;方便设定执行周期。框架UI漂亮&#xff0c;添加管理定时任务设置简单&#xff0c;客户都可以做自己调整执行时间周期…

LLC开关电源开发:第一节,LLC原理概述

第一节&#xff0c;LLC原理概述文章目录 一、LLC概述二、LLC电路拓扑1.电路拓扑2.电路工作原理3.电路原理分析 总结 一、LLC概述 LLC电路&#xff0c;是一种通过控制开关频率&#xff08;频率调节&#xff09;来实现输出电压恒定的谐振电路&#xff0c;它包括一个电感L、一个电…

transfomer中attention为什么要除以根号d_k

简介 得到矩阵 Q, K, V之后就可以计算出 Self-Attention 的输出了&#xff0c;计算的公式如下: A t t e n t i o n ( Q , K , V ) S o f t m a x ( Q K T d k ) V Attention(Q,K,V)Softmax(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)Softmax(dk​ ​QKT​)V 好处 除以维…

算法每日一题(python,2024.05.31)

题目来源&#xff08;力扣. - 力扣&#xff08;LeetCode&#xff09;&#xff0c;简单&#xff09; 解题思路&#xff1a; 二次遍历&#xff0c;第一次遍历用哈希表记录每个字母的出现次数&#xff0c;出现一次则将它的value值赋为True&#xff0c;将它的下标赋为key值&#x…

leetcode74搜索二维矩阵

题目 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 fa…

LeetCode-47 全排列Ⅱ

LeetCode-47 全排列Ⅱ 题目描述解题思路代码说明 题目描述 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 示例 &#xff1a; 输入&#xff1a;nums [1,1,2]输出&#xff1a; [[1,1,2], [1,2,1], [2,1,1]] b站题目解读讲的不好&…

充电宝哪个牌子好?怎么选充电宝?压箱底充电宝购买指南大全!

充电宝作为我们日常生活中不可或缺的便携式电源之一&#xff0c;市场上品牌众多、种类繁多。对于消费者来说&#xff0c;如何选择适合自己的充电宝成为一个值得重视的问题。有的充电宝厂家为节省成本“偷工减料”&#xff0c;使用劣质电池&#xff0c;以次充好、参数造假等现象…

Win10安装TensorRT

目录 什么是TensorRT 下载TensorRT 安装TensorRT 拷贝文件 安装whl文件 验证是否安装成功 什么是TensorRT TensorRT是由Nvidia推出的C语言开发的高性能神经网络推理库&#xff0c;是一个用于生成部署的优化器和运行时引擎。和cudnn类似&#xff0c;但它不支持训练&#xff…

Mysql(一)查询Sql是如何执行的

Hello&#xff0c;大家好我是极客涛&#x1f60e;&#xff0c;我最近在整理Mysql相关的知识点&#xff0c;所以准备开启一个Mysql的主线任务&#xff0c;大概耗时3周左右&#xff0c;整个节奏还是由浅入深&#xff0c;主要包括Mysql的架构、事务实现、索引组织形式、SQL优化、日…

kettle 使用动态变量名定义变量

name是变量&#xff0c;value 值也是变量 我需要把name作为变量名&#xff0c;value作为变量值&#xff1b; 在kettle中&#xff0c;使用javascript脚本 key与lastVsxzl都是变量 //Script here setVariable(key,lastVsxzl,r);var rgetVariable(key,r); Demo 1、从记事本里面…

sensitive-word 敏感词 v0.16.1 新特性支持字典内存资源释放

敏感词系列 sensitive-word-admin 敏感词控台 v1.2.0 版本开源 sensitive-word-admin v1.3.0 发布 如何支持分布式部署&#xff1f; 01-开源敏感词工具入门使用 02-如何实现一个敏感词工具&#xff1f;违禁词实现思路梳理 03-敏感词之 StopWord 停止词优化与特殊符号 04-…

【第十三节】C++控制台版本坦克大战小游戏

目录 一、游戏简介 1.1 游戏概述 1.2 知识点应用 1.3 实现功能 1.4 开发环境 二、项目设计 2.1 类的设计 2.2 各类功能 三、程序运行截图 3.1 游戏主菜单 3.2 游戏进行中 3.3 双人作战 3.4 编辑地图 一、游戏简介 1.1 游戏概述 本项目是一款基于C语言开发的控制台…

linux--------线程的同步和互斥

前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、线程互斥 &#xff08;1&#xff09;互斥&#xff1a; 任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区&#xff0c;访问临界资源&#xff0c;通常对临界资源起保护作用 要了解互…