手写一个面向接口的动态代理

如题,手写一个面向接口的动态代理。我们需要先了解jdk中的动态代理是怎么实现的。

理解生成的代码和调用过程

设置vm参数,-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,可以使jdk动态生成的class文件输出到磁盘中。

设置vm options

使用下面代码进行调试

public interface IService {
    public void service(String name )throws Exception;
}

public class ServiceImplA implements IService {
    @Override
    public void service(String name) throws Exception {
        System.out.println("ServiceImplA name" + name);
    }
}

public class DynaProxyServiceA implements InvocationHandler {
    private Object object;
    /**
     *   将目标对象关联到InvocationHandler接口,返回代理对象obj
     *   调用代理对象的方法时,都会自动调用invoke方法
     */

    public Object bind(Object object){
        this.object = object;
        return Proxy.newProxyInstance(
                this.object.getClass().getClassLoader(),
                this.object.getClass().getInterfaces(),
                this);
    }

    @SuppressWarnings("unchecked")
    public <T> T bindInterface(Class<T> proxyInterface){
        object = proxyInterface;
        return (T)Proxy.newProxyInstance(
                proxyInterface.getClassLoader(),
                new Class[]{proxyInterface},
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        System.out.println("log start");
        if(object instanceof Class<?>){
            Class<?> clazz = (Class<?>) object;
            for (Method clazzMethod : clazz.getDeclaredMethods()) {
                System.out.println(clazzMethod.getName());
            }
        }
        try{
            result = method.invoke(this.object,args);
            Class<?> returnType = method.getReturnType();
            System.out.println(returnType);
        }catch (Exception e){
            throw e;
        }
        System.out.println("log end");
        return result;
    }
    public static void main(String [] args) throws Exception {
        IService service = (IService)new DynaProxyServiceA()
                        .bind(new ServiceImplA());
        service.service("zhjl");
    }
}

输出结果
log start
ServiceImplA namezhjl
void
log end

运行完程序后,会在项目的根目录生成一个文件夹com.sun.proxy,里面会生成一个$Proxy0.class的代理类文件。

打开文件可以看到以下生成的源代码。

public final class $Proxy0 extends Proxy implements IService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } 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 service(String var1) throws Exception {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (Exception | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } 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("org.example.aop.IService").getMethod("service", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

很容易可以发现,生成的源代码是有规律可寻的。

  • 固定会重写java.lang.Object中的equalstoStringhashCode三个方法
  • 对比静态变量Method的声明对应方法的位置与static代码块这三块地方,可以发现,声明顺序与静态代码块中反射获取的顺序一致,并且在各方法中执行反射出来的Method也是相等的。这里做到了提前加载被代理类的方法,然后使用到该代理类的方法时将被代理类的方法当参数传到h.invoke中执行。

进入到被继承的Proxy中,发现在生成的类中使用的h就是InvocationHandler这个类。即我们在使用动态代理时所要实现才那个类!

image
image.png

所以这个地方是生成一个回调代理类的invoke方法的类,来调用invoke时决定什么时候执行被代理类的service方法就能达到切面增强这个方法的效果

执行代理类的流程图

理解完了生成代码的意义和处理流程,剩下的就是怎么构造这些代码并将他编译成.class文件和被类加载器加载并被创建实例被我们所使用了。

如何构造和加载

参考Proxy.newProxyInstance的代码,看到getProxyClass0,前面的代码忽略,看注释就知道这里是生成指定代理类的方法。直接点进去就好了。

生成代码的方法
image
image
image

记住这两个变量分别是KeyFactoryProxyClassFactory。然后回到proxyClassCache.get(loader, interfaces)

image
image

根据实现的接口数量来返回Key

往下走,最后指向的类都是Factory,并在最后执行get方法。

image

最后回到ProxyClassFactory这个类的apply方法。

image

最后在方法的底部发现ProxyGenerator.generateProxyClass对应的作用就是构造相当于构造.java源文件。
defineClass0相当于javac编译成.class文件并loadClass返回对应的类

image

这个生成方法较复杂,经过简单的查看源码,已经知道步骤如下:

  • 构造.java源文件
  • 编译成.class后加载类

笔者的方法比较简单,直接使用freemaker来构造源文件,需要传入以下四个参数。

  • package->生成类所在的包
  • className->生成的代理类名称
  • interface->实现的接口类全类名
  • methodList->需要重写的方法列表

freemaker模板如下

package ${package};
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
* @Author: Jdragon
* @email: 1061917196@qq.com
* @Description: jdk动态代理实质
*/
public class ${className} extends Proxy implements ${interface} {
    public ${className}(InvocationHandler h) {
        super(h);
    }
<#list 0..(methodList!?size-1) as i>
    @Override
    public final ${methodList[i].retType} ${methodList[i].methodName}(
        <#list methodList[i].paramList as param>
                ${param} var${param_index}<#if param_has_next>,</#if>
        </#list>) {
        try {
            <#if (methodList[i].retType!="void")>return (${methodList[i].retType})</#if>
            <#if (methodList[i].paramList?size==0)>
            super.h.invoke(this, m${i}, (Object[])null);
            <#else>
            super.h.invoke(this, m${i}, new Object[]{
                <#list 0..(methodList[i].paramList!?size-1) as k>var${k}
                    <#if k_has_next>,</#if>
                </#list>});
            </#if>
        } catch (RuntimeException | Error e) {
            throw e;
        }catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
</#list>
<#list 0..(methodList!?size-1) as i>
    private static Method m${i};
</#list>
    static{
        try{
            <#list 0..(methodList!?size-1) as i>
                m${i} = Class.forName("${methodList[i].className}").getMethod("${methodList[i].methodName}"
                <#list methodList[i].paramList as param>
                    ,Class.forName("${param}")
                </#list>);
            </#list>
        }  catch (NoSuchMethodException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

主要代码。以下代码不完整,可到gitee获取源码

public class JdkProxyFactory {

    private final static String LN = System.lineSeparator();

    private final static AtomicInteger PROXY_INDEX = new AtomicInteger(0);

    private final static Boolean SAVE_GENERATED_FILES = Boolean.valueOf(System.getProperty("sun.misc.ProxyGenerator.saveGeneratedFiles"));

    private final static String USER_DIR = System.getProperty("user.dir") + "/com/jdragon/proxy/";

    private final static String PACKAGE_NAME = "com.jdragon.proxy";

    public static Object newProxyInstance(ClassLoader classLoader,
                                          @NotNull Class<?>[] interfaces,
                                          @NotNull InvocationHandler h) {
        try {
            if (interfaces.length == 0) {
                throw new Exception("至少要实现一个接口");
            }
            //使用被代理类的类名和自增数定义代理类的名字
            String proxyClass = interfaces[0].getSimpleName() + "$Proxy" + PROXY_INDEX.incrementAndGet();
            //加载代理类
            Class<?> loadClass = loadClass(interfaces[0], proxyClass);
            Constructor<?> constructor = loadClass.getDeclaredConstructor(InvocationHandler.class);
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * @Description: 加载类
    **/
    private static Class<?> loadClass(Class<?> interfaces, String proxyClassName) throws Exception {
        String classPath = PACKAGE_NAME + "." + proxyClassName;
        //构建源代码
        String sourceCode = generateSourceCode(interfaces, proxyClassName);
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null))) {
            List<JavaFileObject> files = Collections.singletonList(new MemoryJavaFileObject(proxyClassName, sourceCode));
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, files);
            if (!task.call()) {
                throw new Exception("任务调用异常");
            }
            ClassLoader classLoader = manager.getClassLoader(null);
            Class<?> aClass = manager.getClassLoader(null).loadClass(classPath);
            if (SAVE_GENERATED_FILES) {
                save(proxyClassName, classLoader);
            }
            return aClass;
        }
    }

    /**
     * @Description: 构造源代码
    **/
    private static String generateSourceCode(Class<?> interfaces, String proxyClassName) {
        String interfaceName = interfaces.getName();
        List<MethodEntity> methodEntities = new ArrayList<>();
        methodEntities.add(new MethodEntity(Object.class, "toString", null,
                String.class));
        methodEntities.add(new MethodEntity(Object.class, "hashCode", null,
                int.class));
        methodEntities.add(new MethodEntity(Object.class, "equals", Collections.singletonList(Object.class.getName()),
                boolean.class));

        for (Method declaredMethod : interfaces.getDeclaredMethods()) {
            MethodEntity methodEntity = new MethodEntity();
            methodEntity.setClassName(interfaces);
            methodEntity.setMethodName(declaredMethod.getName());
            List<String> params = new ArrayList<>();
            for (Parameter parameter : declaredMethod.getParameters()) {
                String paramTypeName = parameter.getType().getName();
                params.add(paramTypeName);
            }
            methodEntity.setParamList(params);
            methodEntity.setRetType(declaredMethod.getReturnType());
            methodEntity.setTransferType(declaredMethod.getReturnType());
            methodEntities.add(methodEntity);
        }

        //利用定义好的模板传入参数到freemaker进行遍历填充,最后获得源代码
        Map<String, Object> map = new HashMap<>(8);
        map.put("package", PACKAGE_NAME);
        map.put("className", proxyClassName);
        map.put("interface", interfaceName);
        map.put("methodList", methodEntities);
        FreeMakerUtil freeMakerUtil = new FreeMakerUtil("/template/freemaker/", "ftl");
        return freeMakerUtil.printString("proxy", map);
    }
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容