《Java基礎知識》Java動態代理(InvocationHandler)詳解


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

運行結果:

模擬動態代理自己實現一個動態代理的功能:https://www.cnblogs.com/jssj/p/12499086.html。這篇文章應該可以更好的讓人理解動態代理。

代理代理核心代碼

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM