静态代理和动态代理

文章来自:郭霖的公众号https://mp.weixin.qq.com/s/dSt4ifbAaRxPAc7gCAVxoA
如果按照代理创建的时期来进行分类的话, 可以分为静态代理、动态代理

静态代理是由程序员创建或特定工具自动生成代理类,再对其编译,在程序运行之前,代理类.class文件就已经被创建了
动态代理是在程序运行时通过反射机制动态创建代理对象

微信图片_20190703124638.jpg

开源框架应用:

Spring框架是时下很流行的Java开源框架,Spring之所有如此流行,跟它自身的特性是分不开的,一个是IOC,一个是AOP

IOC是Inverse Of Control,即控制反转,也有人把IOC称作依赖注入。我觉得依赖注入这种说法很好理解,但不完全对;依赖注入是Dependency Injection的缩写,是实现IOC的一种方法,但不等同于IOC,IOC是一种思想,DI只是一种实现

AOP是Aspect Oriented Programming的缩写,即面向切面编程;与面向过程和面向对象的编程方式相比,面向切面编程提供了一种全新的思路,解决了OOP编程过程中的一些痛点。

IOC的实现原理是利用了JAVA的反射技术,那么AOP的实现原理是什么呢?就是动态代理技术,目前动态代理技术主要分为Java自己提供的JDK动态代理技术和CGLIB技术

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

/ 静态代理 /

静态代理是代理模式实现方式之一,比较简单,主要分为三个角色:客户端,代理类,目标类;而代理类需要与目标类实现同一个接口,并在内部维护目标类的引用,进而执行目标类的接口方法,并实现在不改变目标类的情况下前拦截,后拦截等所需的业务功能。

假设现在项目经理给你提了一个需求:在项目所有类的有关用户操作的方法前后打印日志;这种情况下就可以通过静态代理实现

做法就是:为每个相关类都编写一个代理类(业务重合的可以共用一个代理类),并且让它们实现相同的接口

public interface ILogin {
    void userLogin();
}
定义目标类:
public class UserLogin implements ILogin {

    @Override
    public void userLogin() {
        System.out.print("用户登录");
    }
}

定义代理类:

public class UserLoginProxy implements ILogin {

    private UserLogin mLogin;
    public UserLoginProxy() {
        mLogin = new UserLogin();
    }

    @Override
    public void userLogin() {
        System.out.print("登录前。。。");
        mLogin.userLogin();
        System.out.print("登录后。。。");
    }
}

客户端:

public class UserLoginProxy implements ILogin {
public class Test {
    public static void main(String[] args){
        ILogin loginProxy = new UserLoginProxy();
        loginProxy.userLogin();
    }
}

这样我们就在尽量不修改现有类的基础上实现了这个需求,同时客户端只用跟代理类打交道,完全不用了解代理类与目标类的实现细节,也不需要知道目标类是谁;当然你如果想指定目标类,将在外部创建目标类,传给代理类

这里只是在同步情况下的一种实现,如果是异步操作,就需要进行一些改动

静态代理总结:

优点:在符合开闭原则的情况下,对目标对象功能进行扩展和拦截
缺点:需要为每个目标类创建代理类和接口,导致类的数量大大增加,工作量大;接口功能一旦修改,代理类和目标类也得相应修改,不易维护

从静态代理的实现过程可以知道工作量太大,如果是在项目早期同步做还好,要是在接手老项目或者项目晚期再做,你可能要为成百上千个类创建对应的代理对象,那真的挺让人崩溃的;所以我们就需要想有没有别的方法:如何少写或者不写代理类却能完成这些功能

做Java的都知道一个.java文件会先被编译成.class文件,然后被类加载器加载到JVM的方法区,存在形式是一个Class对象(所谓的Class对象就是Class文件在内存中的实例);

我们构造出的任何对象实例是保存在Heap中,而实例是由其Class对象创建的(可以通过任意实例的getClass方法获取对应的Class对象),比如平时使用new关键字加构造方法Person p = new Person()就是将这个Class对象在Heap中创建一个实例;

可以看出要创建一个实例,最关键的是得到对应的Class对象,而得到Class对象追根溯源需要一个Java文件;那么我们要做的就是不写Java文件,而是直接得到代理Class对象,然后根据它通过反射创建代理实例

Class对象里面包含了一个类的所有信息,比如构造器,方法,字段等;那我们怎么在不写代理类的前提下获取到这些信息呢?

从静态代理的实现知道目标类和代理类实现了同一个接口,这是为了尽可能保证代理对象的内部结构和目标对象一致,这样代理对象只需要专注于代码增强部分的编写,所以接口拥有代理对象和目标对象共同的类信息,那么我们就可以从接口获取本来应该由代理类提供的信息,但是要命的是接口是不能实例化的啊

这就要讲到动态代理了:在动态代理中,不需要我们再手动创建代理类,只需要编写一个动态处理器及指定要代理的目标对象实现的接口,真正的代理对象由JDK在运行时为我们创建;JDK提供了java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy来实现动态代理

JDK代理

Proxy.getProxyClass(ClassLoader, interfaces)方法只需要接收一个类加载器和一组接口就可以返回一个代理Class对象,然后就可以通过反射创建代理实例;其原理就是从传入的接口Class中,拷贝类结构信息到一个新的Class对象中,并继承Proxy类,拥有构造方法;站在我们的角度就是通过接口Class对象创建代理类Class对象

这里通过一个很骚的比喻来说明下:一个东厂太监(接口Class对象)有一家子财产,但是为了侍奉皇帝这一伟大事业,毅然决然的割了DD(没有构造方法),虽然实现了自己的理想,但是不能繁育下一代(不能构造器创建对象),也就没有后人继承自己的家业;但是好在华佗在世,江湖上有一个高人(Proxy),发明了一个克隆大法(getProxyClass),不仅克隆出了几乎和太监一样的下一代(新的Class),还拥有自己的小DD(构造方法),这样这个下一代就能继承太监的家产(类结构信息,其实是实现了该接口),同时还能娶妻生子,传给下一代(创建实例)

