2 代理-动态代理-JDK

动态代理

考虑到静态代理的缺点,如果系统能自动生成代理类,我们只需要考虑相关代理功能的实现,是最好的。实现动态代理有两种方式:

  1. 所有的目标类都有一个共同的父类(接口),这样代理对象只需要实现接口,通过多态的形式,内部持有目标对象,这样就可以通过代理对象对外暴露相关方法了。这就是JDK动态代理的思想,缺陷是目标对象必须有个父类接口。
  2. 不计较目标对象,只要给目标对象,创建一个对象继承目标对象,针对目标对象中需要业务代理的方法,进行代理业务,实际功能是调用父类(目标对象)的方。这就是cglib。没有缺点,什么目标对象都可以。

java文件和class文件

正常的静态代理,需要手动写个java文件(新建代理类),动态代理的作用就是省去java文件,单并不是说系统会自动生成java文件,而是会生成java对应的class文件,或者说生成一个Class对象。具体可以看看class文件的加载过程。

JDK动态代理

上述讨论的动态代理的形式,提供一系列的接口,可以生成相关接口的代理对象。jdk提供了一个Proxy类,查看Proxy的方法:

public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)

根据指定的接口,生成相关的代理对象的Class对象,我们先看看到底生成的是什么样的代理对象。

public class DynProxyTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<?> proxyClass = Proxy.getProxyClass(DynProxyTest.class.getClassLoader(), new Class[]{Singer.class});
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        printClassInfo(CaiQin.class);
        printClassInfo(Singer.class);
        printClassInfo(proxyClass);


        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        Singer singerProxy =(Singer) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                CaiQin caiQin = new CaiQin();
                System.out.println("proxy");
                System.out.println(proxy.getClass().getName());
                System.out.println(method.getName());
                Object result = method.invoke(caiQin,args);
                return result;
            }
        });
        singerProxy.sing("你好刀郎");
    }

    private static void printClassInfo(Class cla){
        System.out.println("className: "+cla.getName());
        System.out.println("构造函数:");
        for (Constructor constructor : cla.getConstructors()) {
            StringBuilder sb = new StringBuilder();
            for (Class param : constructor.getParameterTypes()) {
                sb.append(","+param.getTypeName());
            }
            if(sb.length() == 0){
                System.out.println(constructor.getName()+"()");
            } else{
                System.out.println(constructor.getName()+"("+sb.substring(1)+")");
            }
        }
        System.out.println("方法:");
        for (Method method : cla.getMethods()) {
            StringBuilder sb = new StringBuilder();
            for (Class param : method.getParameterTypes()) {
                sb.append(","+param.getTypeName());
            }
            if(sb.length() == 0){
                System.out.println(method.getName()+"()");
            } else{
                System.out.println(method.getName()+"("+sb.substring(1)+")");
            }
        }
        System.out.println("属性:");
        for (Field field : cla.getDeclaredFields()) {
            System.out.println(field.getName()+"="+field.getDeclaringClass().getName());
        }

        System.out.println();
        System.out.println();
        System.out.println();

    }
}


输出内容:

className: com.jkf.java.proxy.dyn.CaiQin
构造函数:
com.jkf.java.proxy.dyn.CaiQin()
方法:
sing(java.lang.String)
wait()
wait(long,int)
wait(long)
equals(java.lang.Object)
toString()
hashCode()
getClass()
notify()
notifyAll()
属性:



className: com.jkf.java.proxy.sta.Singer
构造函数:
方法:
sing(java.lang.String)
属性:



className: com.sun.proxy.$Proxy0
构造函数:
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
方法:
equals(java.lang.Object)
toString()
hashCode()
sing(java.lang.String)
isProxyClass(java.lang.Class)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,java.lang.Class[],java.lang.reflect.InvocationHandler)
getProxyClass(java.lang.ClassLoader,java.lang.Class[])
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
属性:
m1=com.sun.proxy.$Proxy0
m3=com.sun.proxy.$Proxy0
m2=com.sun.proxy.$Proxy0
m0=com.sun.proxy.$Proxy0

经过比对Proxy的Class对象,在接口的基础上,补充了一些公共方法equals、hashCode、wait、notify等方法,最终重要的提供了:构造函数(入参InvocationHandler),这样通过Class对象我们就可以直接生成代理对象了。这就是JDK动态代理的功能,父接口不能直接创建对象,但是JDK动态代理提供了带有构造函数的Class对象,解决了接口不能创建对象的情况。剩下就是通过Class反射创建对象。

InvocationHandler

JDK动态代理生成的Class对象,有个入参为InvocationHandler的构造函数,先看看InvocationHandler。

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

