zoukankan      html  css  js  c++  java
  • Java使用序列化的私有方法巧妙解决部分属性持久化问题

    部分属性持久化问题看似很简单,只要把不需要的持久化的属性加上瞬态关键字(transient关键字)即可,没错,这也是一种解决方案,但在有的时候行不通,例如在一个计税系统和人力系统对接的时候,计税系统需要从人力系统获得人员的姓名和基本工资,作为纳税的一句,而人力系统的工资分成 分成两个部分:基本工资和绩效工资,基本工资没有什么秘密,一般都是直接跟年限挂钩,但是绩效工资一般来说是保密的,不能泄露到外系统,话不多说,上代码

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    import java.io.Serializable;
    
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Salary implements Serializable {
        private static final long serialVersionUID = 75632L;
        // 基本工资
        private Integer basePay;
        // 绩效工资
        private Integer bonus;
    }
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    import java.io.Serializable;
    
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Person implements Serializable {
        private static final long serialVersionUID =45829L;
        // 员工姓名
        private String name;
        // 员工薪资
        private Salary salary;
        
    }

    如上所示,他们都序列化了,都基本了持久化的条件,计税系统请求人力系统,然后人力系统后将人员和工资信息传递到计税系统中,做一个测试,代码如下:

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/demo")
    public class UserController {
    
        @GetMapping("/get-user")
        public Person getUser() {
            // 基本工资 1000 绩效3000
            Salary salary = new Salary(1000,3000);
            Person person = new Person("小哇",salary);
            return person;
        }
    }

    在通过网络传输的计税系统中,进行反序列化,代码如下:

    import com.cp.security.user.common.JsonUtil;
    
    public class TestController {
    
        public static void main(String[] args) {
            String personJson = HttpRequest.sendGet("http://localhost:8080/demo/get-user","");
            Person person = JsonUtil.str2Obj(personJson);
            StringBuffer sb = new StringBuffer();
            sb.append("name"+person.getName());
            sb.append("基本工资"+person.getSalary().getBasePay());
            sb.append("绩效工资"+person.getSalary().getBonus());
            System.out.println(sb);
        }
    
    
    //运行结果如下:
    // name 小哇 基本工资1000 绩效工资3000

    很明显这不符合要求,存在严重的信息泄露问题,那么怎么解决呢?提供以下思路:

    1.在bonus(绩效工资)前面加上transient关键字进行修饰

      这的确是一个办法,但不是一个好的办法,在分布式部署的时候性能很差,不推荐

    2.新增一个业务对象(*)

      新增一个对象,该对象只有姓名和基本工资两个属性,符合开闭原则,对原来系统也没有侵入性,只是增加了工作量,相比较来说,这个是目前最受欢迎的方式了

    3.在请求端进行过滤

      在计税系统得到Person对象之后,对立面的绩效薪资进行隐藏,可行但是很危险,业务交由其他系统处理,对外依旧是暴露的,差评

    4.引出本文重点,采用Serializable接口中的两个私有方法writeObject和readObject,我们将Person稍作修改,上代码

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Person implements Serializable {
        private static final long serialVersionUID =45829L;
        // 员工姓名
        private String name;
        // 员工薪资
        private Salary salary;
    
        private void writeObject(ObjectOutputStream out)throws IOException {
            out.defaultWriteObject();
            out.writeInt(salary.getBasePay());
        }
        private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
            in.defaultReadObject();
            salary = new Salary(in.readInt(),0);
        }
    }
    // 此时运行的结果
    // name 小哇 基本工资1000 绩效工资0

    总结:

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

    a> out.defaultWriteObject();

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

    b>in.defaultReadObject();

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

    c>依然存在分布式部署的问题,只是提供一个思路而已,正常情况下依然推荐使用第二种方式,重新声明一个对象返回

  • 相关阅读:
    [Luogu P4178]Tree 题解(点分治+平衡树)
    [20190725NOIP模拟测试8]题解
    暑假集训考试反思+其它乱写
    [bzoj2752]高速公路 题解(线段树)
    bzoj1211树的计数 x bzoj1005明明的烦恼 题解(Prufer序列)
    [CQOI2014]数三角形 题解(找规律乱搞)
    [Catalan数三连]网格&有趣的数列&树屋阶梯
    [NOIP模拟测试7]visit 题解(组合数学+CRT+Lucas定理)
    [7.22NOIP模拟测试7]方程的解 题解(扩展欧几里得)
    leetcode371
  • 原文地址:https://www.cnblogs.com/slymonkey/p/10549361.html
Copyright © 2011-2022 走看看