使用Ehcache要注意的对象拷贝问题(深拷贝、浅拷贝)

一、前言

最近工作中使用到Spring Ehcache作为一级缓存以减轻对redis的压力,在代码改造后遇到了一个对象拷贝的问题,在这里记录下踩到的坑。

将获取的文章详情使用ehcache缓存到本地内从中,访问量大的时候会先走本地缓存拿数据,然后再Redis、数据库,ehcache相关代码如下:

@Cacheable(value = EhCacheSpaceKey.ContentApi.EHCACHE_DETAIL_CONTENTVO, key = "#root.targetClass.name+#root.methodName+#id")
public ContentApiVo getContentDetail(String id) {

    //获取文章详情
    return contentApiVo;
}

测试时发现响应确实更快、redis的压力也小了,就在以为大功告成时,发现串数据的问题,看代码是如何翻车的:

public String getContentDetail(String contentId, String id, String userId, String appId, Integer contentType) {
  
   ContentApiVo contentApiVoCache = ehCacheService.getContentDetail(id);
  
  //处理数据
        
}

拿到缓存中的contentApiVoCache后,进行数据处理然后返回给前端,但是在请求量起来后会出现串数据的现象,排查后发现问题出在了contentApiVoCache这个对象上,原因在于ehcache在命中缓存后,会直接返该对象,也就是再一次缓存周期中,每次返回的都是同一个对象,后续的数据处理必然会改变数据的内容。所以在需要改变缓存结果的操作时,所以要拷贝一个新的对象进行操作。这里就引出了对象拷贝。

二、对象拷贝

1、基本概念

在Java中谈到对象拷贝,我们主要是说浅拷贝(shallow copy)深拷贝(deep copy)两种方式。

  • 浅拷贝 , 就是仅把原来对象的属性值复制到新对象对应属性上,对于基本类型属性,会对值进行复制,而引用类型属性则是直接复制应用对象的地址,这就导致复制后的新对象会依赖原对象的属性。
  • 深拷贝,基础类型属性和浅拷贝的一致,引用类型属性则是新生成一个引用,把原属性里的内容复制到新引用中,这样拷贝出来的对象,和原来的对象完全独立。

2、Cloneable接口

我们可以实现Cloneable接口进行代码拷贝,构造两个对象进行说明

Address:

public class Address implements Cloneable{

    private String city;

    private String street;

        //标准构造方法、setter、getter省略

    //重写toString方法,便于输出结果
    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", street='" + street + '\'' +
                '}';
    }
}

User:

public class User implements Cloneable{

    private String name;

    private Integer age;

    private Address address;

    @Override
    public Object clone() throws CloneNotSupportedException {
        //调用super.clone()方法进行拷贝
        return super.clone();
    }
  
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

Address作为User的一个引用类型的属性,两个对象都实现了Cloneable接口,调用super.clone()方法进行拷贝复制,编写一个测试类,使用clone()复制一个新对象,改变新对象里的引用属性值,观察是否会影响到原对象的值:

public static void main(String[] args) {

    Address address= new Address("北京","长安街");
    User user = new User("wyj",28,address);
    System.out.println("user = " + user.toString());
    User copyUser = null;
    try {
        copyUser = (User)user.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    copyUser.getAddress().setCity("上海");
    System.out.println("copyUser = " + copyUser.toString());
    System.out.println("second print user = " + user.toString());
}

输出结果:

user = User{name='wyj', age=28, address=Address{city='北京', street='长安街'}}
copyUser = User{name='wyj', age=28, address=Address{city='上海', street='长安街'}}
second print user = User{name='wyj', age=28, address=Address{city='上海', street='长安街'}}

可以看出,将新复制出来的user对象中的address属性的city值由”北京“设置为”上海“,再次输出原user的值,发现city值也变为了”上海“,即可说明,user和copuUser的值都是指向了一个Address,即Cloneable接口默认实现的是浅拷贝,那么如何实现深拷贝呢?

3、重写Clone()方法,实现深拷贝

道理很简单,我们需要重写User类的clone()方法达到深拷贝的目的,代码如下:

@Override
public Object clone() throws CloneNotSupportedException {
    User user = (User) super.clone();
    //复制一个新的address设置到user中
    user.address = (Address) this.address.clone();
    return user;
}

在clone()方法中,调用super.clone后,我们重新对得到的user的address赋新值,来避免和原user中的address值一样,注意:如果Address中也有引用类型属性,也要对其clone方法进行重写,拷贝对象有多层引用要重写多层clone()方法。再次输出结果:

user = User{name='wyj', age=28, address=Address{city='北京', street='长安街'}}
copyUser = User{name='wyj', age=28, address=Address{city='上海', street='长安街'}}
second print user = User{name='wyj', age=28, address=Address{city='北京', street='长安街'}}

可以看到,这次user和copyUser中的值互不影响,实现了深拷贝。

三、总结

深拷贝、浅拷贝,并未好坏之分,要看业务场景,深拷贝实现时会重新new出一个新的引用,所以开销也要比浅拷贝大,回到ehecache的问题上,如果仅是从缓存中国取出数据返给前端,不需要对数据进行加工,也就无所谓复制新对象了。

欢迎拍砖,欢迎交流~

注:转载请注明出处

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容

  • 他对我的忽视,让我最终决定从他的生活中消失。我爱过他。他是个优秀的坏男人。 所有认识我的人都会觉得我是个懂事的姑娘...
    青城的春天短阅读 322评论 2 5
  • 一般情况下,吃枸杞对眼睛是有好处的。枸杞具有明目的作用,适量食用枸杞可以增进视力,保持眼睛健康。那么保护眼睛吃多少...
    苯_b1a2阅读 978评论 0 1
  • 待你长发及腰,浪漫七夕可好? 穿越红尘烟火,重返最美年少。 (感谢好风明月为诗作画)
    一剪红梅阅读 313评论 2 5
  • 十九世纪作家纪伯伦这样写道: 生活的确是黑暗的,除非有了渴望; 所有渴望是盲目的,除非有了知识; 一切知识都是徒然...
    一埝阅读 74评论 0 1
  • 认同感受 1)允许孩子有自己的感受,这样孩子才能了解他们有能力处理自己的感受。 2)不要修复、解救、或试图说服孩子...
    令芳老师阅读 412评论 0 1