java 동적 프록시 원리 원본 해석 (jdk8)

14147 단어 jdk
본고는 동적 에이전트 문장을 토대로 jdk8의 동적 에이전트 원본을 분석한 것이다. jdk8에서 동적 에이전트의 실현 방식은 변하지 않았고 원본의 위치가 다르기 때문에 여기서 분석하고자 한다.
먼저 Dynamic Agent의 예제 코드를 사용한 다음 소스 분석을 수행합니다.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler {

    //     
    private Object target;

    /**
     *     
     * @param target     
     */
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }


    /**
     *          
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //                    
        System.out.println("------------------before------------------");

        //          
        Object result = method.invoke(target, args);

        //                    
        System.out.println("-------------------after------------------");

        return result;
    }

    /**
     *            
     * @return     
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}
public interface UserService {

    /**
     *     
     */
    public abstract void add();

}
public class UserServiceImpl implements UserService {

    /* (non-Javadoc)
     * @see dynamic.proxy.UserService#add()
     */
    public void add() {
        System.out.println("--------------------add---------------");
    }
}
public class ProxyTest {

    public static void main(String[] args) {
        //        
        UserService userService = new UserServiceImpl();

        //    InvocationHandler
        MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);

        //             
        UserService proxy = (UserService) invocationHandler.getProxy();

        //          
        proxy.add();

    }
}
getProxy()              ,               
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() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});

먼저 인터페이스의 복제 클래스를 만들고 getProxyClass0 방법을 통해 동적 클래스를 생성합니다. getProxyClass0 원본 코드를 보십시오.
    private static Class> getProxyClass0(ClassLoader loader,
                                           Class>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

우리는 아래에 빨간색으로 표시된 proxyClassCache를 중점적으로 보아야 한다.get(loader,interfaces) 방법,java는 이 방법을 통해 생성된 동적 프록시 클래스입니다.proxyClassCache의 설명은 다음과 같습니다.
private static final WeakCache[], Class>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
   ProxyClassFactory            。
    public V get(K key, P parameter) {
                。。。
                V value = supplier.get();
                。。。
}

이 방법 중의 supplier.get () 방법은 프록시 클래스를 생성하는 것입니다. get () 방법을 살펴보겠습니다.
public synchronized V get() { // serialize access
            // re-check
               。。。
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
               。。。
}

이 동기화 방법은 프록시 클래스를 생성하는 것이다. 그 중에서valueFactory는 우리가 앞서 언급한 프록시 클래스를 생성할 수 있는 공장 클래스,ProxyClassFactory(), 우리는 apply에 들어가는 방법이다.
        public Class> apply(ClassLoader loader, Class>[] interfaces) {

            Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);

앞에는 모두 검증된 것들입니다. 우리는 상관하지 않습니다. 주로 뒤에 있는 몇 줄을 보십시오. 먼저 프록시 클래스의 이름인proxyName을 정의한 다음에 PrxoyGenerator를 통과합니다.generateProxyClass는 바이트 파일을 생성한다(바이트 파일을 생성하는 것은 비교적 복잡하다. 우리는 여기서 많이 소개하지 않았다) 그리고 defineClass0 방법을 통해 이 클래스를 불러온다. 사용하는 클래스 마운트는 우리가 이전에 전송한 클래스이다. 이로써 프록시 클래스는 생성되었지만 생성된 후에 어떻게 실례화되었는지 계속 분석한다.
이전에 우리가 막 들어간 첫 번째 방법은 위로 검색할 수 있습니다: new Proxy Instance에서 실례화되었습니다. 다음을 보십시오.
final Constructor> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
private static final Class>[] constructorParams =
    { InvocationHandler.class };

이 두 가지 방법은 먼저 파라미터가 있는 InvocationHandler 클래스의 구조기를 얻은 다음에 구조기를 통해 안을 들여다보고 마지막으로NativeConstructorAccessorImpl을 호출하는 로컬 방법으로 이 클래스를 실례화했다
프록시 클래스의 생성 소스 코드에 대해 설명했습니다. 프록시 클래스의 코드가 어떤 것인지 꼭 보고 싶습니다. 다음은 프록시 클래스를 생성하는 방법을 제공해 드리겠습니다.
/**
 *          
 * @author zyb
 * @since 2012-8-9 
 */
public class ProxyGeneratorUtils {

    /**
     *               
     * @param path      
     */
    public static void writeProxyClassToHardDisk(String path) {
        //      ,         ProxyGenerator        
        // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);  

        //        

        //            
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());

        FileOutputStream out = null;

        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}  
public static void main(String[] args) {

        ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class");

지정한 C디스크 x 폴더에 프록시 클래스가 생성됩니다.class 파일, 역컴파일된 결과를 봅시다.
public final class $Proxy11 extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy11(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void add() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("UserService").getMethod("add");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

equals,hashcode,toString은 모두 Object의 것입니다. 구조 방법을 먼저 보고 앞에서 설명한 바와 같이 구조기를 통해 실례화하여 전송된 invocationHandler는 우리가 이전에 정의한 MyInvocationHandler이기 때문에dd() 방법은 MyInvocationHandler의 invoke() 방법을 집행합니다.

좋은 웹페이지 즐겨찾기