最详细Java中动态代理分析-- Proxy

接下来都是源码分析了,在分析之前必须了解一下代理模式。

什么是代理模式

代理模式是java常用的设计模式,他的特征是代理类与委托类有同样的接口,其他类想访问这个类必须先通过代理类。

在某些情况下,一个客户在访问一个对象之前想再前后做一些固定的操作,那么这个时候就适合用到代理模式。

比较常见的:中介、快递/外卖小哥、黄牛等

JAVA中的静态代理和动态代理

我们分别通过代码来演示静态代理和动态代理

1.先了解一下静态代理

//顶层接口
public interface Person {
    void sayHello();
}
//代理工厂类 如果多一个其他的接口 那么也要多一个代理工厂实现类
public class PersonProxy implements Person {
    private Person person;
    public PersonProxy(Person person) {
        this.person = person;
    }
    //静态代理 每多一个方法 就要写一遍重复的代码
    public void sayHello() {
        System.out.println("调用前");
        person.sayHello();
        System.out.println("调用后");
    }
}
//实现类
public class PersonBoyImpl implements Person {
    public void sayHello() {
        System.out.println("hello 我是男孩");
    }
}
//main方法调用
public static void main(String[] args) {
     new PersonProxy(new PersonBoyImpl()).sayHello();
}

//执行打印的结果
调用前
hello 我是男孩
调用后

以上就是静态代理的实现,缺点很明显,就是每个接口都需要对应的代理工厂类,每个方法都需要去写一遍重复的代码

2.动态代理的两种方式演示

在Java中有两种生成代理的方式我们先来看第一种JDK代理JDK动态代理必须要有实现接口

2.1 JDK动态代理

  • 上述静态代理中接口Person和实现类BoyPerson不变
  • 新建动态代理Handler类
//动态代理Handler类
public class PersonPoxyHandler implements InvocationHandler {
    //保留原始类
    Object target;
	//创建代理类
    public Object getInstance(Object target){
        this.target = target;
        Class<?> clazz = target.getClass();
        //1.生成新的class类 方法名相同 JDK代理对象为接口
        //2.通过实现接口创建
        //3.回调类传了this ,而此类又实现了InvocationHandler,那么调用代理类的invoke会调到当前类
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
	
    //代理调用类
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[JDK]代理调用之前");
        //代理方法执行 此处传入原始类
        Object result = method.invoke(target, args);
        System.out.println("[JDK]代理调用之后");
        return result;
    }
}
  • 接下来我们看下Main方法
public static void main(String[] args) throws Exception {
	//传入实现类,实际上返回的对象已经是代理对象了
   	//如果是其他不同的类,传入不同的接口实现类就可拿到代理对象
    Person person = (Person) new PersonPoxyHandler().getInstance(new BoyPerson());
    person.sayHello();
}

//打印结果,具体原理看后面
[JDK]代理调用之前
hello 我是xxxx
[JDK]代理调用之后

因为此时拿到已经是代理类了,每次调用方法都会走到代理handler类的invoke方法

2.2 CGLIB动态代理

Cglib动态代理是基于字节码生成类,需要引入cglib包。

<dependency>
   <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>
  • 新建一个class类GirlPerson类型
//新建一个类 里面就一个方法
public class GirlPerson{
	public void sayHello() {
	    System.out.println("hello 我是Girl");
	}
}
  • Cglib的handler类,实现MethodInterceptor
/**
 * cglib 动态代理 基于字节码生成类
 * 需要引入cglib包
 */
public class CglibProxyHandler implements MethodInterceptor {
	//通过字节码创建代理对象
    public Object getInstance(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        //回调
        enhancer.setCallback(this);
        return enhancer.create();
    }
	
	//回调 因为创建时传的也是this
	public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("[CGLIB]代理调用之前");
        //代理方法执行 o = 原始类 methodProxy/代理类方法 调用父类
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("[CGLIB]代理调用之后");
        return result;
    }
}
  • main方法执行
