浅谈java动态代理

本文介绍了Java动态代理的概念,对比静态代理并提供示例。动态代理由Proxy类和InvocationHandler接口协作完成,通过newProxyInstance静态方法生成代理类,实现方法调用的拦截和额外处理。在invoke方法中执行被代理类的方法,并在调用前后加入额外操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  看到很多介绍Java动态代理的文章上来就是一大推动态代理的例子和源码,让我们这些菜鸡情何以堪。。。研究了一天,简单的看了下源码,把自己的一些理解记下来。
  动态代理听起来很高大上,其实也只是一种代理的实现方式,与之相对的还有一种静态代理。首先来聊一聊“代理”这个东西。代理作为一种经典的设计模式,应用灰常广泛,举个实际栗子:


还在路上,稍等...

如上图所示,厂商委托微商帮它卖东西,买家从微商这里买东西,而且一般情况下买家是不知道背后是哪个厂商(所以才会有辣么多假货)。微商有点像厂商提供对外的“接口”,买家想要拿到里面的商品必须要通过微商(厂家直供什么的除外)。如此,厂商(被代理类)就可以在不调整公司部门结构(类的实现)的情况下,通过微商(代理类)为买家(一般为方法调用)提供服务渠道,更重要的是微商在把商品卖给买家之前和之后还可以做一些不可描述的事情。。。综合以上描述,我们可以知道代理具备以下两个优点:

  1. 可以隐藏被代理类的实现细节;
  2. 实现调用方和被代理类之间的解耦,在不修改被代理类的基础上增加额外的处理。

  作为一个程序猿,还是看代码来的痛快一点,下面来一段炒鸡简单的静态代理的栗子。显示定义两个接口类:

package blog.xu;

public interface Goods1 {
    public void produce1();
}
package blog.xu;

public interface Goods2 {
    public void produce2();
}

被代理类:

package blog.xu;

public class Manufacturer implements Goods1,Goods2{

    @Override
    public void produce1() {
        System.out.println("商品1");
    }

    @Override
    public void produce2() {
        System.out.println("商品2");
    }

}

代理类:

package blog.xu;

public class StaticProxy implements Goods1,Goods2{

    private Manufacturer proxied;

    public StaticProxy(Manufacturer proxied){
        this.proxied = proxied;
    }

    public void doSomethingBefore(){
        System.out.println("偷天");
    }

    public void doSomethingAfter(){
        System.out.println("换日");
    }


    @Override
    public void produce1() {
        System.out.println("********************");
        doSomethingBefore();
        proxied.produce1();
        doSomethingAfter();
    }

    @Override
    public void produce2() {
        System.out.println("********************");
        doSomethingBefore();
        proxied.produce2();
        doSomethingAfter();
    }
}

写个测试程序:

public static void main(String[] args){
    Manufacturer manufacturer = new Manufacturer();
    StaticProxy proxy = new StaticProxy(manufacturer);
    manufacturer.produce1();
    manufacturer.produce2();
    proxy.produce1();
    proxy.produce2();
}

结果:


静态代理测试结果

  代理类需要实现和被代理类相同的接口列表,这样就能强制包含被代理类的所有方法。其次,代理类需要有一个被代理类的引用,用来调用原生的方法。可以看到在调用代理类中的对应方法中,可以在被代理类的方法调用之前和之后做一些额外的工作,事实上这就是Spring AOP的拦截功能策略,在被代理类的基础上增加切面逻辑。
  好了,看到这里估计有童鞋就要问了:我要实现的接口里面有100种方法,那我写代理类岂不是要重复复制

@Override
public void produce1() {
    doSomethingBefore();
    proxied.produce1();
    doSomethingAfter();
}

