前言

php 审计的同时,java 不能搁置呀 : )

序列化与反序列化

php 一样,java 中也存在序列化与反序列化。通过该机制,可以将对象当前的状态转换成字节序列,并存储在内存、文件、数据库中,便于传输。当下次想使用该对象时,只需要从相应的地方将其反序列化,把字节序列还原成对象即可。

序列化:对象 –> 字节序列(–> 存储空间)

反序列化:(存储空间 –>)字节序列 –> 对象

以将对象序列化后存入文件为例,简单演示下序列化与反序列化的过程。

首先需要创建一个文件,可以通过输出流 FileOutputStream 向文件中写入数据,

01

实际上,上面 FileOutputStream 的第二种直接传入文件名的构造方法,在内部创建了 File 对象。

02

这两种写文件的方式都会覆盖原文件,如果要向文件追加数据,可以传入第二个参数,为 true 则表示追加数据。

1
2
3
4
public FileOutputStream(File file)
public FileOutputStream(File file, boolean append)
public FileOutputStream(String name)
public FileOutputStream(String name, boolean append)

和输出流相对的就是文件的输入流,其对应的类为 FileInputStream。

1
2
public FileInputStream(File file)
public FileInputStream(String name)

在序列化与反序列化中,当然少不了 ObjectOutputStream 和 ObjectInputStream,这两个是实现该机制的关键类。ObjectOutputStream 可以将一个对象转换成二进制的字节流,其构造方法需要传入一个 OutputStream 类,表示将对象二进制流写入到指定的 OutputStream 中。

1
public ObjectOutputStream(OutputStream out)

我这里用的是 OutputStream 的子类 FileOutputStream。

03

指定了 OutputStream 之后,调用 ObjectOutputStream 类的 writeObject() 方法,即可将对象序列化,并写入指定输出流中。

1
public final void writeObject(Object obj)

反序列化和序列化大致相同,只需调用 ObjectInputStream 的 readObject() 方法,即可从指定输入流中读取字节序列,并反序列化成相应的对象。

1
public final Object readObject()

readObject() 方法返回的是 Object 类型变量,需要对返回值进行向下转型成实际的类型。

接下来,我们看一个简单的示例。

04

在该项目的目录下,我们生成了一个 ll.obj 文件,将 String 对象序列化后存入该文件中,并调用 readObject() 将该对象反序列化成 String,打印原本的值。

05

ll.obj 文件的前 5 个字节aced0005是 java 序列化内容的特征。

参考:https://xz.aliyun.com/t/3847#toc-1

根据上面的示例,我们简单地了解了序列化与反序列化的过程,其中还需要注意以下几点:

  1. 需要序列化的类必须实现 Serializable 接口,或者实现 Externalizable 接口。
  2. 序列化不会保存静态变量,即 static 关键字标识的属性。
  3. 当父类实现了 Serializable 接口时,子类自动实现该接口。如果父类没有实现 Serializable 接口,则父类的所有属性不会被序列化,并且反序列化时会调用父类的默认构造方法来初始化父类属性,而子类直接从流中恢复属性的值。
  4. 如果反序列化后的类的 serialVersionUID 和流中对象的 serialVersionUID 不同,则会抛出异常。
  5. 序列化会将一个类包含的引用中所有的成员变量保存下来(深度复制),所以里面的引用类型也必须要实现 Serializable 接口。
  6. 序列化不会保存被 transient 修饰的变量。
  7. 每个类可以自己实现 readObject()、writeObject() 方法,自定义自己的序列化策略,即使是 transient 修饰的成员变量,也可以手动调用 ObjectOutputStream 的 writeInt 等方法将其序列化。

在前面的示例中,我们序列化了 String 对象,该对象实现了 Serializable 接口。

06

所以,如果我们想要序列化某个类,只需要让其实现 Serializable 接口即可。

07

我们可以自定义一个 Person 类,并将其对象序列化写入文件。

08

根据前面分析,我们也可以让需要序列化的类实现 Externalizable 接口。

1
2
3
4
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

Externailzable 是 Serializable 的子类,其中定义了 writeExternal() 和 readExternal() 这两个方法,需要自己实现序列化和反序列化的读写操作,可以保存 transient 修饰的变量,且必须包含一个无参数构造方法(默认隐式的也可以)。

09