public static void main(String[] args) throws Exception {
	//基于继承的形式创建的代理类,不用接口实现
     GirgPerson instance = (GirgPerson)new CglibProxyHandler().getInstance(new GirgPerson().getClass());
     instance.sayWorld();
 }

//打印结果,具体原理看后面
[CGLIB]代理调用之前
hello 我是Girl
[CGLIB]代理调用之后

2.3 两者区别

  • JDK代理是通过实现接口创建代理类,在创建的同时保存了原始类,在通过代理类调用同样的方法时,先经过代理对象invoke逻辑,然后在代理对象里面通过原始类调用对应的方法
  • CGLIB代理时通过字节码重置,经过它生成的对象,内部的代码已经发生改变

上面不懂的不要紧,我准备了代理类生成的源码!

3.JDK动态代理源码分析

  • 从创建代理对象开始,我们来一探究竟
//直接看 Proxy.newProxyInstance 这个方法
 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
		//克隆一下接口
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
         //通过实现接口获得一个代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
			//获取代理类的构造方法
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //实例化代理类 并通过构造方法传参 传入InvocationHandler
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
  • 在main方法执行这几行代码,可以将生成的代理类保存下来
public static void main(String[] args) throws Exception {
	Person person1 = new BoyPerson();
	Class<?>[] interfaces = person1.getClass().getInterfaces();
	byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", interfaces);
	FileOutputStream stream = new FileOutputStream(new File("D:/$Proxy.class"));
	stream.write(bytes);
	stream.flush();
	stream.close();
}
  • 接下来我们来看下$Proxy.class的内容到底是什么?
//我们实际拿到的是这个代理对象
public final class $Proxy extends Proxy implements Person {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
	//构造类 通过newInstance(h) 直接将InvocationHandler传参直接到父类
    public $Proxy(InvocationHandler var1) throws UndeclaredThrowableException {
        super(var1);
    }

	//重点看这里
    public final void sayHello()  {
        try {
            //调用父类的h 然后调用的invoke对象
            super.h.invoke(this, m3, (Object[])null);
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.xxx.demo.动态代理.Person").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从$Proxy.class代理类可以看到,通过构造方法将回调类(handler)传入代理类,然后代理会传入父类super,那么在调用方法时实际执行的是super.h.invoke(),自然就能调用到PersonPoxyHandler类实现的invoke方法了

4.CGLIB动态代理原理分析

  • CGLIB就不去看代理类生成的源码了,采用的是继承原始类然后动态生成的字节码,直接看生成的代理类
public static void main(String[] args) throws Exception {
	//在main方法第一行加上这句代码,就能保存到指定目录
	//存储代理类
	System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
	//基于继承的形式创建的代理类,不用接口实现
    GirgPerson instance = (GirgPerson)new CglibProxyHandler().getInstance(new GirgPerson().getClass());
    instance.sayWorld();
 }
  • 接下来看生成的代理类,里面会有很多同名的方法,主要关注这个
//代理类对象调用方法
public final void sayHello() {
   MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (this.CGLIB$CALLBACK_0 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        //回调类里面的实现方法,多的也不用展开说了
        var10000.intercept(this, CGLIB$sayHello$1$Method, CGLIB$emptyArgs, CGLIB$sayHello$1$Proxy);
    } else {
        super.sayHello();
    }
}

//set回调方法 就是handler
public void setCallbacks(Callback[] var1) {
    this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
}

动态代理的两种方式及原理已经分析就到这里,还想研究更深层次的小伙伴自行了解哦!这里提一点,动态代理在以后的代码分析中起到至关重要的作用,务必学好学懂!

以上就是本章的全部内容了。

上一篇:线程池工作线程ForkJoin的使用
下一篇:SpringMvc手写简单实现篇 - IOC容器、DI依赖注入篇

读书破万卷,下笔如有神

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值