htttpinvoker管中窥豹近期,借着修复一个遗留系统的bug的机会,自己对httpinvoker长期存在的一个问题进行了
htttpinvoker管中窥豹
近期,借着修复一个遗留系统的bug的机会,自己对httpinvoker长期存在的一个问题进行了相关的技术调研,在这里记录下来。
?
问题:httpinvoker客户端和服务器端同时部署了一个接口包,接口包中包含了服务器端提供的接口类和返回对象的类,当我改变服务器端返回类的时候,客户端使用老的接口访问httpinvoker服务,会产生什么现象?
?
答案:看情况。
?
解释:
1.private static final long serialVersionUID
?
+ "\nNationality:" + nationality + "\n\n";
}
}
而后我们要实例化一个Person对象,把我们实例化出来的这个对象序列化,并把序列化后的对象流保存到文件中去:
WritePerson.java
Java代码
package com.lanber.serial;
import java.io.*;
public class WritePerson {
public static void main(String[] args) {
try {
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("Person.tmp"));
Person obj = new Person("Tony", 25, "Female", "han");
System.out.println("将Person对象写入到文件Person.tmp中...");
oos.writeObject(obj);
oos.close();
System.out.println("完成!");
} catch (Exception e) {
System.out.println(e);
}
}
}
这样,我们就把序列化后的二进制对象流保存到Person.tmp的文件中去了(特别要注意这里的oos.writeObject()方法,这个方法就是实现把obj对象序列化的方法)。你可以打开生成的这个文件查看,不过我们是看不懂这里面的信息的,因为这是二进制数据啊。
那我们要怎么才能看懂这些二进制数据呢?这就要用到我们的反序列化了。反序列化就是要把序列化后的二进制数据反向解析成可以正常使用的Java对象。现在让我们来把刚才的那个对象反序列化一下吧:
ReadPerson.java
Java代码
package com.lanber.serial;
import java.io.*;
public class ReadPerson {
public static void main(String[] args) {
Person obj = null;
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"Person.tmp"));
obj = (Person) ois.readObject();
System.out.println(obj);
ois.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
要注意一下这个语句:
Java代码
obj = (Person) ois.readObject();
就是通过这个语句把序列化后的对象流反序列化成正常的Java对象的。
?
?自己做了一个列子:
?
sr #com.sp.invoker.client.serial.ClassA L datet Ljava/util/Date;L idt Ljava/lang/Integer;L namet Ljava/lang/String;xpsr java.util.Datehj?KYt xpw 0洐rxsr java.lang.Integer鉅亣8 I valuexr java.lang.Number啲?斷? xp t hell
?
?
里边有些乱码,但是从可见的一些信息上我们可以看到,有全类名称,有serialVersionUID,有属性描述,属性值信息,并没有方法信息。
?
3.Jvm中的方法:
?
?
?写道当JVM使用类装载器装载某个类时,它首先要定位对应的class文件,然后读入这个class文件,最后,JVM提取该文件的内容信息,并将这些信息存储到方法区,最后返回一个class实例。上面是对类的装载过程作了个简单的描述,看了上面一段文字,也许你会问:方法区是什么?里面存了哪些内容?下面我们将对方法区作一个详细的描述。
方法区是什么?有哪些特点?
方法区是系统分配的一个内存逻辑区域,是用来存储类型信息的(类型信息可理解为类的描述信息)。方法区主要有以下几个特点:
一.方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待
二.方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。
三.方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集
方法区里存放的是哪些内容?
方法区里存的都是类型信息,也就是类的信息,而类的信息又包括以下内容:
类的全限定名(类的全路径名)
类的直接超类的全限定名(如果这个类是Object,则它没有超类)
这个类是类型(类)还是接口
类的访问修饰符,如public、abstract、final等
所有的直接接口全限定名的有序列表(假如它实现了多个接口)
常量池
字段、方法信息、类变量信息(静态变量) 装载该类的装载器的引用(classLoader)、类型引用(class)
其实,我们没必要全部记住,只要根据上面内容有个大概的了解,然后对类型这个概念有个大概的认识即可。下面我们将主要对常量池和类变量信息作一下分析。
先说类变量吧,类变量内容少些,描述起来比较容易。类变量,顾名思义,就是属于类的变量,所有类的实例都共享的变量,也就是常说的静态变量。关于类变量,我们只要知道方法区里有个静态区,静态区是专门用来存放静态变量以及静态块的。所有类的实例都共享方法区中的内容。访问类变量的方式可通过实例(对象)来访问,也可通过类型来直接访问,java规范推荐使用类型来直接访问。
?
?
类的方法信息是记录在Jvm方法取里的,java序列化对象的时候,一是将堆中的对象进行了记录,二是记录了类的基本信息(全类名、UID)
?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
?
?
综上所述当服务器端类改变以后,对象会根据相应信息序列化,到客户端以后首先会验证对象的类型信息(全类名、UID)验证失败则会抛出异常,如果验证通过,则会在客户端jvm中还原这个对象,客户端所加载的类依然是本地的方法信息。
?
?
?
以下是几个的结果,了解原理后,结果就顺理成章了:
?
1.UID使用eclipse默认的1L:
a.改变服务器端类方法:客户端调用是本地类的方法
? ? ? ? b.为服务器端类增加属性并赋值:客户端无法看到新属性,调用正常
c.服务器端类减少属性:客户端调用正常,减少的属性值为null
2.服务器端UID使用eclipse generate出来的随机UID,客户端使用老得:
直接抛出版本不匹配异常
3.ClassA,ClassB具有完全相同的属性方法,UID,序列化ClassA的对象以后,反序列化强制转换为ClassB
抛类型转换异常
?
?
?
?
?
?