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

2020-03-15  本文已影响0人  yesAnd_

一、前言

最近工作中使用到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的问题上,如果仅是从缓存中国取出数据返给前端,不需要对数据进行加工,也就无所谓复制新对象了。

欢迎拍砖,欢迎交流~

注:转载请注明出处

上一篇 下一篇

猜你喜欢

热点阅读