一文读懂java中的代理模式(代码片段)

author author     2022-12-05     411

关键词:

代理(Proxy)模式是我们在工作中广泛使用的设计模式之一,提供了对目标对象额外的访问方式。通过代理对象来访问目标对象,可以对目标对象进行功能的增强,即扩展目标对象的功能。例如在Spring中,AOP就是使用动态代理来实现的。

举个栗子,当我们买不到演唱会门票时,只能通过找黄牛替我们买票,将买票这一过程交给他们去代办。在这一环节中,我们不接触到真正的购票公司,黄牛就相当于是代理。目标对象购票公司提供一个代理对象黄牛,通过黄牛可以调用购票公司的部分功能(买票),并添加一些额外的业务功能(交额外的手续费)。

JAVA中实现代理的存在两种方式,下面分别对其进行介绍。

一、静态代理

从创建时期来看,静态代理是由程序员创建或特定工具自动生成源代码再对其编译。在程序运行前代理类的class文件就已经存在了。 从实现方式来看,又可分为继承和聚合两种方式。通过代理类的对象调用重写的方法时,实际上执行的是被代理类同样的方法的调用。

1、继承方式

代理类继承目标类,重写目标类中需要增强的方法:

//售票公司
public class TicketCompany 
    public void sellTicket()
        System.out.println("售票");
    


//黄牛
public class TicketScalper extends TicketCompany
    @Override
    public void sellTicket() 
        super.sellTicket();
        System.out.println("收手续费");
    

2、聚合方式

代理类和目标类实现同一个接口,代理对象当中要包含目标对象。代理类中通过注入目标类的对象,然后重写方法进行功能增强。

public interface Company 
    public void sellTicket();


//售票公司
public class TicketCompany implements Company
    @Override
    public void sellTicket()
        System.out.println("售票");
    


//黄牛
public class TicketScalper implements Company
    TicketCompany ticketCompany;
    public TicketScalper( TicketCompany ticketCompany)
        this.ticketCompany=ticketCompany;
    
    @Override
    public void sellTicket() 
        ticketCompany.sellTicket();
        System.out.println("收手续费");
    

通过以上两种静态代理方式,可以做到在不修改目标对象的功能前提下,对目标功能进行扩展。但是也存在一些缺点,每当我们需要扩展目标类的功能时,就需要重写一个代理类,容易造成代理类过多,项目结构复杂。此外,一旦接口增加方法,目标对象与代理对象都要维护。

总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦写代码,就会产生类,容易造成文件规模的大量增长。那么如何解决这些缺陷呢?我们使用动态代理。

二、动态代理

在静态代理中,一个代理对象只能代理一个目标对象,并且在编译时就已经确定代理逻辑。而动态代理是在运行时,通过反射机制动态创建而成,并且能够代理各种类型的对象。而动态代理也存在两种方式,JDK动态代理与CGLIB代理。

1、JDK动态代理

Java中的Proxy类,提供了newInstance静态方法可以动态生成代理对象。

先看看代码实现:

public class TicketScalperJdkProxy 
    public static void main(String[] args) 
        TicketCompany ticketCompany = new TicketCompany();
        Company company =(Company) Proxy.newProxyInstance(ticketCompany.getClass().getClassLoader(),
                ticketCompany.getClass().getInterfaces(),
                new InvocationHandler() 
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
                        if(method.getName().equals("sellTicket"))
                            System.out.println("黄牛收取手续费");
                        
                        return method.invoke(ticketCompany, args);
                    
                );
        company.sellTicket();
    

newProxyInstance方法中传入的三个参数,依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,一般使用目标对象的类加载器
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入,通过其中的invoke方法进行功能的增强

从传入的第二个参数中,可以发现,要实现JDK动态代理,目标对象必须实现一个接口,才能对接口中的方法进行代理。那么如果没有实现接口呢?CGLIB为我们提供了另一种方式。

2、CGLIB代理

JDK动态代理虽然简单易用,但是只能对接口进行代理。如果被代理的类是一个普通类没有接口,那么JDK动态代理就没法使用了,这时就可以使用CGLIB基于继承来实现代理。

CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。

使用CGLIB来实现功能增强:

public class CglibTest 
    public static void main(String[] args) 
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(TicketCompany.class);
        enhancer.setCallback(new MethodInterceptor() 
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable 
                System.out.println("收手续费");
                Object result = methodProxy.invokeSuper(o, args);
                System.out.println("完成交易");
                return result;
            
        );
        TicketCompany sampleClass=(TicketCompany) enhancer.create();
        sampleClass.sellTicket();
    

需要注意的是,目标类不能为final,目标对象的方法如果为final / static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

最后顺带看一下Spring AOP中使用的代理。在Spring中,通过注解来开启AOP时,默认使用的代理方式为JDK动态代理

@Configuration
@EnableAspectJAutoProxy()
public class AppConfig 

使用debug看一下获取的代理对象:

修改@EnableAspectJAutoProxy注解中proxyTargetClass的属性,可以将其替换为CGLIB代理方式

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig 

再看一下代理对象,已经变为使用CGLIB代理方式:

总的来说,JDK动态代理使用Java原生的反射API来进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效。至于具体的使用,更多还要取舍于我们的应用环境,还是那句话,没有最好的技术,只有更适用的业务场景。

最后

一文读懂物联网mqtt协议之实战篇(代码片段)

