动态代理机制

146 阅读4分钟

说到动态代理,有时候会在想,既然JDK里面有代理类,直接调用不就完事了吗?为什么还要学动态代理?

但是项目中运行时环境是 JRE,那我们就需要自己去实现动态代理,那需要用什么实现动态代理呢?那就是InvocationHandler类。

之前很苦恼,动态代理到底是个什么鬼玩意,但是了解之后理出来一点思路。

请记住:动态代理只能代理接口!!!

简单的说:如果你是马爸爸,需要找周杰伦做代言,但是又直接接触不到周杰伦,所以只能通过周杰伦的经纪人来谈,那么这就是一个代理机制的实际例子。

马爸爸 ---> 经纪人 ---> 周杰伦

今天讲的是通过JDK的动态代理:

1、在程序运行时,通过反射机制动态生成。

2、通常道理接口下的所有类。

3、事先不知道要代理的是什么,只有在运行的时候才能确定。

4、调用处理程序必须事先Invocation接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类。

5、只能代理接口,要代理类需要使用第三方的CLIGB等类库。

动态代理的好处:

1、降低代码的冗余

2、增强方法

3、项目的可扩展性强

接下来,上代码!

1、首先我们要有个代理的接口,我们就先定义成Person吧,简单的两个方法

public interface Person {
 
    public void sayHello(String content, int age);
 
    public void sayBye(boolean seeAgin, double time);
}

2、要有个被代理对象(类)Student,实现 Person 接口,重写 Person 接口的所有方法

public class Student implements Person {
 
    @Override
    public void sayHello(String content, int age){
        System.out.println("student say hello" + content + " "+ age);
    }
 
    @Override
    public void sayBye(boolean seeAgin, double time){
        System.out.println("student sayBye " + time + " "+ seeAgin);
    }
}

3、写一个 MyInvocationHandler 实现 InvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
 
public class MyInvocationHandler implements InvocationHandler {
 
    private Object object;
 
    public MyInvocationHandler(Object object){
        this.object = object;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("MyInvocationHandler invoke begining ......");
        System.out.println("代理对象proxy: "+ proxy.getClass().getName());
        System.out.println("方法method: "+ method.getName());
        for(Object o : args){
            System.out.println("参数arg: "+ o);
        }
        // 通过反射调用 被代理类的方法
        method.invoke(object, args);
        System.out.println("MyInvocationHandler invoke ending ......");
        return null;
    }
 
    public static void main(String [] args){
        // 创建需要被代理的类
        Student student = new Student();
        // 生成代理类的class文件,前提是你需要在工程根目录下创建com/proxy目录,不然会报找不到路径的io异常
        System.getProperties().put("com.proxy.ProxyGenerator","true");
        // 获取类加载器,曾经为Java打下了半壁江山
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        // 获取被代理类实现的接口
        Class<?>[] interfaces = student.getClass().getInterfaces();
        // 创建被代理类的委托类,之后想要调用被代理类的方法时,都会委托给这个类的invoke(Object proxy, Method method, Object[] args)方法
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(student);
        // 生成代理类
        Person proxy = (Person) Proxy.newProxyInstance(loader, interfaces, myInvocationHandler);
        // 通过代理类调用 被代理类的方法
        proxy.sayHello("xiaokang.ma", 20);
        proxy.sayBye(true, 110);
        System.out.println("end");
    }
}

4、接下来我们来运行一下,看看结果

"C:\Program Files\Java\jdk1.8.0_121\bin\java.exe" 
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayHello
arg: xiaokang.ma
arg: 20
student say helloxiaokang.ma 20
MyInvocationHandler invoke end
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayGoodBye
arg: true
arg: 100.0
student sayGoodBye 100.0 true
MyInvocationHandler invoke end
end

总结:

1、创建被代理的类

2、获取类加载器 ClassLoader(ClassLoader可是为Java打下了半壁江山的男人),ClassLoader将对象加载的进程中

3、获取被代理类实现的接口

4、创建被代理类的委托类 MyInvocationHandler

5、生成 Person 的代理类 proxy

6、proxy 调用 被代理类的方法时,调用的是 MyInvocationHandler 的 invoke()方法,从而实现动态代理。

有兴趣的朋友,可以去看下 Proxy 的源码,主要看 newProxyInstance()方法

@CallerSensitive
    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;
                    }
                });
            }
            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);
        }
    }

当然,还有一种方式是 cglib 的动态代理,想要了解的可以进行深究。

我是进阶的球儿,大家一起2019年的爬坑历程。感觉分享很给力的话给个赞,谢谢!!!有问题也可以下方留言沟通。