Java 静态代理&动态代理学习

0x00 前言

最近刚好把之前的webgoat走完了,浅显的知道了一些漏洞存在的原因,在继续下一阶段java代码审计的过程中深知自己基础非常不扎实,又在K0rz3n师傅在博客中范型和动态规划是贯穿框架的非常重要的东西,所以便来学习一下(之前一个月前看的懵逼,现在重新来看看发现思路清晰了不少)

0x01 代理模式

代理应该不陌生,我们在科学上网的时候经常听到这个名字,我们在科学上网的时候通过正向代理来访问网站,这时候代理服务器能较好的藏匿我们的客户端,这样服务端就不知道是哪台客户端发起的请求

我们来看一下java中是怎么实现的

上面我们的客户端就是java的委派类,代理服务器就是我们的代理类,这么说有可能有点绕,举个例子:

现在的微商其实就很相似,厂商的商品发到我们的微商也就是我们的代理商来销售,这里厂商就是委派类,微商就是代理类

优点如下:

  1. 能隐藏委派类的实现 (这里可以先理解成我们的客户端,后面会说)
  2. 能在不修改委派类代码的进行额外的处理,同时能起到很好的解耦,不然项目一大一个地方改很多地方都要改

0x02 静态代理

公司要我们写一个类来实现一个接口,但是过了一段时间发现,之前写的功能太少了,要求我们再加一些功能。

此时我们不可能再去修改原先的类,太过于麻烦,这是我们可以写一个代理类,引入我们之前的类然后在代理类中添加我们需要添加的方法(虽然我觉得静态代理还是很麻烦)

接下来我们来看一下静态代理是如何实现的

用户接口:

public interface UserServices {
    void getName(String name);
}

我们编写一个实现类UserRealm

public class UserRealm implements UserServices{
  public void getName(String name){
     System.out.println(name);
  }
}

接下来我们要编写我们的代理类,在原本输出名字的基础上增加功能

UserRealmProxy

class UserRealmProxy implements UserServices{
  private UserServices userservices;

  public UserRealmProxy(UserServices userservices){
    this.userservices = uservices;
  }

  @Override
  public void getName(String name){
    System.out.println("UserRealmProxy Start....");
    this.userservices.getName();
    System.out.println("UserRealmProxy End....")
  }
}

接下来我们主要来解释一下上面代理类的代码

private UserServices userservices;

这里定义的是一个接口变量,个人理解为变量userservices添加一个规范,即引入的变量要符合UserServices接口的规范,这里还未对userservices进行赋值,如要赋值还需要进行如下一行代码

UserServices userservices = new UserRealm();

这里代理类引入的是要被代理的对象,然后要被代理的对象是实现UserServices接口规范的,所以我们代理类中传入的对象也应该要符合我们UserServices接口的规范

public UserRealmProxy(UserServices userservices){
    this.userservices = uservices;
  }

这里就是一个构造方法,引入我们需要代理的对象

@Override
  public void getName(String name){
    System.out.println("UserRealmProxy Start....");
    this.userservices.getName(name);
    System.out.println("UserRealmProxy End....")
  }

这里主要是实现了对接口方法的重写,在这个函数中我们实现了我们功能的添加,我们在被代理对象也就是委托类执行getName方法前后分别执行了两句话

image-20201122141912944

中间这行是我们委托类执行的方法

this.userservices.getName(name);

执行函数如下

