Spring Boot中自定义@Cacheable:仅缓存返回对象指定字段
实现自定义缓存特定字段的几种思路
当然可以实现!Spring Cache的扩展性很强,不管是自定义@Cacheable相关逻辑,还是用其他替代方案,都能做到只缓存Person对象的personId和personName字段。下面我会从简单到复杂给你梳理几种可行的方式:
一、最直接:在缓存方法里返回精简对象
不需要自定义注解,直接在被@Cacheable标记的方法里,把查询到的完整Person对象转换成只保留目标字段的实例再返回——这样Spring Cache就只会缓存这个精简版的对象。
示例代码:
// 从数据库/其他数据源获取完整Person的方法(不缓存) private Person getFullPersonFromDB(String personID) { // 这里写实际查询逻辑,返回包含所有字段的Person } // 带缓存的对外方法,只返回精简版Person @Cacheable(value = "personCache", key = "#personID") public Person getPersonById(String personID) { Person fullPerson = getFullPersonFromDB(personID); // 构建只包含需要字段的Person实例 Person cachedPerson = new Person(); cachedPerson.setPersonId(fullPerson.getPersonId()); cachedPerson.setPersonName(fullPerson.getPersonName()); // 其他字段不赋值,缓存时自然不会存储这些内容 return cachedPerson; }
这种方式的好处是简单直接,不需要额外配置,适合只针对单个方法的场景。
二、自定义注解+AOP切面(更灵活)
如果需要给多个方法复用这个“缓存特定字段”的逻辑,可以自定义一个扩展@Cacheable的注解,再结合AOP切面来统一处理对象的转换。
步骤1:自定义注解
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) // 继承@Cacheable的所有属性,保留原生缓存能力 @Cacheable public @interface CacheablePersonFields { String value() default ""; String key() default ""; // 可以根据需求添加@Cacheable的其他属性,比如condition、unless等 }
步骤2:编写AOP切面处理值转换
切面会拦截被@CacheablePersonFields标记的方法,在方法返回后把完整Person转成精简版,缓存时只存精简对象;读取缓存时再把精简对象转成Person返回(如果需要)。
@Aspect @Component public class PersonCacheAspect { // 拦截自定义注解的方法 @Around("@annotation(cacheablePersonFields)") public Object handlePersonCache(ProceedingJoinPoint joinPoint, CacheablePersonFields cacheablePersonFields) throws Throwable { // 先执行原方法,获取完整Person对象 Object result = joinPoint.proceed(); if (result instanceof Person) { // 转换为只包含目标字段的对象(这里也可以用专门的DTO) Person fullPerson = (Person) result; Person cachedPerson = new Person(); cachedPerson.setPersonId(fullPerson.getPersonId()); cachedPerson.setPersonName(fullPerson.getPersonName()); return cachedPerson; } return result; } }
步骤3:使用自定义注解
@CacheablePersonFields(value = "personCache", key = "#personID") public Person getPersonById(String personID) { // 直接返回完整Person,切面会自动处理转换 return getFullPersonFromDB(personID); }
三、替代方案:用DTO专门缓存
创建一个只包含需要字段的DTO类,缓存这个DTO而不是原Person实体,这样逻辑更清晰,也避免原实体类字段变化影响缓存结构。
步骤1:定义缓存用DTO
// 只包含需要缓存的字段 public class PersonCacheDTO { private String personId; private String personName; // 从Person构造DTO public PersonCacheDTO(Person person) { this.personId = person.getPersonId(); this.personName = person.getPersonName(); } // 从DTO转回Person的方法(如果需要) public Person toPerson() { Person person = new Person(); person.setPersonId(this.personId); person.setPersonName(this.personName); return person; } // getter/setter 或者用Lombok简化 }
步骤2:缓存DTO
@Cacheable(value = "personCache", key = "#personID") public PersonCacheDTO getPersonCacheDTO(String personID) { Person fullPerson = getFullPersonFromDB(personID); return new PersonCacheDTO(fullPerson); } // 对外提供的方法,把DTO转成Person返回 public Person getPersonById(String personID) { PersonCacheDTO cachedDTO = getPersonCacheDTO(personID); return cachedDTO.toPerson(); }
四、替代方案:自定义缓存序列化器(适合Redis等缓存中间件)
如果你的项目用Redis作为缓存,可以自定义序列化器,在序列化Person对象时只序列化指定字段,从缓存读取时也只填充这些字段。
步骤1:自定义Redis序列化器
public class PersonRedisSerializer implements RedisSerializer<Person> { private final ObjectMapper objectMapper = new ObjectMapper(); static { // 使用MixIn指定只序列化/反序列化的字段 objectMapper.addMixIn(Person.class, PersonCacheMixIn.class); } @Override public byte[] serialize(Person person) throws SerializationException { try { return objectMapper.writeValueAsBytes(person); } catch (JsonProcessingException e) { throw new SerializationException("序列化Person失败", e); } } @Override public Person deserialize(byte[] bytes) throws SerializationException { try { return objectMapper.readValue(bytes, Person.class); } catch (IOException e) { throw new SerializationException("反序列化Person失败", e); } } // 定义MixIn,只标记需要序列化的字段 abstract static class PersonCacheMixIn { @JsonProperty abstract String getPersonId(); @JsonProperty abstract String getPersonName(); // 其他字段不标注@JsonProperty,不会被序列化 } }
步骤2:配置RedisCacheManager使用自定义序列化器
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() // 设置值的序列化器为我们自定义的PersonRedisSerializer .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new PersonRedisSerializer())); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(cacheConfig) .build(); }
这样配置后,即使你用原生的@Cacheable注解,缓存的Person对象也只会包含personId和personName两个字段,完全不需要修改业务方法。
内容的提问来源于stack exchange,提问作者Mehraj Malik
相关产品推荐
相关产品推荐

