设计模式-动态代理

目录

定义

代理模式的优缺点

优点

缺点

应用场景

静态代理

动态代理 

相关资料


定义

        代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色。代理类负责代理真实类,为真实类提供控制访问的功能,真实类则完成具体的业务逻辑。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

这样理解其实有点抽象,现实生活中举几个例子 :

  • 明星宣传

    假如要找一个明星来宣传某一个品牌,那么我们一般不会直接接触到明星,一般是找他的经纪人来商量,这样的话其实经纪人就算是一个代理

  • 游戏代练

    一般比如玩某些传奇等游戏,里面需要打怪才能升级,这个过程比较繁琐,就会找代练来帮助完成,这样代练就是一个代理

这种还有比如租房中介等等

代理一般分为两类,静态代理和动态代理 

  • 静态代理

静态代理是指代理类在编译期就已经确定,即需要事先手动编写一个代理类。

  • 动态代理

    动态代理则是在运行时动态生成代理类 (可以通过继承和实现接口两种方式分别来实现静态和动态,这里就都用接口实现的方式来演示,动态代理方案有两种实现:其一,通过Java本身自带 java.lang.reflect.Proxy 类来实现动态代理功能 。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类 。二者底层实现上都应用了反射和操作字节码技术。 )

    • JDK动态代理

      JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。

    • CGlib

      CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类作为真实类的子类去代理父类,即代理类继承自真实类。这种方式不需实现接口,可以作为JDK代理方式的补充方案。

代理模式的优缺点

优点

  • 代理对象可以隐藏原始对象的实现细节,使得客户端无需了解原始对象的具体实现。

  • 代理对象可以在原始对象的基础上添加额外的功能,例如缓存、安全验证、日志、权限验证等功能。

  • 代理对象可以控制对原始对象的访问,保护原始对象不被非法访问。

  • 代理对象可以在客户端和原始对象之间起到中介作用,使得客户端与原始对象之间的耦合度降低。

缺点

  • 引入代理类会增加系统的复杂性,增加了学习和理解的成本。

  • 由于增加了代理层,导致请求处理速度变慢。

应用场景

  • 日志记录代理:通过代理模式,我们可以在真实对象的方法执行前后进行日志记录,以实现日志记录、调试和性能监测等功能。

  • 缓存代理:代理模式可以用于实现对象的缓存,当客户端请求某个对象时,代理对象先检查缓存中是否存在该对象,如果存在则直接返回,否则创建新对象并缓存起来,从而提高系统性能。

  • 权限校验:某个用户是否具有访问某个特定功能的权限,可以在请求处理前让代理对象先检查一下是否拥有该方法的权限,没有则拒绝,有则可以访问

下面就通过传奇游戏代练的场景来实现下没有代理模式是怎么样的,以及有了代理模式发生了哪些变化

我们想一下传奇游戏一般玩家都会干些什么,登录,打怪,升级 , 将这些事情抽象成一个游戏玩家接口 (IGamePlayer):

public interface IGamePlayer {
    /**
     * 登录
     * @param userName
     * @param password
     */
    void login(String userName,String password);

    /**
     * 打怪
     */
    void killBoss();

    /**
     * 升级
     */
    void upgrade();
}

然后创建一个普通的玩家 (GamePlayer) 真实类

public class GamePlayer implements IGamePlayer {
    //用户名
    private String name;

    public GamePlayer(String name) {
        this.name = name;
    }

    public GamePlayer() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void login(String userName, String password) {
        System.out.println("登录名为:"+userName+"的用户登录成功");
    }

    public void killBoss() {
        System.out.println(this.name+"在打怪");
    }

    public void upgrade() {
        System.out.println(this.name+"升级成功!");
    }
}

此时类图结构如下 :

这是没有代理模式的情况

public class Main {
    public static void main(String[] args) {
        //原始,zhangsan只能自己打游戏
        GamePlayer gamePlayer = new GamePlayer("zhangsan");
        gamePlayer.login("zhangsan","123");
        gamePlayer.killBoss();
        gamePlayer.upgrade();
	}
}

结果:

登录名为:zhangsan的用户登录成功
zhangsan在打怪
zhangsan升级成功!

静态代理

        我们此时假如叫了一个代练,让代练除了帮我打升级以外,还得在开始代练前后通知我一下,让我别挤你的账号,创建一个代理类,继承游戏玩家接口GamePlayerProxy (代理类)

public class GamePlayerProxy implements IGamePlayer {
    //被代理的目标对象
    private GamePlayer gamePlayer;

