看Nacos的源代码时,发现其中有对代理模式的运用,而且用得还不错,可以作为一个典型案例来聊聊,方便大家以更真实的案例来体验一下代理模式的运用。如果你对Nacos不了解,也并不影响对本篇文章的阅读和学习。,本文涉及知识点:代理模式的定义、代理模式的运用场景、Nacos的服务注册、静态代理模式、动态代理模式、Cglib动态代理、Spring中AOP所使用的代理等。,代理模式(Proxy Pattern)是一种结构型设计模式,通常使用代理对象来执行目标对象的方法并在代理对象中增强目标对象的方法。,定义有一些绕口,举个生活中的简单例子:你去租房,可以直接找房东,也可以找中介。而代理模式就是你租房不用找房东,通过中介来租,而中介呢,不仅仅能够提供房屋出租服务(目标对象的方法),还可以提供房屋清洁的服务(对目标对象方法的增强)。,在上述例子中,中介是代理对象,房东是目标对象(或委托对象),中介为房东提供了出租的功能,在出租的功能上代理又可以提供增强的房屋清洁功能。,为什么要使用代理模式呢?,原因有二:,代理模式通常可分为两类:静态代理和动态代理。动态代理的实现又有JDK动态代理和CGLIB动态代理两种实现方式。,静态代理是由开发人员直接编写代理类,代理类和委托类之间的关系在运行前已经确定好的。当需要修改或屏蔽一个或若干类的部分功能,复用另一部分功能时,可使用静态代理。,动态代理的代理类是在运行时期间由编译器动态生成(比如,JVM的反射机制生成代理类),在运行时确定代理类和委托类之间的关系。当需要拦截一批类中的某些方法,在方法前后加入一些公共操作时,可使用动态代理。,在Nacos中服务注册接口使用的代理模式为静态代理。静态代理模式需要先定义接口,委托类和代理类一起实现该接口,然后通过调用代理类对应的方法间接调用委托类的对应方法。,常见的静态代理类数据模型如下:,
,静态代理(图片来源网络),上图中通过代理类对委托类的方法进行拓展,在方法执行前后新增一些逻辑处理,比如日志、计时等,这是最简单的一种代理模式实现。,在Nacos中静态代理模式运用的场景是客户端实例向Nacos的注册、注销等操作。由于实例的注册方式支持临时实例和持久实例两种方式,代理类就起到了判断到底是采用临时实例注册服务,还是使用持久实例注册服务。,下面直接以Nacos相关源码来进行解析说明。,第一步,定义接口,静态代理是需要先定义一个共同的实现接口的。,在Nacos中定义了一个ClientOperationService的接口,其中提供了实例的注册、注销等功能,这里为了方便阅读,仅展示注册实例代码(后续代码相同)。,第二步,定义两个委托类,一个委托类实现临时实例注册,一个委托类实现持久实例注册。,EphemeralClientOperationServiceImpl类为临时实例操作服务实现,实现了ClientOperationService接口。PersistentClientOperationServiceImpl类为永久实例操作服务实现,同样实现了ClientOperationService接口。,第三步,定义代理类。通常情况下,一个代理类代理一个委托类,但在Nacos中,代理类实现了区分到底是临时实例还是永久实例的逻辑,因此代理类同时代理了上述两个委托类。,代理类ClientOperationServiceProxy通过构造方法传入了两个委托类,通过chooseClientOperationService方法根据参数来判断具体使用哪个委托类,从而实现了在registerInstance方法中,根据参数动态的判断注册实例的方式。,Nacos的代理模式实现,符合我们前面提到的“客户类不想或者不能直接引用一个委托对象”的场景,这里是(每个)客户类“不想”每次调用时都判断采用何种方式注册,从而把这个判断逻辑交给了代理类才进行处理。,像Nacos中的这种实现就属于静态代理模式,在程序运行之前,已经通过代码实现了具体的代理类实现。静态代理的优点非常明显,可以在不改变目标对象的前提下,扩展目标对象的功能。,但缺点也同样明显:,静态代理是在编码阶段已经把代理类实现好了,那么是否可以在运行时动态构建代理类,来实现代理的功能呢?JDK动态代理便提供了这样的功能。,需要注意的是,JDK动态代理并不等价于动态代理,它只是动态代理的实现方式之一,即我们后面要讲到的Cglib动态代理也是动态代理的实现之一。,使用JDK动态代理时,代理对象不需要再实现接口,而目标对象依旧需要实现接口。使用JDK动态代理时需要用到两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。,下面以用户登录时,在登录操作前后打印日志为例,体验一下JDK动态代理的功能。,第一步,创建业务接口。,第二步,创建业务实现类。,第三步,创建业务逻辑处理器,实现InvocationHandler接口。,这里我们编写了一个LogHandler类,实现InvocationHandler接口,重写invoke方法。,invoke方法中定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用。,这里,在执行目标类方法前后可添加对应的日志信息打印或其他操作,在上述代码中分别打印了“Before Login”和“After Login”的信息。,第四步,模拟客户端使用。,在上述测试类中,先创建了被代理类的对象,然后通过Proxy的newProxyInstance方法构建了代理对象,生成的代理对象实现了目标类的所有接口,并对接口的方法进行了代理。,当我们通过代理对象调用具体方法时,底层将通过反射,调用我们实现的invoke方法,最后通过调用目标对象的登录方法。,执行上述方法,控制台打印日志如下:,可以看到,在登录操作前后,打印了对应的日志。,在构建代理对象时,用到了Proxy的newProxyInstance方法,该方法接收三个参数:,通过上述方式,我们实现了基于JDK的动态代理。JDK动态代理有以下特点:,可以看出,JDK动态代理的一个致命缺点就是目标类必须实现某个接口。而要解决这个问题,可以通过Cglib代理来实现,我们后面会具体讲到。,JDK动态代理类,在上述实践的过程中,我们是否考虑过,通过JDK动态代理生成的代理类到底是什么样子呢?我们通过下面的工具类,可以一探究竟。,上面代码定义了一个将代理类保持到磁盘中的工具类。然后,在JdkProxyTest类的最后,调用该方法,将JDK动态生成的代理类打印出来。,其他代码未变,最后一行添加了工具类ProxyUtils的调用。,执行上述代码,会在项目目录的target下生成名为“UserServiceProxy”的class文件。本人执行时,打印的路径为“.../target/classes/com/secbro2/proxy/”。,在该目录下找到UserServiceProxy.class类文件,通过IDE的反编译功能,可看到如下代码:,从反编译的代理类中,我们可以得到以下信息:,至此,我们已经了解了基于JDK动态代理的使用以及所生成代理类的结构,下面就来看看无需目标类实现接口的Cglib动态代理实现。,在上面的实例中可以看到无论使用静态代理或是JDK动态代理,目标类都需要实现一个接口。在某些情况下,目标类可能并没有实现接口,这时就可以使用Cglib动态代理。,Cglib(Code Generation Library)是一个功能强大、高性能、开源的代码生成包,它可以为没有实现接口的类提供代理。,Cglib代理可以称为子类代理,具体而言,Cglib会在内存中构建一个目标类的子类,重写其业务方法,从而实现对目标对象功能的扩展。因为采用继承机制,所以不能对final修饰的类进行代理。,Cglib通过Enhancer类来生成代理类,通过实现MethodInterceptor接口,在其intercept方法中对目标对象的方法进行增强,并可通过Method或MethodProxy继承类来调用原有方法。,这次以下订单(OrderService)为例来展示一下通过Cglib在下订单操作前后添加日志信息。,在使用Cglib之前,首先需要引入对应的依赖jar包,大多数项目中往往Cglib已经被间接引入了,可核实其版本是否是预期版本。这里采用Maven形式,引入Cglib依赖。,第一步,定义业务类OrderService,不需要实现任何接口。,第二步,定义动态代理类的创建及业务实现。,LogInterceptor类实现了MethodInterceptor接口,在重写的intercept方法中添加了要扩展的业务内逻辑。其中需要注意的是,intercept方法内调用的是MethodProxy#invokeSuper方法,而不是invoke方法。,同时,在LogInterceptor类中定义了创建目标对象的代理对象的工具方法getProxyInstance,值得留意的是Enhancer#setCallback方法的参数this,指的便是LogInterceptor的当前对象。,第三步,编写测试客户端。,执行上述方法,打印日志如下:,成功的在目标对象的方法前后植入日志信息。,关于Cglib动态代理有以下特点:,静态代理:代理类和目标类都需要实现接口,从而达到代理增强其功能。,JDK动态代理:基于Java反射机制实现,目标类必须实现接口才能生成代理对象。使用Proxy.newProxyInstance方法生成代理类,并实现InvocationHandler中的invoke方法,实现增强功能。,Cglib动态代理:基于ASM机制实现,通过生成目标类的子类作为代理类。无需实现接口,使用Cblib中的Enhancer来生成代理对象子类,并实现MethodInterceptor的intercept方法来实现增强功能。,JDK动态代理的优势:JDK自身支持,减少依赖,可随着JDK平滑升级,代码实现简单。,Cglib动态代理的优势:无需实现接口,达到无侵入;只操作我们关心的类,而不必为其他相关类增加工作量;,Spring的AOP实现中主要应用了JDK动态代理以及Cglib动态代理,对应的实现类位于spring-aop的jar包中。,Spring默认使用JDK动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式的动态代理。如果目标对象没有实现接口,则需要使用Cglib动态代理来实现。,在了解了JDK动态代理及Cglib动态代理的使用及特性之后,大家可以对照思考一下Spring事务失效的一些场景,Spring的事务实现便是基于AOP来实现的,比如:,关于Spring中动态代理的其他内容,本文就不再展开了,感兴趣的读者可直接阅读对应的源码。,本文从Nacos中的静态代理模式实现,延伸拓展讲解了代理模式的定义、代理模式的运用场景、静态代理模式、动态代理模式、Cglib动态代理、Spring中AOP所使用的代理等。,通过文章中关联的知识点,以及在不同跨度的项目中的实践案例,大家应该能够感知到到代理模式,特别是基于JDK动态代理和Cglib动态代理在实践中的重要性。抓紧学一波吧。,https://segmentfault.com/a/1190000040407024,https://juejin.cn/post/6844903744954433544,https://www.cnblogs.com/clover-toeic/p/11715583.html
© 版权声明
文章版权归作者所有,未经允许请勿转载。