当前位置:首页 > java > 正文内容

静态代理和动态代理的区别

关中浪子2年前 (2022-03-10)java1108
【腾讯云】2核2G4M云服务器新老同享99元/年,续费同价

静态代理

静态代理,设计模式的代理模式举例通常是用这种方式实现的,对于每一个要代理的类为了添加相同的操作,需要分别去实现其接口,容易造成代理类过多


public interface Subject {
    public void doSomething();
}
public class RealSubject inplements Subject {
    public void doSomething() {
        // haha
    }
}
public class SubjectProxy implements Subject {
    Subject origin = new RealSubject();
    public void doSomething(){
    // 这里添加要在方法执行前执行的逻辑
        origin.doSomething();
    // 这里添加要在方法执行后执行的逻辑
    }
}


静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能。


动态代理

Java中的动态代理有两种实现方式:


Jdk动态代理

自Java 1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多地方。


JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。


public interface Subject {
    void doSomething();
}
public static class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("haha");
    }
}
public static class MyInvocationHandler implements InvocationHandler {
    //目标对象
    private Subject sub;
    public MyInvocationHandler(Subject sub) {
        this.sub = sub;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("切面方法");
        //调用目标类的目标方法
        Object obj = method.invoke(this.sub, args);
        System.out.println("切面方法");
        return obj;
    }
}
public static class Test {
    public static void main(String[] args) {
    Subject subject = new RealSubject();
    MyInvocationHandler invocationHandler = new MyInvocationHandler(subject);
    //生成代理对象,此代理对象实现了Subject接口
    Subject proxyInstance = (Subject) Proxy.newProxyInstance(
    subject.getClass().getClassLoader(),
    subject.getClass().getInterfaces(),
    invocationHandler);
    proxyInstance.doSomething();
    }
}


JDK动态代理有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例?答案就是CGLib。


Cglib

CGLib采用底层的字节码技术(ASM),全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。


public class Base {  //被代理类
    public void add() {
        System.out.println("add ------------");
    }
}
public class CglibProxy implements MethodInterceptor {
    //此为代理类,用于在pointcut处添加advise
    public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { //拦截器
        // 添加切面逻辑(advise),此处是在目标类代码执行之前,即为MethodBeforeAdviceInterceptor。
        System.out.println("before-------------");
        // 执行目标类add方法
        proxy.invokeSuper(object, args);
        // 添加切面逻辑(advise),此处是在目标类代码执行之后,即为MethodAfterAdviceInterceptor。
        System.out.println("after--------------");
        return null;
    }
}
public class Factory {  //工厂类,生成加强过的目标类
    //获得增强之后的目标类,即添加了切入逻辑advice之后的目标类
    public static Base getInstance(CglibProxy proxy) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Base.class);
        //回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
        enhancer.setCallback(proxy);
        // 此刻,base不是单纯的目标类,而是增强过的目标类
        Base base = (Base) enhancer.create();
        return base;
    }
}
public class Test {  //测试类
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();//代理类
        // base为生成的增强过的目标类
        Base base = Factory.getInstance(proxy);
        base.add();
    }
}


两者区别

JDK动态代理是面向接口的。


CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么会失败)


JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:(以下是旧版本JDK与Cglib对比)


(jdk<1.8)CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

(jdk<1.8)但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

JDK动态代理的版本优化中主要是针对虚拟机对反射调用的优化,优化如下:


在jdk1.6中,反射方法的调用在15次以内是调用本地方法,即是java到c++代码转换的方法,这种方式比直接生成字节码文件要快的多,而在15次之后则开始使用java实现的方式。(生成比较快,执行相对慢)

在1.8的版本优化中,反射调用的次数达到阈值(也就是发射调用的类成为热点时)之后采用字节码的方式,因为字节码的方式只有在第一次生成字节码文件时比较消耗时间。

总结


jdk需要接口,生成的代理和目标类都需要实现接口,生成的是兄弟。在生成类的过程比较高效

Cglib不需要接口,不需要更改目标类,生成的代理是目标类的子类,是儿子。在生成类之后类的执行过程中比较高效(适合单例、有实例池的对象)

Mybatis用jdk动态代理实现,而Spring两种都用,有接口时用动态代理,没有接口用cglib


找梯子最重要的就是稳定,这个已经上线三年了,一直稳定没有被封过,赶紧下载备用吧!

扫描二维码推送至手机访问。

版权声明:本文由码农翻生发布,如需转载请注明出处。

本文链接:https://lubojian.cn/post/133.html

分享给朋友:

相关文章

SpringBoot   http请求 提交日期参数 转换失败的解决方法 (无法自动转换字符串到日期)

SpringBoot http请求 提交日期参数 转换失败的解决方法 (无法自动转换字符串到日期)

如果你有类似如下报错信息,可阅读此文尝试解决:2018-08-18 14:05:17.687  WARN 17100 --- [p-nio-80-exec-2] .w.s...

springboot  导出数据到 excel

springboot 导出数据到 excel

问题来源:前一段时间公司的项目有个导出数据的需求,要求能够实现全部导出也可以多选批量导出(虽然不是我负责的,我自己研究了研究),我们的项目是xboot前后端分离系统,后端的核心为SpringBoot 2.2.6.RELEASE,因此今天我主...

poi4.0.0读取excel文件时报java.lang.NoClassDefFoundError: org/apache/commons/compress/archivers/zip/ZipFile

最近想用poi写个处理excel的工具,看了一下poi的官网,出了个4.0.0的版本,于是想尝尝鲜,下载了一把poi4.0.0的bin。下载的是下面的文件: 下载完,将压缩包里所有的jar包导入到工程中(当然,如果只需要处理exc...

JWT实现认证和授权的原理 和小程序SESSION_KEY(第三方session)

目前的H5,公众号,小程序,APP,等登录授权方式都是JWT来实现的,就是用户在登录后后端返回一个token,然后下次请求时request头会带上token,然后后端根据此token来解析用户信息,总结下就3步。用户调用登录接口,登录成功后...

SQL之CASE WHEN用法详解

当我们需要从数据源上 直接判断数据显示代表的含义的时候 ,就可以在SQL语句中使用 Case When这个函数了.Case具有两种格式。简单Case函数和Case搜索函数。 第一种 格式 : 简单Case函数 :格式说明...

简单两步配置 Maven远程仓库 阿里云镜像 【图文】

简单两步配置 Maven远程仓库 阿里云镜像 【图文】

为什么配置阿里云的镜像,是因为它比较快。咱国内的,赞1、打开Maven目录中conf目录下找打setting.xml右键打开;2、添加这段到mirrors标签中,默认这标签是没有什么内容的;<!-- 阿里云镜像 --> ...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。