JDK动态代理简析

前言

代理模式通过为其他对象提供一种代理以控制对这个对象的访问,可以增加或者减少原始对象提供的功能。代理分为远程代理、虚拟代理、保护代理等。

远程代理,为不同位置对象提供本地代表对象;虚拟代理,当真正需要对象的时候才真正创建需要的对象;保护代理通常会对真实对象的访问增加权限机制,确保对象不会被非法访问;

现在来看一下代理的实现方法,通常包含静态代理和动态代理两大类。

静态代理

代理和被代理对象在代理之前是确定的,它们都实现相同的借口或者抽象类,这样代理和被代理对象就有着相同的借口。用户访问代理对象接口时会将请求最终转发到真实对象上去。

public interface UserApi {
    boolean login(String name, String password);
}

public class UserManager implements UserApi {
    @Override
    public boolean login(String name, String password) {
        System.out.println(name + " " + password);
        return true;
    }
}

public class UserProxy implements UserApi {
    private UserApi userApi = new UserManager();
    private static final int MAX_COUNT = 10;
    private int count = 0;
    @Override
    public boolean login(String name, String password) {
        if (count > MAX_COUNT) {
            return false;
        }

        return userApi.login(name, password);
    }
}

上面的代码就是普通组合方式的静态代理,UserProxy代理了UserManager对象,如果用户持续的访问登录login接口就会直接拒绝请求,这种代理其实就是保护代理。
这里写图片描述

动态代理

前面的静态代理可以看出来为每个对象生成代理都需要多增加一个代理类,如果有很多个类都需要做代理那么就要生成同样多的代理类。如果有一种自动能够为类生成代理的方法呢,就不用用户维护这么多代理类了。JDK内部自带了一个动态代理的实现方案,其实是在代理类和被代理对象之间增加了一个InvocationHandler的事务处理器对象。

public class DynamicProxy {
    public static void main(String[] args) {
        UserApi userApi = (UserApi) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), new Class<?>[] {
                UserApi.class
        }, new AccessTimesHandler(new UserManager()));

        userApi.login("zhangsan", "1234561");
    }

    private static class AccessTimesHandler implements InvocationHandler {
        private static final int MAX_COUNT = 10;
        private int count = 0;
        private Object target;

        public AccessTimesHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = false;
            if (count < MAX_COUNT) {
                result = method.invoke(target, args);
            }
            count++;
            System.out.println(count);
            return result;
        }
    }

    private static class LogInvocationHandler implements InvocationHandler {
        private Object target;

        public LogInvocationHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Start login");
            Object result = method.invoke(target, args);
            System.out.println("Login end");
            return result;
        }
    }
}

上面的动态代理只需要在业务处理器的调用方法里写出代理实际执行步骤,通过反射调用实际被代理对象的方法,最后通过Proxy动态的生成代理类,执行代理类实现的IUserApi接口方法就会调用业务处理器实现。
这里写图片描述
那么生成的动态代理类到底是如何生成的,查看Proxy.newProxyInstance实现代码:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    if (h == null) {
        throw new NullPointerException();
    }

    // 生成动态代理类并且加载动态代理类的class对象
    Class<?> cl = getProxyClass0(loader, interfaces);

    // 找到动态代理类中带有InvocationHandler作为参数的构造函数,并且用它生成一个动态代理类对象
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        return newInstance(cons, h);
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

newProxyInstance内部首先生成动态代理类之后用这个类有InvocationHandler参数的构造函数生成一个动态代理对象。接着查看getProxyClass0内部的实现逻辑。

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {

    Class<?> proxyClass = null;

    // 记录所有接口的名字
    String[] interfaceNames = new String[interfaces.length];

    // 所有接口类对象的集合,如果传递进来的接口里有重复接口会被过滤掉
    Set<Class<?>> interfaceSet = new HashSet<>();

    for (int i = 0; i < interfaces.length; i++) {
        // 校验所有接口类并且将类和接口名保存起来
        interfaceSet.add(interfaceClass);

        interfaceNames[i] = interfaceName;
    }

    // 由于动态代理生成较慢,需要为本次生生的动态代理类添加缓存, 
    // 下次生成时速度更快,这里使用动态代理实现的所有接口名作为缓存的键值
    // 如果通过键值在缓存里找到了动态代理类直接返回,否则继续生成代理类
    List<String> key = Arrays.asList(interfaceNames);
    .....

    try {
    // 首先获取动态代理的package信息
        String proxyPkg = null;     // package to define proxy class in

       // 接着获取所有接口里的方法

        {
            List<Method> methods = getMethods(interfaces);

            // 生成代理类的类名
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                    exceptionsArray);
        }
        // add to set of all generated proxy classes, for isProxyClass
        proxyClasses.put(proxyClass, null);

    } finally {
        // 如果生成代理类成功,将生成的代理类放入缓存中,否则需要清空key也就是所有接口生成的对应的动态代理类
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference<Class<?>>(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
        }
    }
    return proxyClass;
}

接着获取动态代理的类就是根据传入接口的报名和接口中包含的所有方法在调用本地方法generateProxy来生成动态代理类。

private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                             ClassLoader loader, Method[] methods,
                                             Class<?>[][] exceptions);

以上就是生成动态类并且获取它的Java类对象的过程,底层具体的实现由于到了本地代码,无法了解具体实现过程,不过JDK6.0有动态编译的功能可以通过动态编译模拟实现这种效果。

可惜的是没办法查看实际生成的源代码,所以只能手动写一下生成的动态代理类如下Proxy$1,可以看到动态代理内部的接口方法会通过interface获取method反射对象,然后再将动态代理对象、method对象和参数一起传递给InvocationHandler,在InvocationHandler内部再调用实际对象UserManager。

public class Proxy$1 implements UserApi {
    private InvocationHandler handler;

    public Proxy$1(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public boolean login(String name, String password) {
        try {
            Method method = UserApi.class.getMethod("login", String.class, String.class);
            return (Boolean) handler.invoke(this, method, new Object[] { name, password});
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return false;
    }

    // 测试代码
    public static void main(String[] args) {
        UserApi userApi = new Proxy$1(new LogInvocationHandler(new UserManager()));
        userApi.login("zhangsan", "123456");
    }

    private static class LogInvocationHandler implements InvocationHandler {
        private Object target;

        public LogInvocationHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Start login");
            Object result = method.invoke(target, args);
            System.out.println("Login end");
            return result;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值