...前言上一篇我们介绍了MQTT协议格式以及相关的特性:一文读懂物联网MQTT协议之基础特性篇,这一篇我们就来实战一番,理论得与实践结合,方能吃透MQTT。我的那个读者还提到了讲一下Mosquitto,这是一款开源... 查看详情

一文读懂物联网mqtt协议之实战篇(代码片段)

...前言上一篇我们介绍了MQTT协议格式以及相关的特性:一文读懂物联网MQTT协议之基础特性篇,这一篇我们就来实战一番,理论得与实践结合,方能吃透MQTT。我的那个读者还提到了讲一下Mosquitto,这是一款开源... 查看详情

一文读懂redis的四种模式,单机主从哨兵集群(代码片段)

前言:redis有多种模式:单机模式、主从模式、哨兵模式、集群模式1.单机模式安装一个redis,启动起来,业务调用即可。单机在很多场景也是有使用的,例如在一个并非必须保证高可用的情况下。优点:部... 查看详情

一文读懂redis中的多路复用模型(代码片段)

一文读懂Redis中的多路复用模型服务端思维  5天几种I/O模型为什么Redis中要使用I/O多路复用这种技术呢?首先,Redis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输... 查看详情

一文彻底读懂python装饰器(代码片段)

装饰器主要用途是:不修改函数源码的前提下,添加额外的功能。如果你有Java开发经验,你会发现,Python中的装饰器其实就类似于Java的注解。好的,废话不多说,进入正题。我们假想如下一个场景:... 查看详情

一文彻底读懂python装饰器(代码片段)

装饰器主要用途是:不修改函数源码的前提下,添加额外的功能。如果你有Java开发经验,你会发现,Python中的装饰器其实就类似于Java的注解。好的,废话不多说,进入正题。我们假想如下一个场景:... 查看详情

drools规则引擎一文读懂(代码片段)

目录一、Drools简介电商平台的促销活动规则引擎Drools简介二、Drools快速入门 电商平台促销积分规则开发实现三、Drools规则引擎构成及其核心类Drools规则引擎构成Drools规则引擎概念四、Drools基础语法规则文件构成 规则体语法结... 查看详情

一文读懂数仓中的pg_stat(代码片段)

...也可以通过下面函数进行查询。本文分享自华为云社区《一文读懂pgstat》,作者:leapdb。GaussDB(DWS)在S 查看详情

linux-一篇带你读懂curlproxy代理模式(代码片段)

curl是一个很有名的处理网络请求的类Unix工具。出于某种原因,我们进行网络请求,需要设置代理。本文讲全面介绍如何为curl设置代理设置代理参数基本用法-x,--proxy[protocol://]host[:port]设置HTTP代理下面两种设置代理的方式... 查看详情

linux-一篇带你读懂curlproxy代理模式(代码片段)

curl是一个很有名的处理网络请求的类Unix工具。出于某种原因,我们进行网络请求,需要设置代理。本文讲全面介绍如何为curl设置代理设置代理参数基本用法-x,--proxy[protocol://]host[:port]设置HTTP代理下面两种设置代理的方式... 查看详情

一文读懂.net中的高性能队列channel(代码片段)

介绍System.Threading.Channels是.NETCore3.0后推出的新的集合类型,具有异步API,高性能,线程安全等特点,它可以用来做消息队列,进行数据的生产和消费,公开的 Writer 和 Reader api对应消息的生产者和消费者,也让Channel... 查看详情

java并发关键字大练兵—一文读懂各个关键字(代码片段)

本文介绍了Threadlocal、volatile、condition、Semaphore、CountDownLatch、unsafe等关键字目录如下:Threadlocal本地线程volatileconditionCountDownLatch闩锁CyclicBarrier篱栅Semaphore信号灯unsafe魔法类StampedLock新读写锁1.Threadlocal从名字我们 查看详情

设计模式我终于读懂了代理模式。。。(代码片段)

文章目录👦代理模式的基本介绍👧代理模式示意图👩静态代理👨应用实例👶思路分析图解(类图)👵静态代理优缺点👴动态代理👱JDK中生成代理对象的API👲动态代理应用实例👳思路图解(... 查看详情

任务调度框架quartz一文读懂(代码片段)

1、Quartz简介Quartz是OpenSymphony开源组织在Jobscheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer,Quartz增加了很多功能:持久性作业-就是保持调度定时的... 查看详情

任务调度框架quartz一文读懂(代码片段)

1、Quartz简介Quartz是OpenSymphony开源组织在Jobscheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer,Quartz增加了很多功能:持久性作业-就是保持调度定时的... 查看详情

一文读懂c++虚继承的内存模型(代码片段)

一文读懂C++虚继承的内存模型1、前言2、多继承存在的问题3、虚继承简介4、虚继承在标准库中的使用5、虚继承下派生类的内存布局解析6、总结1、前言C++虚继承的内存模型是一个经典的问题,其具体实现依赖于... 查看详情

makefile入门(超详细一文读懂)(代码片段)

1、Makefile编译过程  Makefile文件中的命令有一定规范,一旦该文件编写好以后在Linux命令行中执行一条make命令即可自动编译整个工程。不同厂家的make可能会稍有不同,并且语法上也有区别,不过基本思想都差不多&#x... 查看详情

elasticsearch一文读懂(代码片段)

目录1、Elasticsearch简介2、Docker安装 Elasticsearch2.1使用Docker安装ElasticSearch7.6.22.2Elasticsearch目录详解2.3 使用Docker安装elasticSearch--head通过Chrome插件安装ElasticSearch-head  2.4了解ELKELK功能结构图Docker安装Kibana 3ElasticSe 查看详情