百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Java中的动态代理(java动态代理invoke)

wxin55 2025-05-05 19:14 1 浏览 0 评论

一、什么是代理?

为对象提供了一个代理以控制对某个对象的访问。其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。

代理分类:

  • 静态代理
  • 动态代理

JDK动态代理 ——对于实现了接口的类生成代理,而不是针对类

Cglib动态代理——对于类实现代理,只要是对于指定的类生成一个子类来继承其中的方法


二、静态代理

在说动态代理之前,先提一下静态代理是什么?以及它的局限性在哪?

2.1 什么是静态代理?

为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口。

下面通过代码案例来看一下静态代理的实现方式。

  1. 首先定义一个接口
public interface Action {
    /**
     * walk
     */
    void walk();

    /**
     * talk
     */
    void talk();
}
  1. 被代理类
public class ActionImpl implements Action{
    @Override
    public void walk() {
        System.out.println("没事,走两步...");
    }

    @Override
    public void talk() {
        System.out.println("没事,说两句...");
    }
}
  1. 静态代理类
public class ActionStaticProxy implements Action{
    private final Action action = new ActionImpl();
    @Override
    public void walk() {
        System.out.println("walk前..");
        action.walk();
        System.out.println("walk后..");
    }

    @Override
    public void talk() {
        System.out.println("talk前..");
        action.talk();
        System.out.println("talk后..");
    }
}
  1. 测试
public class Test {

    public static void main(String[] args) {
        Action action = new ActionStaticProxy();
        action.walk();
        System.out.println("------------next method-------------");
        action.talk();
    }
}
输出:
walk前..
没事,走两步...
walk后..
------------next method-------------
talk前..
没事,说两句...
talk后..

分析:在代理类ActionStaticProxy中,在调用每个方法前后都加了输出语句,这个开头所说的代理模式的思想,在方法的前后增加消息处理。

2.2 静态代理的局限性

由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐,增加不必要的工作。

那么,有没有一种能够少写或者甚至不写代理类的方法来实现代理的功能呢?答案是:动态代理


三、动态代理

相比于静态代理,动态代理就是不用自己手写代理类,转而由JDK在运行时通过反射生成代理对象。JDK提供了
java.lang.reflect.InvocationHandler
接口和java.lang.reflect.Proxy类来进行获得代理对象。

例如,像下面这样:

Class<?> actionProxyClazz = Proxy.getProxyClass(ClassLoader loader, Class<?> interface);
  • loader:被代理类的类加载器,目的是为了加载该类实现的接口到内存中
  • interface:代理对象需要和目标对象实现相同的接口

现在说说为什么要使用getProxyClass()这个方法。

用通俗的话说,getProxyClass()这个方法,会从你传入的接口Class中,“拷贝”类结构信息到一个新的Class对象中,但新的Class对象带有构造器,是可以创建对象的。所以,一旦我们明确接口,完全可以通过接口的Class对象,创建一个代理Class,通过代理Class即可创建代理对象。

3.1 案例说明

  1. 接口Action
public interface Action {
    /**
     * walk
     */
    void walk();

    /**
     * talk
     */
    void talk();
}
  1. 接口实现类ActionImpl
public class ActionImpl implements Action{
    @Override
    public void walk() {
        System.out.println("没事,走两步...");
    }

    @Override
    public void talk() {
        System.out.println("没事,说两句...");
    }
}

根据代理Class的构造器创建对象时,需要传入InvocationHandler通过构造器传入一个引用,那么必然有个成员变量去接收。没错这个成员变量就是InvocationHandler。而且代理对象的每个方法内部都会调用handler.invoke()。InvocationHandler对象成了代理对象和目标对象的桥梁,不像静态代理这么直接。

  1. 通过反射创建代理对象的实例
