Dynamic Proxy

用过Spring的人都能感受到AOP带来的好处。什么是AOP(Aspect Oriented Programming)呢?所谓AOP就是面向切面编程。将业务过程中具有共性的功能提取出来,统一进行处理,这样在简化业务代码的同时,方便管理。一旦需求改变,只需要改变提取出的切面即可,无需到处修改业务代码中的功能。

而java中的动态代理就可以实现简单的AOP。下面的例子中通过动态代理为方法添加日志,用于记录进入方法和退出方法。而这个功能在Spring的AOP中是一个十分常见的应用例子。

首先定义接口HelloWorld,

public interface HelloWorld {
	public void sayHello();
	public void sayHello1();
}

然后编写业务代码也就是上面接口的实现类,

public class HelloWorldImpl implements HelloWorld{

	@Override
	public void sayHello() {
		// TODO Auto-generated method stub
		System.out.println("helloworld");
	}

	@Override
	public void sayHello1() {
		// TODO Auto-generated method stub
		System.out.println("helloworld1");

	}
}

实现代理类,这里需要仔细区分 代理对象,被代理对象,被代理之后的对象。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class MyProxy implements InvocationHandler{

	private Object delegate;
	// 设置被代理的对象
	public void setDelegate(Object obj){
		this.delegate = obj;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("Enter function : " + method.getName());
		method.invoke(delegate, args);
		System.out.println("Leave function : " + method.getName());
		return null;
	}

	public static void main(String[] args) {
		// 生成被代理对象
		HelloWorld helloworld = new HelloWorldImpl();
		
		// 生成代理对象
		MyProxy proxy = new MyProxy();
		proxy.setDelegate(helloworld);
		
		// 生成被代理后的对象
		HelloWorld helloworldProxy = (HelloWorld) Proxy.newProxyInstance(helloworld.getClass().getClassLoader(), helloworld.getClass().getInterfaces(), proxy);
		helloworldProxy.sayHello();
		helloworldProxy.sayHello1();
		
		// 查看新生成的类的名称
		System.out.println(helloworldProxy.getClass().getName());
	
		// 查看新生成的类实现的接口
		Class[] interfaces = helloworldProxy.getClass().getInterfaces();
		for (Class clazz : interfaces){
			System.out.println(clazz.getName());
		}
	}
}


我们可以看到上面的类MyProxy实现了接口InvocationHandler,这个接口中有invoke方法。通过Proxy的静态方法newProxyInstance生成被代理之后的对象,这里传入的参数分别是被代理对象的类加载器,被代理对象的接口,以及代理对象。在上面的例子中这个代理对象就是MyProxy的实例proxy。在newProxyInstance方法中会生成一个新的类,并且这个新类的类加载器就是传入的类加载器。并且这个新类实现了传入的接口,因此在上面的例子中代理之后的对象可以强制转换成HelloWorld类型。

运行上面的代码后,看到如下输出

Enter function : sayHello
helloworld
Leave function : sayHello
Enter function : sayHello1
helloworld1
Leave function : sayHello1
$Proxy0
HelloWorld

可以看到通过代理类,被代理类的方法被调用的时候都会打印出相应的信息。这是为什么呢?因为生成的代理之后的对象在调用相应的方法时,会调用代理对象的invoke方法,而在我们的实现中invoke方法主要干了以下几件事情

1)首先会打印出“Ener function : xxx”字样

2)然后通过反射机制调用被代理对象相应的方法,这个方法外层的方法具有相同的名字

3)之后会打印出“Leave function: xxx”字样

而之后打印出的$Proxy0就是新生成的类的名字,HelloWorld就是$Proxy0实现了的接口。


需要注意的是,要将被代理对象传入到代理对象中,然后在代理的invoke方法中调用被代理对象的方法。千万不要再invoke方法中使用如下语句,

method.invoke(proxy, args)。

假设调用了helloworldProxy.sayHello(),然后会进入到methond.invoke(proxy, args),由于这里传入的proxy就是helloworldProxy,所以会再次进入helloworldProxy.sayHello()方法,这就形成了死递归。


为了验证上面的猜想,可以看一下newProxyInstance方法,新生成的类主要通过如下代码生成,

                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
                try {
                    proxyClass = defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    /*
                     * A ClassFormatError here means that (barring bugs in the
                     * proxy class generation code) there was some other
                     * invalid aspect of the arguments supplied to the proxy
                     * class creation (such as virtual machine limitations
                     * exceeded).
                     */
                    throw new IllegalArgumentException(e.toString());
                }

ProxyGenerator位于sun.misc包中,可以看一下openjdk中是如何实现的。

http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/f097ca2434b1/src/share/classes/sun/misc/ProxyGenerator.java

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/ProxyGenerator.java

generateProxyClass方法如下,

    public static byte[] generateProxyClass(final String name,
                                            Class[] interfaces)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces);
        final byte[] classFile = gen.generateClassFile();

        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        FileOutputStream file =
                            new FileOutputStream(dotToSlash(name) + ".class");
                        file.write(classFile);
                        file.close();
                        return null;
                    } catch (IOException e) {
                        throw new InternalError(
                            "I/O exception saving generated file: " + e);
                    }
                }
            });
        }

        return classFile;
    }

