jdk动态代理实现原理详解编程语言

动态代理的作用

   我们都知道,spring的面向切面编程默认由jdk动态代理和cglib动态代理实现,使用动态代理我们可以无侵入的实现切面编程,比如日志管理、权限管理、事务管理等。jdk动态代理是面向接口的,cglib是面向普通类。弄明白了这两种动态代理实现原理也就懂了spring的aop编程。


jdk动态代理说明

jdk动态代理是面向接口的,只能对接口生成代理类。我曾经以为这个接口必须有实现类,其实这是错误的。接口实现是否存在是根据业务需求而来的。

jdk动态代理实现方法

1、目标接口、目标接口实现类

2、自定义的InvocationHandler

public interface UserService { 
	 
	public void methodA(); 
	 
}

public class UserServiceImpl implements UserService { 
 
	public void methodA() { 
		System.out.println("UserServiceImpl methodA()..."); 
	} 
	 
}

public class MyInvocationHandler implements InvocationHandler { 
	 
	public MyInvocationHandler() { 
		super(); 
	} 
 
	public MyInvocationHandler(Object obj) { 
		super(); 
		this.obj = obj; 
	} 
 
	private Object obj; 
	 
	public Object invoke(Object proxy, Method method, Object[] args) 
			throws Throwable { 
		System.out.println("执行前...."); 
		Object value = method.invoke(obj, args); 
		System.out.println("执行后"); 
		return value; 
	} 
 

import java.lang.reflect.Proxy; 
 
import com.interceptor.MyInvocationHandler; 
import com.jun.UserService; 
import com.jun.impl.UserServiceImpl; 
 
public class TestJDKDynamicProxy { 
 
	public static void main(String[] args) { 
	 
		MyInvocationHandler h = new MyInvocationHandler(new UserServiceImpl()); 
		Class<?>[] interfaces ={UserService.class}; 
		UserService service = (UserService) Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), interfaces, h); 
		service.methodA(); 
                System.out.println(service.getClass()); 
       } 
}

输出结果

执行前.... 
UserServiceImpl methodA()... 
执行后 
class com.sun.proxy.$Proxy0 

jdk动态代理原理

切面编程的效果出来了,在执行方法前和执行方法后,执行了逻辑操作,这里,我们可以做事务开启、关闭,日志的记录等。但背后的原理是什么?

通过对目标接口生成代理类,代理类会继承Proxy并实现目标接口,代理类的所有方法都会调用定制的invocationhandler的invoke方法,在定制InvocationHandler中调用目标类方法, 从而实现切面编程。

我们注意到打印出来的UserService类是com.sun.Proxy.$Proxy0,而不是UserService。通过追踪Proxy.newProxyInstance方法,我们发现有个getProxyClass0方法

Class<?> cl = getProxyClass0(loader, interfaces);


这个方法生成了代理类信息,进入方法发现,是这一行代码生成字节码

  byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 
                    proxyName, interfaces);

由于jdk是不开源的,所以我们需要下载openjdk查看源代码

 /** 
     * Generate a proxy class given a name and a list of proxy interfaces. 
     */ 
    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; 
    }


我们发现saveGeneratedFiles控制是否把代理类保存到磁盘上

 private final static boolean saveGeneratedFiles = 
        java.security.AccessController.doPrivileged( 
            new GetBooleanAction( 
                "sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();

现在已经确定是ProxyGenerator.generateProxyClass生成代理类,既然已经找到”真凶”,我们就生成代理类看看吧。

import sun.misc.ProxyGenerator; 
 
public class TestJDKDynamicProxy { 
 
	public static void main(String[] args) { 
		 
		 
		System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");  
		Class<?>[] interfaces ={UserService.class}; 
		ProxyGenerator.generateProxyClass( 
                "C://Users//DELL//Desktop//$Proxy1", interfaces); 
        } 
}


注意,传入的属性值是字符串”true”,而不是布尔值true,传入布尔值true不会保存到磁盘


通过java反编译工具jad(java decomplier)反编译$Proxy1生成jad文件,打开后如下所示

jdk动态代理实现原理详解编程语言

import java.lang.reflect.*; 
 
public final class C_3A__5C_Users_5C_DELL_5C_Desktop_5C_$Proxy1 extends Proxy 
    implements UserService 
{ 
 
	static  
    { 
        try 
        { 
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { 
                Class.forName("java.lang.Object") 
            }); 
            m4 = Class.forName("com.jun.UserService").getMethod("methodB", new Class[0]); 
            m3 = Class.forName("com.jun.UserService").getMethod("methodA", new Class[0]); 
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); 
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); 
        } 
        catch(NoSuchMethodException nosuchmethodexception) 
        { 
            throw new NoSuchMethodError(nosuchmethodexception.getMessage()); 
        } 
        catch(ClassNotFoundException classnotfoundexception) 
        { 
            throw new NoClassDefFoundError(classnotfoundexception.getMessage()); 
        } 
    } 
	 
	 private static Method m3; 
	  
    public C_3A__5C_Users_5C_DELL_5C_Desktop_5C_$Proxy1(InvocationHandler invocationhandler) 
    { 
        super(invocationhandler); 
    } 
 
 
    public final void methodA() 
    { 
        try 
        { 
            super.h.invoke(this, m3, null); 
            return; 
        } 
        catch(Error _ex) { } 
        catch(Throwable throwable) 
        { 
            throw new UndeclaredThrowableException(throwable); 
        } 
    } 
}

我们可以看到代理类$Proxy1,实现了指定接口UserService并继承了Proxy,执行methodA时,会调用父类的invocationHandler,而这个InvocationHandler就是在创建代理类对象时,传入的MyInvocationHandler

 Class<?> cl = getProxyClass(loader, interfaces); 
 
        /* 
         * Invoke its constructor with the designated invocation handler. 
         */ 
        try { 
            Constructor cons = cl.getConstructor(constructorParams); 
            return cons.newInstance(new Object[] { <strong><span style="font-size:18px;color:#ff0000;">h</span></strong> });

我们再回过头来回顾一下事情的经过,首先执行代理的methodA方法,然后调用MyInvocationHandler.invoke方法,首先进行方法前逻辑处理,然后执行UserServiceImpl的methodA方法,然后执行方法后逻辑处理。

注意:

1、目标接口不是必须有实现类的,比如Mybatis的mapper,mapper都是接口,但是没有实现类。Mybatis的InvocationHandler就是MapperProxy,在invoke方法中方法里执行sql。



原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/20276.html

(0)
上一篇 2021年7月19日 23:18
下一篇 2021年7月19日 23:18

相关推荐

发表回复

登录后才能评论