    //通过构造传入进来
    public GamePlayerProxy(GamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    public GamePlayerProxy() {
    }

    public GamePlayer getGamePlayer() {
        return gamePlayer;
    }

    public void setGamePlayer(GamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    //对下面方法进行代理
    public void login(String userName, String password) {
        System.out.println("通知用户代练,login开始了");
        this.gamePlayer.login(userName,password);
        System.out.println("通知用户代练,login结束了");
    }

    public void killBoss() {
        System.out.println("通知用户,代练killBoss开始了");
        this.gamePlayer.killBoss();
        System.out.println("通知用户,代练killBoss结束了");
    }

    public void upgrade() {
        System.out.println("通知用户,代练upgrade开始了");
        this.gamePlayer.upgrade();
        System.out.println("通知用户,代练upgrade结束了");
    }
}

此时类图结构 :

这样的话我自己就其实不需要打游戏了

public class Main {
    public static void main(String[] args) {
        //原始,zhangsan只能自己打游戏
//        GamePlayer gamePlayer = new GamePlayer("zhangsan");
//        gamePlayer.login("zhangsan","123");
//        gamePlayer.killBoss();
//        gamePlayer.upgrade();

        //zhangsan找了代练
        GamePlayer gamePlayer = new GamePlayer("zhangsan");
        GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(gamePlayer);
        gamePlayerProxy.login("zhangsan","123");
        gamePlayerProxy.killBoss();
        gamePlayerProxy.upgrade();
	}
}

结果 :

通知用户代练,login开始了
登录名为:zhangsan的用户登录成功
通知用户代练,login结束了
通知用户,代练killBoss开始了
zhangsan在打怪
通知用户,代练killBoss结束了
通知用户,代练upgrade开始了
zhangsan升级成功!
通知用户,代练upgrade结束了

        这其实就是静态代理,创建一个类,实现指定目标对象的接口,然后重写方法,内部会保存目标对象,然后重写的方法中会去调用目标对象的方法,前后可以干些非主业务的事情

静态代理的缺点其实比较明显 , 主要有以下几点 :

  • 代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理,这在程序规模稍大时就无法胜任了。

  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,这增加了代码维护的复杂度。

  • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法,这样就出现了大量的代码重复。

下面我们来看下动态代理是如何解决这些问题的

动态代理 

        JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:

  1. 创建InvocationHandler接口的代理类工厂:在调用Proxy类的静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。

  2. InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke方法,并将被调用的方法名、参数等信息传递给该方法。

  3. 调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。

  4. invoke方法调用:在invoke方法中,会再通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。

代理类工厂 :

public class EnhanceHandler implements InvocationHandler {
    //目标对象
    private Object obj;

    public EnhanceHandler(Object obj){
        this.obj = obj;
    }

    //获取jvm在内存中生成的代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                this);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        System.out.println("通知用户,代练" + methodName +"开始了");
        method.invoke(obj,args);
        System.out.println("通知用户,代练" + methodName + "结束了");
        return null;
    }
}
public class Main {
    public static void main(String[] args) {        
EnhanceHandler enhanceHandler = new EnhanceHandler(new GamePlayer("zhangsan"));
        IGamePlayer proxy = (IGamePlayer) enhanceHandler.getProxy();
        proxy.login("zhangsan","123");
        proxy.killBoss();
        proxy.upgrade();
    }
}

结果如下 :

通知用户,代练login开始了
登录名为:zhangsan的用户登录成功
通知用户,代练login结束了
通知用户,代练killBoss开始了
zhangsan在打怪
通知用户,代练killBoss结束了
通知用户,代练upgrade开始了
zhangsan升级成功!
通知用户,代练upgrade结束了

此时类图结构如下 :

其实这里 Proxy会在运行时动态生成IGamePlayer的实现类然后重写方法,然后我们调用代理对象方法的时候就会先到继承了InvocationHandler接口对象的invoke方法中,内部会再次调用真实对象的方法

流程图 : 

PS : 默认情况下JVM是不保存动态创建代理类字节码对象的,可以在main方法中配置代理参数让字节码保留

//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

执行完之后,会在项目根目录生成代理类字节码对象。

JVM运行时生成的代理类对象

public final class $Proxy0 extends Proxy implements IGamePlayer {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m5;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void upgrade() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void killBoss() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("upgrade");
            m5 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("killBoss");
            m4 = Class.forName("com.changjunkai.designmode.proxyPattern.service.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

相关资料

        设计模式之禅第二版

  • 29
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值