在前面提到,我们可以实现 readObject() 和 writeObject() 方法自定义反序列化策略。在对象反序列化时,会调用相应类的 readObject() 方法,我们可以实现该方法,并在其中添加弹计算器的代码。

10

这和 php 中的魔术方法__wakeup()类似,在对象被反序列化的时候会调用__wakeup()方法,可以在其中做一些对象的初始化操作。

RMI

rmi,远程方法调用(Remote Method Invocation),支持存储与不同地址空间的程序级对象之间彼此通信,实现远程对象间的远程调用。该实现依赖于 jvm,可用于不同 java 虚拟机(jvm)之间的通信,即一个虚拟机中的对象调用另一个虚拟机中的对象的方法,这些虚拟机可以运行在同一个主机上,也可以在不同主机上。

引用参考文章的 rmi 流程图

11

java rmi 主要涉及三个部分,client、server、rmi registry。server 端首先编写一个远程接口,该接口需要实现 Remote 接口,client 可以根据此接口调用其中定义的方法。接着 server 端编写一个该远程接口的实现类,该服务实现类需要继承自 UnicastRemoteObject,以便 client 端获得 stub。然后,server 端将该服务实现类注册到 rmi registry,客户端要调用远程对象的方法时,直接从 rmi registry 中取出该对象的 stub,通过 stub 和 skeleton 通信将 server 端的执行结果返回给 client 端,从而实现远程方法调用。具体内容可以参考最后三篇参考文章。

下面根据参考文章总结下 java.rmi 包。

Remote

远程实现类需要继承 Remote 接口,只有继承了 Remote 接口中的方法才可以被远程调用。

RemoteException

RemoteException 是所有在远程调用中所抛出异常的超类,所有能被远程调用的方法声明中,都需要处理该异常。

Naming

提供向注册中心 rmi registry 保存远程对象引用,或者从注册中心获取远程对象引用的方法,这个类中的方法是静态的,每一个方法都包含了一个类型为 String 的 name 参数,这个参数是 url 格式,如 xx://hsot:port/name。

Registry

一个接口,其功能和 Naming 类似,每个方法都有一个 String 类型的 name 参数,但是 name 不是 url 格式的,而是远程对象的一个命名。Registry 的实例可以通过方法 LocateRegistry.getRegistry() 获得。

LocateRegistry

用于获取到注册中心的一个连接,这个连接可以用于获取一个远程对象,也可以创建一个注册中心。

RemoteObject

重写覆写了 Object 对象中的 equals、hashCode、toString 方法,从而可以用于远程调用。

UnicastRemoteObject

用于从 server 中导出一个远程对象,并获得一个 stub。这个 stub 封装了底层细节,用于和远程对象进行通信。

Unreferenced

一个接口,声明了方法 void unreferenced(),如果一个远程对象实现了此接口,则这个远程对象在没有任何客户端引用的时候,这个方法就会被调用。

可以看到 Registry 和 Naming 类似,我们可以直接使用 Registry 实现 rmi。

  • 继承了 Remote 接口的远程接口 RIHello。

12

  • 实现远程接口的服务实现类 RIHelloImpl,父类为 UnicastRemoteObject。

13

  • server 端

14

  • client 端

15

先启动 server 注册服务,然后启动 client 即可实现 rmi。

16

同理,我们也可以用 Naming 实现 rmi。只需简单修改 server 端和 client 端代码即可。

  • server 端

17

  • client 端

18

通过 Naming 的 lookup() 方法和 Registry 效果一样。

19

可以看一下 Naming 的 lookup() 的实现。

20

实际上是对 Registry 的一个封装。

总结

java web 的东西很多,但也很有趣,慢慢学叭 : )

参考

ref :

https://baijiahao.baidu.com/s?id=1600984799323133994&wfr=spider&for=pc

https://blog.csdn.net/abc123lzf/article/details/82318148

http://www.lmxspace.com/2019/03/11/Java-web%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF-%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/

https://www.smi1e.top/java%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e5%9f%ba%e7%a1%80/

https://xz.aliyun.com/t/3847#toc-5

https://blog.csdn.net/qq_28081453/article/details/83279066

https://segmentfault.com/a/1190000016598069?utm_medium=referral&utm_source=tuicool

https://www.jianshu.com/p/de85fad05dcb