`

使用序列化类的私有方法巧妙解决部分属性持久化问题

 
阅读更多

 

来源:http://book.51cto.com/art/201202/317465.htm

部分属性持久化问题看似很简单,只要把不需要持久化的属性加上瞬态关键字(transient关键字)即可。这是一种解决方案,但有时候行不通。例如一个计税系统和人力资源系统(HR系统)通过RMI(Remote Method Invocation,远程方法调用)对接,计税系统需要从HR系统获得人员的姓名和基本工资,以作为纳税的依据,而HR系统的工资分为两部分:基本工资和绩效工资,基本工资没什么秘密,根据工作岗位和年限自己都可以计算出来,但绩效工资却是保密的,不能泄露到外系统,很明显这是两个相互关联的类。先来看薪水类Salary类的代码:

 

public class Salary implements Serializable{  
     private static final long serialVersionUID = 44663L;     
    //基本工资  
     private int basePay;  
     //绩效工资  
     private int bonus;  

     public Salary(int _basePay,int _bonus){  
           basePay = _basePay;  
           bonus = _bonus;  
     }  
     /*getter/setter方法省略*/  

} 
 

 

 

Peron类与Salary类是关联关系,代码如下:

 

public class Person implements Serializable{  
     private static final long serialVersionUID =60407L;  
     //姓名  
     private String name;  
     //薪水  
     private Salary salary;  

     public Person(String _name,Salary _salary){  
           name=_name;  
           salary=_salary;  
     }    
     /*getter/setter方法省略*/  
} 
 

 

 

这是两个简单的JavaBean,都实现了Serializable接口,都具备了持久化条件。首先计税系统请求HR系统对某一个Person对象进行序列化,把人员和工资信息传递到计税系统中,代码如下:

 

public class Serialize {  
     public static void main(String[] args) {  
           //基本工资1000元,绩效工资2500元  
           Salary salary = new Salary(1000,2500);  
           //记录人员信息  
           Person person = new Person("张三",salary);  
           //HR系统持久化,并传递到计税系统  

           SerializationUtils.writeObject(person);  
    }  
} 
 

 

 

在通过网络传送到计税系统后,进行反序列化,代码如下:

public class Deserialize {  
     public static void main(String[] args) {  
           //技术系统反序列化,并打印信息  
           Person p = (Person)SerializationUtils.readObject();  
           StringBuffer sb = new StringBuffer();  
           sb.append("姓名:" + p.getName());  
           sb.append("\t基本工资:" + p.getSalary().getBasePay());  
           sb.append("\t绩效工资:" + p.getSalary().getBonus());  
           System.out.println(sb);  
     }  
} 
 

 

打印出的结果很简单:

  1. 姓名:张三   基本工资:1000   绩效工资:2500。 

但是这不符合需求,因为计税系统只能从HR系统中获得人员姓名和基本工资,而绩效工资是不能获得的,这是个保密数据,不允许发生泄露。怎么解决这个问题呢?你可能马上会想到四种方案:

(1)在bonus前加上transient关键字

这是一个方法,但不是一个好方法,加上transient关键字就标志着Salary类失去了分布式部署的功能,它可是HR系统最核心的类了,一旦遭遇性能瓶颈,想再实现分布式部署就不可能了,此方案否定。

(2)新增业务对象

增加一个Person4Tax类,完全为计税系统服务,就是说它只有两个属性:姓名和基本工资。符合开闭原则,而且对原系统也没有侵入性,只是增加了工作量而已。这是个方法,但不是最优方法。

(3)请求端过滤

在计税系统获得Person对象后,过滤掉Salary的bonus属性,方案可行但不合规矩,因为HR系统中的Salary类安全性竟然让外系统(计税系统)来承担,设计严重失职。

(4)变更传输契约

例如改用XML传输,或者重建一个 Web Service服务。可以做,但成本太高。

可能有读者会说了,你都在说别人的方案不好,你提供个优秀的方案看看!好的,这就展示一个优秀的方案。其中,实现了Serializable接口的类可以实现两个私有方法:writeObject和readObject,以影响和控制序列化和反序列化的过程。我们把Person类稍做修改,看看如何控制序列化和反序列化,代码如下:

public class Person implements Serializable{  
     private static final long serialVersionUID =60407L;  
     //姓名  
     private String name;  
     //薪水  
     private transient Salary salary;  
     public Person(String _name,Salary _salary){  
           name=_name;  
           salary=_salary;  
     }    
     //序列化委托方法  
     private void writeObject(java.io.ObjectOutputStream out)
                  throws IOException {  
           out.defaultWriteObject();  
           out.writeInt(salary.getBasePay());  
     }  
     //反序列化时委托方法  
     private void readObject(java.io.ObjectInputStream in) 
                          throws IOException,ClassNotFoundException {  
        in.defaultReadObject();  
        salary = new Salary(in.readInt(),0);  
    }  
} 
 

 

其他代码不做任何改动,我们先运行看看,结果为:

  1. 姓名:张三   基本工资:1000   绩效工资:0 。 

我们在Person类中增加了writeObject和readObject两个方法,并且访问权限都是私有级别,为什么这会改变程序的运行结果呢?其实这里使用了序列化独有的机制:序列化回调。Java调用ObjectOutputStream类把一个对象转换成流数据时,会通过反射(Reflection)检查被序列化的类是否有writeObject方法,并且检查其是否符合私有、无返回值的特性。若有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化。同样,在从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,如果有,则会通过该方法读取属性值。此处有几个关键点要说明:

(1)out.defaultWriteObject()

告知JVM按照默认的规则写入对象,惯例的写法是写在第一句话里。

(2)in.defaultReadObject()

告知JVM按照默认规则读入对象,惯例的写法也是写在第一句话里。

(3)out.writeXX和in.readXX

分别是写入和读出相应的值,类似一个队列,先进先出,如果此处有复杂的数据逻辑,建议按封装Collection对象处理。

可能有读者会提出,这似乎不是一种优雅的处理方案呀,为什么JDK没有对此提供一个更好的解决办法呢?比如访问者模式,或者设置钩子函数(Hook),完全可以更优雅地解决此类问题。我查阅了大量的文档,得出的结论是:无解,只能说这是一个可行的解决方案而已。

再回到我们的业务领域,通过上述方法重构后,其代码的修改量减少了许多,也优雅了许多。可能你又要反问了:如此一来,Person类也失去了分布式部署的能力啊。确实是,但是HR系统的难点和重点是薪水计算,特别是绩效工资,它所依赖的参数很复杂(仅从数量上说就有上百甚至上千种),计算公式也不简单(一般是引入脚本语言,个性化公式定制),而相对来说Person类基本上都是“静态"属性,计算的可能性不大,所以即使为性能考虑,Person类为分布式部署的意义也不大。

分享到:
评论

相关推荐

    序列化数据序列化数据

    在C#中,实现`ISerializable`接口的示例通常涉及创建一个私有的构造函数来处理反序列化,并在`GetObjectData`方法中明确指定要序列化的字段。这在需要特殊处理的复杂对象或敏感数据时特别有用。 总之,序列化是.NET...

    序列化与持久化相同点跟不同点

    ### 序列化与持久化的相同点与不同点 #### 一、持久化定义与应用场景 **持久化**是指将程序中的数据(如内存中的对象)保存到可长期存储的设备上,比如硬盘或者固态硬盘等。持久化的主要目的是确保即使在程序结束...

    java 序列化时排除指定属性

    通过使用`transient`关键字、自定义序列化方法或第三方库,我们可以排除对象中不希望被序列化的属性。理解这些机制对于编写安全且高效的Java应用程序至关重要。 在学习过程中,阅读相关的文档和博客,例如提供的...

    学生管理系统(序列化和反序列化)

    在Java中,我们可以使用`ObjectInputStream`类的`readObject()`方法来完成这个操作。在学生管理系统中,当程序启动时,它会读取之前保存的字节流,通过反序列化将学生对象重新构建,这样就可以继续管理和显示学生...

    C#和Java的序列化反序列化

    而在Java中,我们可以通过实现`Serializable`接口来使类支持序列化,或者使用`java.io.ObjectOutputStream`和`java.io.ObjectInputStream`进行对象的序列化和反序列化。 接下来,我们讨论反序列化。反序列化是序列...

    序列化反序列化例题net

    - 序列化通常只对公共字段和属性生效,私有成员默认不会被序列化。 - 可以通过添加 `[Serializable]` 特性标记一个类为可序列化的。 - 对于不想序列化的字段,可以使用 `[NonSerialized]` 特性。 - 对于.NET Core,...

    序列化类的作用Serializable

    需要注意的是,实现序列化的类应该谨慎处理其内部状态,因为序列化会保存对象的所有字段,包括私有(private)和受保护的(protected)成员。如果某个类的父类没有实现`Serializable`接口,那么子类仍然可以序列化,...

    XML序列化与反序列化 实战

    这个类可能包含了一些方法,如`Serialize`(将对象序列化为XML字符串或写入XML文件)和`Deserialize`(从XML字符串或文件反序列化回对象)。 接下来,`说明.txt`文件应该提供了关于如何使用这个项目的详细指南。它...

    无法序列化会话状态问题的解决办法

    如果存储的对象由你自己创建,你可以通过在类定义前添加`[Serializable]`属性来使其可序列化。例如: ```csharp [Serializable] public class ParmXRLV { public double A { get; set; } public double S { get; ...

    .NET对象序列化与数据持久化

    .NET对象序列化与数据持久化.NET对象序列化与数据持久化.NET对象序列化与数据持久化.NET对象序列化与数据持久化.NET对象序列化与数据持久化.NET对象序列化与数据持久化(Q群号:152088818 本群只为有经验的.NET开发者...

    delphi序列化与反序列化

    在IT行业中,序列化和反序列化是两个关键的概念,特别是在对象持久化、数据传输以及存储方面。在Delphi编程环境中,这两个概念同样至关重要。本文将深入探讨Delphi中的序列化与反序列化,以及如何处理组件和结构体的...

    序列化一个类_php的类的序列化脚本_

    返回的是一个包含需要序列化属性名的数组。 ```php class MyClass { private $var1; private $var2; public function __sleep() { // 只序列化$var1 return ['var1']; } } ``` - `__wakeup()`:在反序列化...

    hessian学习基础篇——序列化和反序列化

    这对于优化自定义序列化逻辑或者解决Hessian使用中遇到的问题非常有帮助。 总结起来,Hessian是一种高效、轻量级的二进制序列化协议,特别适合于跨语言的网络通信。理解并掌握Hessian的基本概念和使用方法,能够...

    序列化与反序列化经典例子

    序列化与反序列化是计算机科学中的重要概念,特别是在数据存储、网络通信和持久化对象等领域。简单来说,序列化是将对象的状态转换为可存储或传输的数据格式的过程,而反序列化则是将这种数据格式恢复为原来的对象...

    c#序列化的例子序列化的例子

    1. 只有公共属性和字段会被序列化,私有和受保护的成员不会被包含。 2. 对于依赖关系和循环引用的对象,需要特殊处理。 3. 序列化可能导致安全问题,因此应谨慎处理敏感数据。 总结,C#序列化是开发过程中不可或缺...

    C++ JSON 序列化与反序列化

    1. 复杂类型的处理:除了基本类型外,还可以序列化和反序列化自定义类、结构体以及容器(如vector、map等)。对于自定义类型,通常需要重载`operator和`operator>>`,或者使用nlohmann/json库中的`to_json`和`from_...

    C#序列化与反序列化类-源代码

    1. **序列化类**:这个类可能有一个方法,如`Serialize(T obj, string filePath)`,用于接收一个对象和目标文件路径,然后将对象序列化并保存到文件中。它可能会使用Stream或StreamWriter来写入数据,并可能支持多种...

    C# 类序列化 文件数据库 串行化

    总结来说,这个程序利用C#的类序列化技术,提供了一种有效的方法来存储和恢复大量数据,同时优化了文件拷贝和大规模数据管理。通过理解和应用这些知识点,开发者可以创建更灵活、高效的应用程序,特别是在处理数据...

    Unity序列化和反序列XML并添加属性2

    在本篇文章中,我们将会讲解如何使用 Unity 序列化和反序列 XML,并添加属性来实现游戏中的数据存储和读取。 一、Unity 序列化 Unity 序列化是指将游戏中的数据转换为 XML 格式的字符串,以便存储到文件中。 在 ...

    Java序列化_Java序列化结构_

    Java序列化是Java平台中的一种持久化机制,它允许对象的状态被转换成字节流,以便存储、网络传输或在不同时间点恢复。这个过程被称为序列化,而反向操作称为反序列化。序列化在许多场景下都非常有用,比如在分布式...

Global site tag (gtag.js) - Google Analytics