 public static Object loadProxy(Object target) throws Exception {
        //通过接口Class对象创建代理Class对象
        Class<?> proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
        //拿到代理Class对象的有参构造方法
        Constructor<?> constructors = proxyClass.getConstructor(InvocationHandler.class);
        //反射创建代理实例
        Object proxy = constructors.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("执行前日志..."+"\n");
                //执行目标类的方法
                Object result = method.invoke(target, args);
                System.out.println("执行后日志..."+"\n");
                return result;
            }
        });
        return proxy;
    }

    public static void main(String[] args) throws Exception {
        ILogin proxy = (ILogin) loadProxy(new UserLogin());
        proxy.userLogin();
    }

这样无论系统有多少目标类,通过传进来的目标类都可以获取到对应的代理对象,就达到我们在执行目标类前后加日志的效果了,同时还不需要编写代理类

Proxy类还有个更简单的方法newProxyInstance,直接返回代理对象,如下

 public static Object loadProxy(Object object) {
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(), //和目标对象的类加载器保持一致
                object.getClass().getInterfaces(), //目标对象实现的接口,因为需要根据接口动态生成代理对象
                new InvocationHandler() { //事件处理器,即对目标对象方法的执行

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("执行前日志...");

                        Object result = method.invoke(object, args);

                        System.out.println("执行后日志...");
                        return result;
                    }
                });
    }

JDK动态代理总结:

优点:相对于静态代理,极大的减少类的数量,降低工作量,减少对业务接口的依赖,降低耦合,便于后期维护;同时在某些情况下是最大的优势,即可以统一修改代理类的方法逻辑,而不需要像静态代理需要修改每个代理类

缺点:因为使用的是反射,所以在运行时会消耗一定的性能;同时JDK代理只支持interface的动态代理,如果你再继续深究源码,会发现,所有动态生成的代理对象都有一个共同的父类,即都继承于Proxy;
Java的单继承机制决定了无法支持class的动态代理,也就意味着你拿到动态生成的代理对象,只能调用其实现的接口里的方法,无法像静态代理中的代理类可以在内部扩展更多的功能

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

推荐阅读更多精彩内容

  • 1、代理概念 为某个对象提供一个代理,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用...
    孔垂云阅读 7,654评论 4 54
  • 一、代理概念 为某个对象提供一个代理,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用...
    wyatt_plus阅读 791评论 0 5
  • 原创文章,转载请标注出处:《Java基础系列-静态代理和动态代理》 1、动态代理(Dynamic Proxy) 代...
    唯一浩哥阅读 958评论 0 10
  • 一:概述 二:静态代理 理解静态代理的经典场景就是买火车票,火车票代售点和火车站之间的关系就是静态代理的关系。代售...
    涂豪_OP阅读 703评论 0 0
  • 我的三个父亲 一个是我的亲生父亲,他在我两岁的时候弃我而去,到了西安。听说官位不小,是个高干。在86年,我经过陕...
    凡师阅读 673评论 2 3