首页 > 其他分享 >jdk动态代理实现原理

jdk动态代理实现原理

时间:2023-06-28 14:34:52浏览次数:37  
标签:jdk Object 代理 public UserService new 动态 Class


动态代理的作用

   我们都知道,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。






标签:jdk,Object,代理,public,UserService,new,动态,Class
From: https://blog.51cto.com/u_15561616/6571877

相关文章

  • ​​动态规划算法
    以下是一个用C++实现的动态规划算法来解决最长子序列问题(LongestCommonSubsequence)的示例代码:#include<iostream>#include<vector>#include<algorithm>usingnamespacestd;intlongestCommonSubsequence(stringtext1,stringtext2){intm=text1.length();......
  • Linux使用HTTP隧道代理模板
    以下是一个使用HTTP隧道代理的Linux模板:1.首先,确保你已经安装了curl和socat工具。如果没有安装,可以使用以下命令进行安装:```sudoapt-getinstallcurlsocat```2.创建一个名为proxy.sh的脚本文件,并将以下内容复制到文件中:```bash#!/bin/bash#设置代理服务器的地址和端口PROXY_H......
  • Java学习——jdk的卸载和安装
    一、卸载jdk删除java的安装目录删除环境变量里面的JAVA_HOME删除path目录下的关于java的目录dos输入java-version检验卸载结果二、安装jdk百度搜索jdk8找到下载地址(https://www.oracle.com/cn/java/technologies/downloads/archive/)选择对应版本同意协......
  • vue组件动态缓存与动态刷新
    动态缓存前言在项目中,为了减少性能消耗,有时我们需要使用keep-alive把组件缓存起来,但是并不是所有组件都需要被缓存,那么如何控制那些组件需要缓存呢?主要使用到路由meta,路由前置守卫,vux,动态组件。实现APP.vue<scriptsetup>import{ref,computed}from'vue'import{useRo......
  • 高德地图动态Marker和自定义弹框、交互事件、中心点跳转
    高德地图vue3使用下载NPM:npmi@amap/amap-jsapi-loader--save根据官网提示,VUE3需要改变监听模式下载npmi@vue/reactivity组件内配置初始化<scriptsetup>//开发技术vue3piniatsimport{ref}from"vue";importAMapLoaderfrom"@amap/amap-jsapi-loa......
  • ubuntu设置终端代理
    修改bash的配置文件~/.bashrc增加两个函数#setproxyfunctionsetproxy(){ exporthttp_proxy=socks5://192.168.4.61:7890 exportHTTPS_PROXY=socks5://192.168.4.61:7890 exportFTP_PROXY=socks5://192.168.4.61:7890}#unsetproxyfunctionunsetproxy(){ unset......
  • 静态代理和动态代理
    3.静态代理和动态代理的区别按照代理的创建时期,代理类可以分为两种:静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。动态代理:动态代理是指在java程序运行过程(程序已经启动正在运行中)由jvm生成代理类的class信息,该cla......
  • jdk代理 cglib代理 asm javassist
    jdk代理实现代码:publicclassJdkProxyCimplementsInvocationHandler{privateObjecttarget;publicJdkProxyC(Objecttarget){this.target=target;}publicObjectgetProxy()throwsClassNotFoundException,InvocationTargetExceptio......
  • JAVA的动态性之脚本语言支持API
    JAVA语言是一种静态类型的编程语言。静态类型的含义是指在编译的时候进行类型检查。JAVA源代码中的每个每个变量的类型都需要显式地进行声明。所有的变量、方法的参数和返回值的类型在程序运行之前就必须是已知的。JAVA语言的这种静态类型特性使编译器可以在编译的时候执行大量的检......
  • JDK/bin目录下的不同exe文件的用途(appletviewer、HtmlConverter、jar、java、javac、
    目录---------------------------------------1.javacexe2.appletviewerexe3.jarexe4.javadocexe5.javahexe6.HtmlConverterexe7.orbdexe8.policytoolexe9.rmicexe10.rmidexe11.rmiregistryexe12.serialverexe13.servertoolexe14.rmic15.rmid16.rmiregistry17.serialver18.jarsi......