码迷,mamicode.com
首页 > 编程语言 > 详细

《Java基础知识》动态代理(InvocationHandler)详解

时间:2019-10-31 18:11:29      阅读:100      评论:0      收藏:0      [点我收藏+]

标签:test   inter   indexof   https   目标   else   创建用户   多少   access   

1. 什么是动态代理

对象的执行方法,交给代理来负责。比如user.get() 方法,是User对象亲自去执行。而使用代理则是由proxy去执行get方法。

举例:投资商找明星拍广告,投资商是通过经纪人联系的,经纪人可以帮明星接这个广告,也可以拒绝。做不做,怎么做都叫给经纪人和投资商谈。

 

2. 实际场景应用

2.1 校验用户权限,每一个菜单请求,都要判断一下请求的用户是否有该菜单权限。菜单多了,代码冗余,且容易遗漏。

通过动态代理就可以实现为:每一个用户,每一个菜单的请求,都经过代理(proxy),由他判断是否有权限,调用者只需要调用,实现自己的逻辑,不关心权限问题。

 

3. 动态代理完整案例:

/**
 * 创建用户接口
 */
public interface UserBean {
    String getUser();
}
import demo.knowledgepoints.section.inf.UserBean;

public class UserBeanImpl implements UserBean {

    private String user = null;

    //flag:0 无权限,1有权限。
    private String flag = null;

    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }

    public UserBeanImpl(String user,String flag)
    {
        this.user = user;
        this.flag = flag;
    }
    public String getUserName()
    {
        return user;
    }

    public String getUser()
    {
        System.out.println("this is getUser() method!");
        return user;
    }

    public void setUser(String user)
    {
        this.user = user;
        System.out.println("this is setUser() method!");
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserBeanProxy implements InvocationHandler {

    private Object targetObject;

    public UserBeanProxy(Object targetObject)
    {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        UserBeanImpl userBean = (UserBeanImpl) targetObject;
        String flag = userBean.getFlag();
        Object result = null;

        //权限判断
        if("1".equals(flag) ){
            result = method.invoke(targetObject, args);
        }else{
            System.out.println("sorry , You don‘t have permission");
        }
        return result;
    }
}
import demo.knowledgepoints.section.inf.UserBean;

import java.lang.reflect.Proxy;

public class TestSection {
    public static void main(String[] args) {
        UserBeanImpl targetObject = new UserBeanImpl("蕾蕾","1");
        UserBeanProxy proxy = new UserBeanProxy(targetObject);
        //生成代理对象
        UserBean object = (UserBean) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), proxy);

        String userName = object.getUser();
        System.out.println("userName: " + userName);
    }
}

运行结果:

技术图片

 

代理代理核心代码

UserBean object = (UserBean) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), proxy);
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

接口:InvocationHandler,代理需要实现该接口,并且实现方法:invoke。

方法:newProxyInstance原理分析:

先看源码

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) throws IllegalArgumentException {
    //验证传入的InvocationHandler不能为空
    Objects.requireNonNull(h);
    //复制代理类实现的所有接口
    final Class<?>[] intfs = interfaces.clone();
    //获取安全管理器
    final SecurityManager sm = System.getSecurityManager();
    //进行一些权限检验
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    //该方法先从缓存获取代理类, 如果没有再去生成一个代理类
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        //进行一些权限检验
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        //获取参数类型是InvocationHandler.class的代理类构造器
        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实例去构造一个代理类的实例
        //所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
        return cons.newInstance(new Object[]{h});
    } catch (Exception e) {
        //统一用Exception捕获了所有异常
        throw new InternalError(e.toString(), e);
    }
}

可以看到,newProxyInstance方法首先是对参数进行一些权限校验,之后通过调用getProxyClass0方法生成了代理类的类对象,然后获取参数类型是InvocationHandler.class的代理类构造器。检验构造器是否可以访问,最后传入InvocationHandler实例的引用去构造出一个代理类实例,InvocationHandler实例的引用其实是Proxy持有着,因为生成的代理类默认继承自Proxy,所以最后会调用Proxy的构造器将引用传入。在这里我们重点关注getProxyClass0这个方法,看看代理类的Class对象是怎样来的,下面贴上该方法的代码

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //目标类实现的接口不能大于65535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //获取代理类使用了缓存机制
    return proxyClassCache.get(loader, interfaces);
}

可以看到getProxyClass0方法内部没有多少内容,首先是检查目标代理类实现的接口不能大于65535这个数,之后是通过类加载器和接口集合去缓存里面获取,如果能找到代理类就直接返回,否则就会调用ProxyClassFactory这个工厂去生成一个代理类。

//代理类生成工厂
private static final class ProxyClassFactory 
                implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    //代理类名称前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    //用原子类来生成代理类的序号, 以此来确定唯一的代理类
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            //这里遍历interfaces数组进行验证, 主要做三件事情
            //1.intf是否可以由指定的类加载进行加载
            //2.intf是否是一个接口
            //3.intf在数组中是否有重复
        }
        //生成代理类的包名
        String proxyPkg = null;
        //生成代理类的访问标志, 默认是public final的
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        for (Class<?> intf : interfaces) {
            //获取接口的访问标志
            int flags = intf.getModifiers();
            //如果接口的访问标志不是public, 那么生成代理类的包名和接口包名相同
            if (!Modifier.isPublic(flags)) {
                //生成的代理类的访问标志设置为final
                accessFlags = Modifier.FINAL;
                //获取接口全限定名, 例如:java.util.Collection
                String name = intf.getName();
                int n = name.lastIndexOf(‘.‘);
                //剪裁后得到包名:java.util
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                //生成的代理类的包名和接口包名是一样的
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    //代理类如果实现不同包的接口, 并且接口都不是public的, 那么就会在这里报错
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
        //如果接口访问标志都是public的话, 那生成的代理类都放到默认的包下:com.sun.proxy
        if (proxyPkg == null) {
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        //生成代理类的序号
        long num = nextUniqueNumber.getAndIncrement();
        //生成代理类的全限定名, 包名+前缀+序号, 例如:com.sun.proxy.$Proxy0
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        //这里是核心, 用ProxyGenerator来生成字节码, 该类放在sun.misc包下
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
                                  interfaces, accessFlags);
        try {
            //根据二进制文件生成相应的Class实例
            return defineClass0(loader, proxyName, proxyClassFile, 
                              0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

该工厂的apply方法会被调用用来生成代理类的Class对象,由于代码的注释比较详细,我们只挑关键点进行阐述,其他的就不反复赘述了。

1. 在代码中可以看到JDK生成的代理类的类名是“$Proxy”+序号。

2. 如果接口是public的,代理类默认是public final的,并且生成的代理类默认放到com.sun.proxy这个包下。

3. 如果接口是非public的,那么代理类也是非public的,并且生成的代理类会放在对应接口所在的包下。

4. 如果接口是非public的,并且这些接口不在同一个包下,那么就会报错。

参考:

https://www.cnblogs.com/liuyun1995/p/8157098.html

 

 

 

《Java基础知识》动态代理(InvocationHandler)详解

标签:test   inter   indexof   https   目标   else   创建用户   多少   access   

原文地址:https://www.cnblogs.com/jssj/p/11771408.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!