jdk动态代理源码(一)

本篇先通过带大家手动写一个简单版的仿JDK动态代理的实现,帮助大家理解jdk动态代理的原理
代理模式简单使用请参考本人的另一篇博客:代理模式简介及使用
静态代理
举个例子,我们需要实现一个数据库操作接口做数据的新增和修改,在执行新增和修改后打印方法执行的时间。

//数据库连接接口,定义新增和修改方法
public interface Connection {
    void add(Object object);
    void update(Object object);
}

下面模拟jdbc操作数据库的实现类JdbcTemplate,此处只简单打印日志

public class JdbcTemplate implements Connection {
    @Override
    public void add(Object object) {
        System.out.println("add");
    }
    
    @Override
    public void update(Object object) {
        System.out.println("update");
    }
}

静态代理想实现打印方法执行时间的需求,实现代码如下:

public class MyJdbcTemplate implements Connection {
    private JdbcTemplate jdbcTemplate;

    public MyJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void add(Object object) {
        long start = System.currentTimeMillis();
        this.jdbcTemplate.add(object);
        long end = System.currentTimeMillis();
        System.out.println(" addTime =" + (end - start));
    }

    @Override
    public void update(Object object) {
        long start = System.currentTimeMillis();
        this.jdbcTemplate.update(object);
        long end = System.currentTimeMillis();
        System.out.println(" updateTime =" + (end - start));
    }
}

上面的静态代理方式的弊端:如果这个类有100个方法需要计算执行时间,或者项目有100个类需要计算执行时间,那你需要写n多个类,和n多重复代码,会造成类爆发式增长、代码冗余重复等问题效率低下。(= =还有就是太low)

**

  • 我们分析一下我们要做的事然后手写一个简单版的JDK动态代理:

**
jdk动态代理原理就是不用我们自己写增强的实现类(这里指MyJdbcTemplate),用代码自己生成这个MyJdbcTemplate.java文件,然后使用工具编译成class文件,使用类加载器加载到jvm,使用反射得到代理类的Class对象,通过Class对象获取代理类的Method对象,invok执行代理类的增强方法(不熟悉反射知识的小伙伴请自行学习)。

因为代理模式就是要在方法执行前后添加逻辑,增强被调用的方法,因此下面这段逻辑可以单独抽离出来一个接口,用来定义用户自定义的增强行为。同时要满足开闭原则,需要自定义或修改的代码都抽取出来

 			long start = System.currentTimeMillis();
            add.invoke(object, param);
            long end = System.currentTimeMillis();
            System.out.println(" addTime =" + (end - start));

在这里插入图片描述

因此我们定义一个接口用来描述上面刚说的这种行为(参数就是Method对象和方法入参):

import java.lang.reflect.Method;

public interface InvocationHandler {
    Object invoke(Method method, Object... args);
}

用户只需要定义一个类实现这个接口就可以在实现类的invoke方法写代理增强的逻辑了,被增强的对象也抽取invocationHandler的实现类中。修改后的 代理类如下MyJdbcTemplate

public class MyJdbcTemplate implements Connection {
    private InvocationHandler invocationHandler ;

    public MyJdbcTemplate(InvocationHandler invocationHandler ) {
        this.invocationHandler = invocationHandler ;
    }

    @Override
    public void add(Object param) {
        try {
            Method add = Connection.class.getMethod("add");
            //重点体会
           //long start = System.currentTimeMillis();
            //add.invoke(object, param);
            //long end = System.currentTimeMillis();
            //System.out.println(" Time =" + (end - start));
            
            invocationHandler.invoke(add,param);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
   
    @Override
    public void update(Object param) {
        try {
            Method update= Connection.class.getMethod("update");
            invocationHandler.invoke(update,param);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ok,接口的抽取完毕,接下来的事情就是怎样用代码生成这个java文件并编译加载到内存,反射获取到Class对象并获取到一个它的实例了。
根据我们之前的分析定义一个Proxy代理类,初步定义他应该有以下这个方法,入参接口Class对象和InvocationHandler 实现类,出参:反射得到的代理类的实例

public class Proxy {
    public static Object newProxyInstance(Class interfaceClazz, InvocationHandler invocation) throws Exception {
        ......
    }
}

下面的代码小伙伴们不必深究,看懂原理就行:用代码生成上面的MyJdbcTemplate 类,调用java自带的编译器生成class文件,然后使用类加载器加载到jvm,通过Class对象反射得到实例。方法入参需要InvocationHandler和接口,返回Object,方法如下
注:生成java源码可以使用

		<!-- 生成java源码 -->
		<dependency>
			<groupId>com.squareup</groupId>
			<artifactId>javapoet</artifactId>
			<version>1.9.0</version>
		</dependency>

其它的自带编译器javax.tools.JavaCompiler,URLClassLoader类加载器等知识可以自行百度了解。

import com.example.demo.proxy.JavaCompiler;
import com.squareup.javapoet.*;

import javax.lang.model.element.Modifier;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class Proxy {
    public static Object newProxyInstance(Class interfaceClazz, InvocationHandler invocation) throws Exception {
        TypeSpec.Builder typeSpec = TypeSpec.classBuilder("MyJdbcTemplate")
                .addSuperinterface(interfaceClazz).addModifiers(Modifier.PUBLIC);
        //添加属性
        FieldSpec jdbcTemplate = FieldSpec.builder(InvocationHandler.class, "invocationHandler", Modifier.PRIVATE).build();
        typeSpec.addField(jdbcTemplate);
        //构造方法
        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(InvocationHandler.class, "invocationHandler")
                .addStatement("this.invocationHandler = invocationHandler")
                .build();
        typeSpec.addMethod(constructorMethodSpec);
        //增强接口的所有方法(添加执行时间)
        Method[] methods = interfaceClazz.getDeclaredMethods();
        for (Method method : methods) {
            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(method.getReturnType())
                    .addParameter(method.getParameterTypes()[0],"param")
                    .addCode("try {\n")
                    .addStatement("\t$T method = " + interfaceClazz.getName() + ".class.getMethod(\"" + method.getName() + "\",Object.class)", Method.class)
                    // 为了简单起见,这里参数直接写死为空
                    .addStatement("\tthis.invocationHandler.invoke(method, param)")
                    .addCode("} catch(Exception e) {\n")
                    .addCode("\te.printStackTrace();\n")
                    .addCode("}\n")
                    .build();
            typeSpec.addMethod(methodSpec);
        }
        // 生成源码文件
        JavaFile javaFile = JavaFile.builder("com.example.demo.proxy.instance", typeSpec.build()).build();
        javaFile.writeTo(new File("D:\\myworkspase\\test\\src\\main\\java"));
        // 编译
        JavaCompiler.compile(new File("D:\\myworkspase\\test\\src\\main\\java\\com\\example\\demo\\proxy\\instance\\MyJdbcTemplate.java"));
        URL[] urls = new URL[]{new URL("file:/D:\\myworkspase\\test\\src\\main\\java\\com\\example\\demo\\proxy\\instance\\MyJdbcTemplate.java")};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class clazz = classLoader.loadClass("com.example.demo.proxy.instance.MyJdbcTemplate");
        Constructor constructor = clazz.getConstructor(InvocationHandler.class);
        Object object = constructor.newInstance(invocation);
        return object;
    }
}

//编译类
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;

public class JavaCompiler {
    public static void compile(File javaFile) throws IOException {
        javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable iterable = fileManager.getJavaFileObjects(javaFile);
        javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
        task.call();
        fileManager.close();
    }
}

接下来我们验证一下自己模仿jdk写的动态代理类的功能:
先写一个InvocationHandler实现类

public class JdbcInvocationHandler implements InvocationHandler {
    private JdbcTemplate jdbcTemplate;

    public JdbcInvocationHandler(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public Object invoke(Method method, Object[] args) {
        try {
            long start = System.currentTimeMillis();
            method.invoke(jdbcTemplate, args);
            long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start) + "ms");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

接下来测试一下效果

public class MainTest {
    public static void main(String[] args) throws Exception {
        //自定义增强类
        JdbcInvocationHandler handler = new JdbcInvocationHandler(new JdbcTemplate());
        //获取动态代理实例
        Object proxyInstance = Proxy.newProxyInstance(Connection.class, handler);
        Connection connection = (Connection) proxyInstance;
        Object param = new Object();
        //调用方法
        connection.add(param);
        connection.update(param);
    }
}

执行结果:打印形式或者线程休眠时间读者可以自行添加

add
耗时:0ms
update
耗时:0ms

到此为止我们实现了一个基本的基于接口的动态代理工具类,Jdk的动态代理和我们写的原理是一样的
我们来看一下jdk动态代理的IvocationHandler接口的方法就一个:

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

多了一个Object proxy,这个是可以用于返回代理类等相关对象的。其它的定义和我们自己定义的一样。
jdk Proxy方法

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {}

和我们不一样的是接口Class传了一个数组,这个很好理解,一个类可以实现多个接口;还多了一个classLoder,用于用户自定义类加载器,上面我们的类加载器是写死的UrlClassLoder;其它基本和我们写的一样
jdk动态代理源码最重要的两句

			final Constructor<?> cons = cl.getConstructor(constructorParams);
            return cons.newInstance(new Object[]{h});

跟我们之前分析的实现原理也是一样的。
好了本篇文章就到这里了,下一篇带大家一起看一下Jdk动态代理的源码

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资的高效利用。同时,教学质量评估中心和资应用平台的建设,旨在提升教学评估的科学性和教育资的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值