InvocationHandler是接口,有且只有一个方法:invoke,这样我们需要匿名类实现该接口。通过注解我们了解到invoke方法的入参:
proxy:代理对象,注意这是代理对象,不是目标对象。
method:父类接口的方法对象。
args:代理对象方法的入参。
了解反射的相关应用,我们可以通过method.invoke(Object obj,Object[] args),执行方法,通过比较,发现method.invoke和InvocationHanlder的invoke是类似的。其实从InvocationHandler的命名就可以发现,方法调用时,就会执行这个handler。

Proxy创建代理对象

刚才通过Proxy获取代理对象的Class文件,再手动创建对象比较繁琐,Proxy提供了一种快速的创建方法:

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

可以直接创建代理对象。
参数说明:

  1. ClassLoader主要用来声明父接口的加载来源,正常情况,我们可以使用:当前类的Class对象的classLoader,例如:XXX.class.getClassLoader()。
  2. interfaces:父接口。
  3. InvocationHandler:方法调用的处理对象。
    创建一个代理对象,看看执行的流程。
public class DynProxyJdkTest {
    public static void main(String[] args) {

        Singer singer = (Singer) Proxy.newProxyInstance(DynProxyJdkTest.class.getClassLoader(),new Class[]{Singer.class},new InvocationHandler(){

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(proxy.getClass());
                System.out.println(method.getName()+"==="+method.getDeclaringClass().getName());
                return null;
            }
        });
        singer.sing("你好啊");
    }
}
输出内容:
class com.sun.proxy.$Proxy0
sing===com.jkf.java.proxy.sta.Singe

根据输出内容,的确代理对象执行了,定义的InvocationHandler,但是尴尬的是,这个怎么执行目标对象的方法呢?考虑到InvocationHandler中,提供了Method对象,根据反射+目标实例,就可以执行目标方法了,因此可以对InvocationHandler进行改进。

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

        Singer singer = (Singer) Proxy.newProxyInstance(DynProxyJdkTest.class.getClassLoader(),
                new Class[]{Singer.class},
                new SingerInvocationHandler(new DaoLang()));
        singer.sing("你好啊");
    }
}


class SingerInvocationHandler implements InvocationHandler {

    private Singer singer;

    public SingerInvocationHandler(Singer singer) {
        this.singer = singer;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("收到演出邀约");
        System.out.println();
        System.out.println();
        Object ret = method.invoke(singer, args);
        System.out.println();
        System.out.println();
        System.out.println("收到尾款");
        return ret;
    }
}

输出内容:

收到演出邀约


大家好,我是:刀郎......
开始演唱:你好啊
结束演唱:你好啊


收到尾款

这就是常用的JDK动态代理的样例,自定义InvocationHandler,然后通过多态持有目标对象。

Proxy0.class

我们可以直接通过反编译工具查看JDK动态代理生成的class文件。

  1. 设置本地生成class文件。
//跟踪Proxy的源码,你会发现有一个判断saveGeneratedFiles
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

2 查看本地class文件
如下图:
在这里插入图片描述在target的同级中,你会发现一个com文件,点进去,就会发现 P r o x y 0. c l a s s 文 件 , 这 就 是 P r o x y 生 成 的 c l a s s 文 件 。 这 也 是 刚 才 输 出 的 代 理 类 名 称 : c o m . s u n . p r o x y . Proxy0.class文件,这就是Proxy生成的class文件。这也是刚才输出的代理类名称:com.sun.proxy. Proxy0.classProxyclasscom.sun.proxy.Proxy0

  1. 文件分析
    通过反编译工具,查看class文件。$Proxy0.class是继承自Proxy,同时实现了父接口。而且其中的equals、toString、hashCode方法都是Object类的方法,但是实际执行的时候,并没有调用Object的实例,而是调用InvocationHandler的invoke方法,而Handler中的invoke,转手就调用了目标对象的相关方法,就是说方法的调用Proxy、代理对象谁都没有实现具体功能,但是Proxy把处理的时机对我们暴露了,我们把需要的操作添加在了目标对象执行的前面或者后面。从这可以看出Proxy实际上什么业务逻辑都没有做,只是提供了一个接口的子类,并且给子类添加了构造方法。

  2. 更多的接口
    (1) 大家可以通过Proxy.newInstance提供更多的接口,看看有什么区别没。
    (2) 顺便看看在目标对象中,调用另外一个代理方法,是不是会触发两次代理效果,答案是:不会的,这是使用动态代理需要注意的地方。

JDK动态代理的缺点

  1. 共同接口:所有的目标对象,都必须要实现共同的接口,才能针对上述接口进行动态代理。
  2. 如果想对已有代码进行动态代理,但是发现共同接口中,只想代理某一个/几个功能,那么InvocationHandler里面就需要对方法进行特殊判断后,再进行代理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值