public class DynamicProxyTest {
    public static void main(String[] args) {
        try {
            Action action = new ActionImpl();
            /**
             * args1: 类加载器
             * args2: 代理对象和目标对象实现相同的接口
             */
            Class<?> actionProxyClazz = Proxy.getProxyClass(action.getClass().getClassLoader(),
                                                            action.getClass().getInterfaces());
            // 得到有参构造器 $Proxy0(InvocationHandler h)
            Constructor<?> constructor = actionProxyClazz.getConstructor(InvocationHandler.class);
            // 通过有参构造器new出一个代理对象
            Action proxyAction = (Action) constructor.newInstance(new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("方法执行前...");
                    Object invoke = method.invoke(action, args);
                    System.out.println("方法执行后...");
                    return invoke;
                }
            });
            proxyAction.walk();//调用方法
        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
  1. 输出结果
方法执行前...
没事,走两步...
方法执行后...

3.2 代码优化

在实际场景中,一般直接调用Proxy代理类的下面这个方法来简化代理对象的创建:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

相比于getProxyClass()方法,这里面多了个InvocationHandler对象,而InvocationHandler是一个接口,因此首先要创建一个类实现该接口。

  1. InvocationHandler的实现类
public class DynamicProxy implements InvocationHandler {
    /**
     * 用于接收所有实现了不同接口的实例对象
     */
    private Object object;

    /**
     * 绑定委托对象,并返回代理类
     * @param object
     * @return
     */
    public Object getProxyAfterBind(Object object){
        this.object = object;
        //绑定该类实现的所有接口,获取代理对象,返回类型为Object,具体类型需要在调用方强转
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
    }


    /**
     * @param proxy  代理对象本身
     * @param method 反射调用invoke方法
     * @param args 
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这里就可以进行所谓的AOP编程了
        System.out.println("调用方法前..执行相应的逻辑");
        Object result = method.invoke(object, args);
        System.out.println("调用方法后..执行相应的逻辑");
        return result;
    }
}
  1. 代码测试
public class DynamicProxyTest {

    public static void main(String[] args) {
        Action action = new ActionImpl();
        DynamicProxy dynamicProxy = new DynamicProxy();

        // 获取ActionImpl类的代理对象
        Action proxyAction = (Action) dynamicProxy.getProxyAfterBind(action);
        // 此处调用只是个壳子,正在的实现是通过反射调用DynamicProxy中的invoke方法
        proxyAction.talk();
        System.out.println("-----------分割线--------------");
        // 此处调用只是个壳子,正在的实现是通过反射调用DynamicProxy中的invoke方法
        proxyAction.walk();
    }
}

输出:
调用方法前..执行相应的逻辑
没事,说两句...
调用方法后..执行相应的逻辑
-----------分割线--------------
调用方法前..执行相应的逻辑
没事,走两步...
调用方法后..执行相应的逻辑

3.3 JDK动态代理一定需要实现类吗?

答案是:不一定。

应用场景:Mybatis的Mapper接口代理,Mybatis是在invoke方法里面通过调用SqlSession最后进入到doQuery()方法来执行sql语句并返回对应的结果。

  1. 定义一个Subject接口
public interface Subject {
    /**
     * 根据id查询数据
     * @param id
     * @return
     */
    Object selectById(Integer id);
}
  1. 接口代理对象
public class SubjectProxy<T> implements InvocationHandler {

    private Class<T> proxyInterface;

    public SubjectProxy(Class<T> proxyInterface) {
        this.proxyInterface = proxyInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> declaringClass = method.getDeclaringClass();
        // 这里做一个说明:如果调用的是Object自带的方法,比如:toString()、hashCode()方法,则会进入该分支
        if (Object.class.equals(declaringClass)) {
            System.out.println("declaringClass= " + declaringClass);
        } else {
            if ("selectById".equals(method.getName())) {
                System.out.println("反射调用该方法名称为selectById");
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public T getProxy() {
        return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, this);
    }
}
  1. 测试
public class Test {
    public static void main(String[] args) {
        SubjectProxy subjectProxy = new SubjectProxy(Subject.class);
        Subject subject = (Subject) subjectProxy.getProxy();
        subject.selectById(1);
        // 这会进入到if(Object.class.equals(method.getDeclaringClass()))分支
        subject.toString();
    }
}

四、总结

  1. 动态代理的核心目的是:动态的代理目标实例,并附加一些代理逻辑
  2. 其实无论是静态代理还是动态代理本质都是最终生成代理对象,区别在于静态代理对象需要人手动生成,而动态代理对象是运行时,JDK通过反射动态生成的代理类最终构造的对象,JDK生成的类在加载到内存之后就删除了,所以看不到类文件。
  3. 动态代理的作用:为了进行aop编程,也就是可以在目标类目标方法执行前后添加一些额外的操作。比如进行权限校验记录日志等操作

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: