对象必须实现 iconvertible_10分钟让你彻底了解Java动态代理的实现与原理

目录:

1、什么是代理?

2、静态代理的实现方式

3、什么是动态代理?动态代理的实现方式?

4、为什么JDK动态代理必须需要实现一个接口?

5、怎样模仿JDK动态代理实现自己的动态代理?

6、动态代理有哪些使用场景?

7、总结

什么是代理模式?

定义:为其他对象提供一种代理以控制对这个对象的访问。

那我们形象化的说明一下:

比如我们找女朋友,自己身边没有资源,那么就会找婚介所、找朋友介绍、找媒婆、参加相亲大会。

70f78069a83d25278067120d9683aae0.png

代理模式的结构图如下:

afbf8fece60a5546cd22b08e8bdb1501.png

结构说明:

(1) Proxy: 代理对象

实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象。可以控制对具体目标对象的访问,并可以负责创建和删除它。

(2) Subject: 目标接口

定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。

(3) RealSubject

具体的目标对象,真正实现目标接口要求的功能。

静态代理的实现方式

(1) 目标接口的定义

/** * 代理对象 */public class Proxy implements Subject{    /**     * 持有被 代理的具体的目标对象     */    private Subject realSubject = null;    public Proxy(RealSubject realSubject){        this.realSubject = realSubject;    }    /**     *     */    @Override    public void request( ) {        //在 转调具体的目标对象前,可以执行一些功能处理        //转调具体的目标对象的方法        realSubject.request();        // 在 转调 具体的目标对象后,可以执行一些功能处理    }}

(2) 具体目标的实现

/** * 代理对象 */public class Proxy implements Subject{    /**     * 持有被 代理的具体的目标对象     */    private Subject realSubject = null;    public Proxy(RealSubject realSubject){        this.realSubject = realSubject;    }    /**     *     */    @Override    public void request( ) {        //在 转调具体的目标对象前,可以执行一些功能处理        //转调具体的目标对象的方法        realSubject.request();        // 在 转调 具体的目标对象后,可以执行一些功能处理    }}

(3)代理类的实现

/** * 代理对象 */public class Proxy implements Subject{    /**     * 持有被 代理的具体的目标对象     */    private Subject realSubject = null;    public Proxy(RealSubject realSubject){        this.realSubject = realSubject;    }    /**     *     */    @Override    public void request( ) {        //在 转调具体的目标对象前,可以执行一些功能处理        //转调具体的目标对象的方法        realSubject.request();        // 在 转调 具体的目标对象后,可以执行一些功能处理    }}

什么是java动态代理?动态代理的实现方式?

在引出动态代理之前,我们先说一下静态代理的缺点?

(1) 静态代理的时候,如果在接口上定义了很多方法,代理类里面也要写很多方法;而动态代理实现的时候,虽然接口上定义了很多方法,但是动态代理类始终只有一个invoke方法。这样,当需要代理的接口发生变化的时候,动态代理的接口发生变化的时候,动态代理的接口就不需要跟着变化了。

(2) 在静态代理中,我们需要在代理类中,将原始类的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。如果要添加的附加功能的类不止一个,我们需要针对每个类都创建一个代理类。如果有100类要添加附加的功能的原始类,我们就要创建100个对应的代理类。这将导致项目中的类迅速增多,大大增加了维护的成本。并且,每个代理类中的代码都有很多重复的。也增加了不必要的开发成本。那么这个问题怎么解决呢?

我们可以使用动态代理来解决这个问题。那么什么动态代理呢?

动态代理就是我们不事先为每个需要代理的来写代理类,而是在运行的时候,动态地创建对应的代理类,然后在系统中用代理类替换被代理的类。那么如何实现呢?

1、Java动态代理

Java 对代理模式提供了内建的支持,在java.lang.reflect包下面,提供了一个Proxy类和一个InvocationHandler的接口。

动态代理的实现步骤如下:

(1) 要实现InvocationHandler接口。

(2) 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好后,返回被代理的目标对象的接口,以利于客户端的操作。

(3) 需要实现invoke方法。在这个方法中,具体判断当前是在调用什么方法,需要如何处理?

示例代码如下:

public class UserInvocationHandler  implements InvocationHandler{    /**     *     * 被代理的对象     */    private Object target;    public Object newInstance(Object target) throws Exception{        this.target = target;        Class> clazz = target.getClass();        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        try {            System.out.println("开始事务....");            method.invoke(this.target,args);        }catch (Exception e){            System.out.println("回滚事务....");        }finally {            System.out.println("提交事务....");        }        return  null;    }}

客户端如何使用这个动态代理呢。代码如下:

        IUserService userService = new UserServiceImpl();        IUserService userServiceProxy = (IUserService) Proxy.        newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),                         new UserInvocationHandler());        userServiceProxy.save();

SpringAOP 底层的实现原理就是基于动态代理的。首先用户需要配置哪些类需要代理?哪些方法需要代理,并定义好在业务代码前后的附加功能。SpringAOP 实现方式是在Bean初始化完成后,执行动态代理,然后把动态代理类写入Map中,比如有一个UserService,Spring底层是这样存储的的

Map map = new ConcurentHashMap<>();

map.put("userService",userService);

当执行动态代理后,就变成了下面的形式

map.put("userService",userServiceProxy);

这样代码中每次获取UserService都是代理类.

2、CGLIB 动态代理

Java动态代理的类必须实现接口,有很强的局限性,而CGLIB 为目标对象转件代理对象时,目标对象可以不实现接口。在CGLIB底层,其实是使用ASM这个非常强大的Java字节码生成框架。

那么怎样使用呢?

public class UserServiceCglib implements MethodInterceptor{    public Object getInstance(Class> clazz) throws  Exception{        Enhancer enhancer = new Enhancer();        //要把哪个设置为即将生成的新类父类        enhancer.setSuperclass(clazz);        enhancer.setCallback(this);        return  enhancer.create();    }    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)    throws Throwable {        //业务的增强try{  System.out.println("开始事务");  methodProxy.invokeSuper(o,objects);}catch(Exception e){   System.out.println("回滚事务 ");}finally{  System.out.println("提交事务 ");}        return null;    }}

最后我们对比一下JDK动态代理和CGLIB代理

(1) JDK代理要求被代理的类必须实现接口。

(2) CGLIB没有强制被代理的类必须试下接口,可以实现,也可以不实现。

为什么JDK动态代理必须需要实现一个接口?

我们生成的代理类来分析JDK动态代理为什么必须实现一个接口。

怎么查看呢?可以使用下面的代码生成代理类

IUserService userService = new UserServiceImpl();byte[] proxyClassFile = ProxyGenerator.generateProxyClass("proy0",                                                               new Class[] { userService.getClass() });Path path = new File(System.getProperty("user.dir") + "/target/proxy0.class").toPath();Files.write(path, proxyClassFile);

生成的类如下:

public final class proy0 extends Proxy implements IUserService {    private static Method m3;    public proy0(InvocationHandler var1) throws  {        super(var1);    }    public final void save() throws  {        try {            super.h.invoke(this, m3, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }}

从上面代码我们分析一下生成的原理

生成的代理类继承了Proxy类,实现了IUserService接口,因为Java是单继承的,所以不能再继承其他类,那么怎么获取代理类中的方法 生成代理类,必须实现需要代理的接口。

怎样模仿JDK动态代理实现自己的动态代理?

下面我们模仿JDK动态代理,实现自己的一个代理。有以下几个步骤:

1、实现一个 YJInvocationHandler 接口

public interface YJInvocationHandler {    public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable;}

2、自定义类加载器

怎样自定义类加载器呢?我们需要自定义类加载器,然后从本地盘中加载生成的class文件。

/** * 自定义类加载器 */public class YJClassLoader extends ClassLoader{    private File classPathFile;    public YJClassLoader(){        String classPath = YJClassLoader.class.getResource("").getPath();        this.classPathFile = new File(classPath);    }    @Override    protected Class> findClass(String name) throws ClassNotFoundException {        String className = YJClassLoader.class.getPackage().getName() + "." + name;        if(classPathFile != null){            File classFile = new File(classPathFile,name.replaceAll(".","/") + ".class");            if(classFile.exists()){                FileInputStream in = null;                ByteArrayOutputStream out = null;                try{                    in = new FileInputStream(classFile);                    out = new ByteArrayOutputStream();                    byte [] buff = new byte[1024];                    int len;                    while ((len = in.read(buff)) != -1){                        out.write(buff,0,len);                    }                    return  defineClass(className,out.toByteArray(),0,out.size());                }catch (Exception e){                    e.printStackTrace();                }finally {                    if(null != in){                        try {                            in.close();                        } catch (IOException e) {                            e.printStackTrace();                        }                    }                    if(out != null){                        try {                            out.close();                        } catch (IOException e) {                            e.printStackTrace();                        }                    }                }            }        }        return null;    }

ClassLoader 类有以下两个关键方法:

  • loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。
  • findClass(String name):根据二进制名称来查找类。

如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,建议使用findClass()方法,而不是重写loadClass()方法。

3、YjProxy 代理类,和JDK中的Proxy类似

怎样实现YjProxy代理类呢?

(1) 动态生成源代码.java文件

实现一个代理类,实现需要代理的接口,通过构造方法把YJInvocationHandler实现类传进去,然后循环接口的每个方法,然后调用YJInvocationHandler实现类中invoke方法。最终生成一个Java文件。

public class UserInvocationHandler  implements InvocationHandler{    /**     *     * 被代理的对象     */    private Object target;    public Object newInstance(Object target) throws Exception{        this.target = target;        Class> clazz = target.getClass();        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        try {            System.out.println("开始事务....");            method.invoke(this.target,args);        }catch (Exception e){            System.out.println("回滚事务....");        }finally {            System.out.println("提交事务....");        }        return  null;    }}

(2) 将Java文件输出磁盘

(3) 把生成的.java文件编译成.class文件

(4) 使用我们的自定义类加载器,把生成的 class文件加载到JVM中。

        IUserService userService = new UserServiceImpl();        IUserService userServiceProxy = (IUserService) Proxy.        newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),                         new UserInvocationHandler());        userServiceProxy.save();

代理类实现完成后,我们要写一个测试类来测试一下,我们使用保存用户的事务来演示:

用户服务接口如下:

public interface IUserService {    public void save();}

实现类:

public class UserServiceImpl implements IUserService {    @Override    public void save( ) {        System.out.println("保存用户................");    }}

代理类:

public class UserServiceProxy implements YJInvocationHandler {    /**     *     * 被代理的对象     */    private Object target;    public Object newInstance(Object target) throws Exception{        this.target = target;        Class> clazz = target.getClass();        return YjProxy.newProxyInstance(new YJClassLoader(),clazz.getInterfaces(),this);    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {       try {           System.out.println("开始事务....");            method.invoke(this.target,args);       }catch (Exception e){           System.out.println("回滚事务....");       }finally {           System.out.println("提交事务....");       }        return  null;    }}

测试类:

  public static void main(String[] args) throws Exception {        IUserService userService = new UserServiceImpl();        IUserService proxyUserService = (IUserService) new  UserServiceProxy().newInstance(userService);        proxyUserService.save();    }

测试结果:

54b63f16e17aa640c9dbafe55e78f888.png

那么有还没有其他的实现方式呢?答:有

可以使用javassist技术,Javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成。

Javassist有什么作用呢?

a. 运行时监控插桩埋点

b. AOP动态代理实现(性能上比Cglib生成的要慢)

c. 获取访问类结构信息:如获取参数名称信息

05146cfa89886e59788512af39c59b14.png
public class UserServiceCglib implements MethodInterceptor{    public Object getInstance(Class> clazz) throws  Exception{        Enhancer enhancer = new Enhancer();        //要把哪个设置为即将生成的新类父类        enhancer.setSuperclass(clazz);        enhancer.setCallback(this);        return  enhancer.create();    }    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)    throws Throwable {        //业务的增强try{  System.out.println("开始事务");  methodProxy.invokeSuper(o,objects);}catch(Exception e){   System.out.println("回滚事务 ");}finally{  System.out.println("提交事务 ");}        return null;    }}

动态代理有哪些使用场景?

代理模式在我们平时开发的应用系统中场景还是很多的。不是让你拿着代理模式去死搬硬套的去使用,要学会举一反三,Java中的设计模式应用到项目中或者框架中,都是经过变种的,或者是几个设计模式融合在一起的。

1、业务系统的非功能性需求开发

我们开发应用时,经常会涉及到一些非业务需求,比如:监控、统计、鉴权、限流、事务、日志等,以前我们处理可能是在每个模块中单独写,自从有了动态代理,我们可以将这些附加功能和业务功能解耦,放到动态代理中统一处理,让程序员只关注业务的开发。

比如Spring AOP , 就是在切面中实现日志的记录和事务的处理。

2、动态代理在MyBatis中的使用

大家使用Mybatis的时候,很多都是使用分页框架 pagehelper,有了pagehelper我们再也不用关注分页的查询了,那么你知道pagehelper的原理吗?

pagehelper是基于Mybatis 插件实现的。完全可以实现无侵入实现sql的分页 ,其底部通过ThreadLocal来存放分页信息。通过动态代理拦截org.apache.ibatis.executor.Executor的query方法,然后拦截你发送的SQL。根据不同的数据库来生成不同的分页sql。

3、代理模式在 RPC、缓存中的应用

1361082416bf61cba897b74fce88c4e3.png

实际上,RPC也是一种代理模式,相当于远程代理,用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以在本机,也可以在其他机器上。在Java里最典型的就是RMI技术。RPC 包含了服务发现、负载、容错、网络传输序列化等组件,其中RPC协议就指明了程序如何进行网络传输和序列化。

通过远程代理,将通信、编解码 和序列化 等细节隐藏 起来,使用过dubbo 和 Spring Cloud的都知道,我们调用远程服务和调用本地服务一样,不需要了解通信细节、编解码、序列化等繁琐的细节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值