这样的代码100次?没办法,搬砖嘛。。。好在有java大佬为我们这些小虾米减轻负担,所以就有了Proxy类InvocationHandler接口前者是所有动态生成代理类的父类,后者用来连接被代理对象并执行各种代理操作。简单来说就是代理类的绝大部分方法调用(包括Object类中的hashCode、toString和equals,所以在invoke中调用代理类的这三个方法用造成循环调用导致爆栈)都会被转交到InvocationHandler接口的实现类中执行,更确切地说就是其中的invoke方法。
  从上面的描述可以知道,动态代理是由一个代理类和一个调用处理类来协作完成代理功能的。先来说一说这个调用处理类,它需要实现java.lang.reflect中的InvocationHandler接口。下面是一个简单的实现类:

package blog.xu;

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

public class InvocationHandlerImp implements InvocationHandler{

    private Object proxied;

    public InvocationHandlerImp(Object proxied){
        this.proxied = proxied;
    }

    public void doSomethingBefore(){
        System.out.println("偷天");
    }

    public void doSomethingAfter(){
        System.out.println("换日");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        System.out.println("********************");
        System.out.println(method);
        doSomethingBefore();
        method.invoke(proxied, params);
        doSomethingAfter();
        return null;
    }
}

这个接口只有一个方法invoke,实现这个方法就阔以了。invoke方法一共三个参数,第一个参数为当前代理类,第二个参数为待调用方法对象,第三个参数为方法调用的参数列表。为了完成代理功能,所以必须得保持一个指向被代理类的引用,在上例中被代理类在调用处理类的构造函数中初始化。在invoke方法中通过method.invoke(proxied, params)完成方法调用。可以看出每一个调用调用处理类invoke方法的请求都会执行doSomethingBefore()和doSomethingAfter(),完成了上面我们要复制100次的功能。。。
  接下来就来看最最重要的代理类实现,照例先上一段代码:

package blog.xu;

import java.lang.reflect.Proxy;

public class DynamicProxyTest {
    public static void main(String[] args) {
        Object proxy = Proxy.newProxyInstance(
                      Manufacturer.class.getClassLoader(), 
                      Manufacturer.class.getInterfaces(), 
                      new InvocationHandlerImp(new Manufacturer()));    
         System.out.println(proxy.getClass().getName());
        ((Goods1)proxy).produce1();
        ((Goods2)proxy).produce2();
    }
}

从上面看来创建一个动态代理类是辣么的简单,才一行代码。。。通过调用Proxy类的newProxyInstance静态方法,一个代理类就神奇地产生了。废话少说,赶紧看一波JDK源码压压惊,除去一些必要的非空检查和权限检查,newProxyInstance中有这么几行关键代码:

Class class_ = Proxy.getProxyClass0(classLoader, arrclass2);
final Constructor constructor = class_.getConstructor(constructorParams);
constructor.newInstance(invocationHandler);

第一行代码是整个动态代理生成过程的灵魂,这个函数在运行时动态生成了代理类的字节码,并通过指定的类加载器载入到虚拟机中,并返回对应的类对象。具体是怎么生成的字代码我没看,有兴趣的童鞋自己去啃代码吧。getProxyClass0的两个参数分别是类加载器和接口类型列表,实际上也是通过这两个参数来唯一确定一个代理类。所以只有在Proxy.newProxyInstance中传入相同的类加载器和接口类型列表才会用同一个代理类生成代理类实例。另外,自动生成的代理类类名统一前缀“$Proxy”,后面加上从0依次递增的整型数。
  第二行代码通过getConstructor函数拿到生成的代理类的构造函数,这个constructorParams参数是Proxy类的私有常量值:

private static final Class<?>[] constructorParams = 
                                    new Class[]{InvocationHandler.class};

类型列表只包含一个调用处理器类,因为Proxy只能通过带有调用处理器类参数的构造函数初始化,Proxy的构造函数如下:

private Proxy() {
}

protected Proxy(InvocationHandler invocationHandler) {
    this.doNewInstanceCheck();
    this.h = invocationHandler;
}

代理类作为Proxy的子类自然有同样的限制。拿到构造函数对象后就可以通过第三行来初始化一个代理类实例。到此便完成了整个代理类实例的生成过程。运行测试代码DynamicProxyTest得到如下结果:


还在路上,稍等...

才疏学浅,如对您有所帮助不胜荣幸,如有错误还请不吝赐教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值