字节码的生成主要通过gen的generateClassFile方法生成。

    private byte[] generateClassFile() {

        /* ============================================================
         * Step 1: Assemble ProxyMethod objects for all methods to
         * generate proxy dispatching code for.
         */

        /*
         * Record that proxy methods are needed for the hashCode, equals,
         * and toString methods of java.lang.Object.  This is done before
         * the methods from the proxy interfaces so that the methods from
         * java.lang.Object take precedence over duplicate methods in the
         * proxy interfaces.
         */
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);

        /*
         * Now record all of the methods from the proxy interfaces, giving
         * earlier interfaces precedence over later ones with duplicate
         * methods.
         */
        for (int i = 0; i < interfaces.length; i++) {
            Method[] methods = interfaces[i].getMethods();
            for (int j = 0; j < methods.length; j++) {
                addProxyMethod(methods[j], interfaces[i]);
            }
        }

        /*
         * For each set of proxy methods with the same signature,
         * verify that the methods' return types are compatible.
         */
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }

        /* ============================================================
         * Step 2: Assemble FieldInfo and MethodInfo structs for all of
         * fields and methods in the class we are generating.
         */
        try {
            methods.add(generateConstructor());

            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {

                    // add static field for method's Method object
                    fields.add(new FieldInfo(pm.methodFieldName,
                        "Ljava/lang/reflect/Method;",
                         ACC_PRIVATE | ACC_STATIC));

                    // generate code for proxy method and add it
                    methods.add(pm.generateMethod());
                }
            }

            methods.add(generateStaticInitializer());

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception");
        }

        if (methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }

        /* ============================================================
         * Step 3: Write the final class file.
         */

        /*
         * Make sure that constant pool indexes are reserved for the
         * following items before starting to write the final class file.
         */
        cp.getClass(dotToSlash(className));
        cp.getClass(superclassName);
        for (int i = 0; i < interfaces.length; i++) {
            cp.getClass(dotToSlash(interfaces[i].getName()));
        }

        /*
         * Disallow new constant pool additions beyond this point, since
         * we are about to write the final constant pool table.
         */
        cp.setReadOnly();

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        try {
            /*
             * Write all the items of the "ClassFile" structure.
             * See JVMS section 4.1.
             */
                                        // u4 magic;
            dout.writeInt(0xCAFEBABE);
                                        // u2 minor_version;
            dout.writeShort(CLASSFILE_MINOR_VERSION);
                                        // u2 major_version;
            dout.writeShort(CLASSFILE_MAJOR_VERSION);

            cp.write(dout);             // (write constant pool)

                                        // u2 access_flags;
            dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
                                        // u2 this_class;
            dout.writeShort(cp.getClass(dotToSlash(className)));
                                        // u2 super_class;
            dout.writeShort(cp.getClass(superclassName));

                                        // u2 interfaces_count;
            dout.writeShort(interfaces.length);
                                        // u2 interfaces[interfaces_count];
            for (int i = 0; i < interfaces.length; i++) {
                dout.writeShort(cp.getClass(
                    dotToSlash(interfaces[i].getName())));
            }

                                        // u2 fields_count;
            dout.writeShort(fields.size());
                                        // field_info fields[fields_count];
            for (FieldInfo f : fields) {
                f.write(dout);
            }

                                        // u2 methods_count;
            dout.writeShort(methods.size());
                                        // method_info methods[methods_count];
            for (MethodInfo m : methods) {
                m.write(dout);
            }

                                         // u2 attributes_count;
            dout.writeShort(0); // (no ClassFile attributes for proxy classes)

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception");
        }

        return bout.toByteArray();
    }
代理方法的产生主要通过如下的代码,

        for (int i = 0; i < interfaces.length; i++) {
            Method[] methods = interfaces[i].getMethods();
            for (int j = 0; j < methods.length; j++) {
                addProxyMethod(methods[j], interfaces[i]);
            }
        }

继续看addProxyMethod是如何工作的,

 private void addProxyMethod(Method m, Class fromClass) {
        String name = m.getName();
        Class[] parameterTypes = m.getParameterTypes();
        Class returnType = m.getReturnType();
        Class[] exceptionTypes = m.getExceptionTypes();

        String sig = name + getParameterDescriptors(parameterTypes);
        List<ProxyMethod> sigmethods = proxyMethods.get(sig);
        if (sigmethods != null) {
            for (ProxyMethod pm : sigmethods) {
                if (returnType == pm.returnType) {
                    /*
                     * Found a match: reduce exception types to the
                     * greatest set of exceptions that can thrown
                     * compatibly with the throws clauses of both
                     * overridden methods.
                     */
                    List<Class<?>> legalExceptions = new ArrayList<Class<?>>();
                    collectCompatibleTypes(
                        exceptionTypes, pm.exceptionTypes, legalExceptions);
                    collectCompatibleTypes(
                        pm.exceptionTypes, exceptionTypes, legalExceptions);
                    pm.exceptionTypes = new Class[legalExceptions.size()];
                    pm.exceptionTypes =
                        legalExceptions.toArray(pm.exceptionTypes);
                    return;
                }
            }
        } else {
            sigmethods = new ArrayList<ProxyMethod>(3);
            proxyMethods.put(sig, sigmethods);
        }
        sigmethods.add(new ProxyMethod(name, parameterTypes, returnType,
                                       exceptionTypes, fromClass));
    }

继续进入到ProxyMethod,然后可以在generateMethod中看到
 out.writeByte(opc_invokeinterface);
            out.writeShort(cp.getInterfaceMethodRef(
                "java/lang/reflect/InvocationHandler",
                "invoke",
                "(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
                    "[Ljava/lang/Object;)Ljava/lang/Object;"));
            out.writeByte(4);
            out.writeByte(0);

写入了字节码opc_invokeinterface,查看这个常量值,

private static final int opc_invokeinterface        = 185;

在虚拟机规范中查看这个字节码,




总和上面可以看出代码主要负责调用invoke方法,而这个invoke方法属于某个invokeHandler接口的实例。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值