class Main{
    public static void main(String[] args) {
        // 实现一个类实例 userservices
        UserServices userServices = new UserRealm();
        UserRealmProxy proxy = new UserRealmProxy(userServices);
        proxy.getName("Frank");
    }

从上面的代码我们可以看到我们将实例化的对象直接传入我们的代理类中,然后我们就可以通过直接调用代理类的方法来执行我们添加完功能之后的getName(在代理类的getname方法中会调用我们委派类的方法)

总结一下,静态代理其实就是通过写一个代理类,将我们的委派类作为对象进行一个传入,然后利用代理类中引用委派类的时候添加我们自己的方法,但是仔细想想其实还是很麻烦的如果换了一个我们就不得不重新进行编写,所以动态代理孕育而生

0x03 动态代理

动态代理如同字面意思,我们的代理类是动态生成的,我们回看上面我们说的静态代理会发现静态代理中的代理类都是要我们人为进行编写的,这在一定程度上还是比较麻烦的有的时候我们不得不重新写一个代理类去实现,所以动态代理能更加方便我们在实际中的运用,能起到很好的解耦的作用

动态代理和静态代理还有一个区别就是,动态代理多了一个中介类,后面会详细讲解

还是一样的我们首先写一个类来实现我们的接口 这个还是和之前静态代理的一样

class UserReal implements UserServices{
    @Override
    public void getName(String name){
        System.out.println(name);
    }
}

下面就是比较重要的中介类了

中介类

首先中介类必须要实现InvocationHandler接口

class Delegate implements InvocationHandler{

    private Object object;

    public Delegate(Object object){
        this.object = object;
    }
    /**
     * 重写我们接口中的方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Delegate Start.....");
        Object res = method.invoke(object,args);
        System.out.println("Delegate End.....");
        return res;
    }
}

从上面的代码中看到在我们的中介类中持有一个委托类的对象引用,在invoke方法中调用我们委托类的方法

image-20201122154538010

我们这里复写了我们InvocationHandler接口的方法

这是我们接口中的方法

image-20201122154700735

其实仔细查看代码不难发现,中介类和委托类的代码非常像上面静态代理中委托类和代理类

实际上,中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;

代理类与中介类也构成一个静态代理关系,在这个关系中,中介类是委托类,代理类是代理类。

也就是说,动态代理关系由两组静态代理关系组成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。

代理类

下面我们会用到Proxy类进行动态的生成我们的代理类

public class DynamicTest {
    public static void main(String[] args) {
        UserReal user = new UserReal();
        // 创建我们的中介类
        Delegate delegate = new Delegate(user);
        //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

        UserServices userServices = (UserServices) Proxy.newProxyInstance(UserServices.class.getClassLoader(), new Class[] {UserServices.class},delegate);

        userServices.getName("KpLi0rn");
    }
}

首先我们创建我们的实现类和中介类

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件

image-20201122155855830

这一行是动态生成我们的代理类的 非常重要

UserServices userServices = (UserServices) Proxy.newProxyInstance(UserServices.class.getClassLoader(), new Class[] {UserServices.class},delegate);

首先规定了我们返回的对象要实现的接口规范,由于返回的对象是Object所以我们需要进行类型的转换 ,然后调用了Proxy类中的newProxyInstance方法进行了代理类的动态创建,发现该方法有三个参数 分别要传入我们的类加载器,代理类实现的接口列表和我们的中介类

image-20201122160110731

ClassLoader loader: 定义了这个新的类的加载器,我们知道每一个类在 javac 编译器编译后都会从 .java 文件转化成对应的 .class 文件(这在我们的反射机制中是比较重要的部分),加载器的作用就是将 .class 文件中的 虚拟机指令转化成对应的类的字节码

Class<?>[] interfaces: 用来指明生成的代理对象要实现的接口,这里其实是通过反射的方法去获取的,获取我们传入的类型和我们的名字

image-20201122160528502

InvocationHandler h: 这里传入的就是我们的中介类

最终执行效果如下

image-20201122160652701

0x04 参考链接

https://juejin.cn/post/6844903591501627405#heading-1

https://juejin.cn/post/6844903744954433544#heading-0

https://www.k0rz3n.com/2019/04/20/JAVA%20%E6%B3%9B%E5%9E%8B%E3%80%81%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%8A%80%E6%9C%AF%E8%A6%81%E7%82%B9%E6%A2%B3%E7%90%86/#1-%E4%BB%80%E4%B9%88%E6%98%AF%E4%BB%A3%E7%90%86

点赞
  1. ruozhi说道:

    木👴永远滴神

    1. 天下大木头说道:

      别打了别打了 人都傻了

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像