Crnk框架中为单个关联映射不同资源的实现问询
在Crnk中实现动态返回不同资源字段的两种方案
你之前尝试的抽象类+子类思路是完全可行的,我来帮你补全具体的实现细节,另外再提供一种更轻量的条件序列化方案,你可以根据业务复杂度灵活选择。
方案一:抽象资源类+子类实现(适合字段差异较大的场景)
这种方式通过定义不同的资源子类,在Mapper中根据业务条件返回对应子类实例,让Crnk自动序列化对应字段,逻辑清晰且扩展性强。
1. 定义抽象基础资源类
把所有通用字段放在抽象类里,子类只扩展各自的专属字段:
@JsonApiResource(type = "items") public abstract class BaseItemResource { private String id; @JsonApiId public String getId() { return id; } public void setId(String id) { this.id = id; } // 这里可以添加所有资源共用的属性,比如创建时间、更新时间等 }
2. 创建两个资源子类
分别对应两种返回场景的专属字段:
// 对应返回firstData的场景 public class FirstItemResource extends BaseItemResource { private Object firstData; @JsonApiAttribute public Object getFirstData() { return firstData; } public void setFirstData(Object firstData) { this.firstData = firstData; } } // 对应返回secondData的场景 public class SecondItemResource extends BaseItemResource { private Object secondData; @JsonApiAttribute public Object getSecondData() { return secondData; } public void setSecondData(Object secondData) { this.secondData = secondData; } }
3. 实现ResourceMapper动态选择子类
在Mapper中根据业务实体的条件(比如某个标志位),实例化对应的子类并填充数据:
public class ItemResourceMapper implements ResourceMapper<Item, BaseItemResource> { @Override public Class<BaseItemResource> getResourceClass() { return BaseItemResource.class; } @Override public BaseItemResource map(Item source, QuerySpec querySpec, RepositoryContext context) { BaseItemResource resource; // 替换成你的实际业务判断逻辑,比如根据source的某个属性值 if ("typeA".equals(source.getBizType())) { FirstItemResource firstResource = new FirstItemResource(); firstResource.setFirstData(source.getFirstBizData()); firstResource.setId(source.getId()); resource = firstResource; } else { SecondItemResource secondResource = new SecondItemResource(); secondResource.setSecondData(source.getSecondBizData()); secondResource.setId(source.getId()); resource = secondResource; } return resource; } // 如果需要反向映射(资源转实体),实现unmap方法 @Override public Item unmap(BaseItemResource resource, QuerySpec querySpec, RepositoryContext context) { Item item = new Item(); item.setId(resource.getId()); if (resource instanceof FirstItemResource) { item.setFirstBizData(((FirstItemResource) resource).getFirstData()); item.setBizType("typeA"); } else if (resource instanceof SecondItemResource) { item.setSecondBizData(((SecondItemResource) resource).getSecondData()); item.setBizType("typeB"); } return item; } }
4. 注册资源和Mapper到Crnk配置
确保两个子类都被Crnk识别,在配置类中注册:
@Configuration public class CrnkConfiguration extends CrnkConfigurerAdapter { @Override public void setupModule(Module module) { // 注册Mapper module.addResourceMapper(new ItemResourceMapper()); // 注册两个资源子类 module.addResource(FirstItemResource.class); module.addResource(SecondItemResource.class); // 注册对应的资源仓库(如果有的话) module.addResourceRepository(ItemRepository.class); } }
方案二:单资源类+条件序列化(适合字段差异小的场景)
如果只是个别字段需要动态显示,可以用Jackson的自定义序列化器,在同一个资源类中控制字段是否序列化,更轻量简洁。
1. 定义带条件字段的资源类
添加判断用的标记字段(用@JsonIgnore隐藏),并为动态字段添加自定义序列化器:
@JsonApiResource(type = "items") public class ItemResource { private String id; private Object firstData; private Object secondData; private String bizType; // 用来判断返回哪个字段,不序列化到响应 @JsonApiId public String getId() { return id; } public void setId(String id) { this.id = id; } @JsonApiAttribute @JsonInclude(JsonInclude.Include.CUSTOM) @JsonSerialize(using = FirstDataConditionalSerializer.class) public Object getFirstData() { return firstData; } public void setFirstData(Object firstData) { this.firstData = firstData; } @JsonApiAttribute @JsonInclude(JsonInclude.Include.CUSTOM) @JsonSerialize(using = SecondDataConditionalSerializer.class) public Object getSecondData() { return secondData; } public void setSecondData(Object secondData) { this.secondData = secondData; } @JsonIgnore public String getBizType() { return bizType; } public void setBizType(String bizType) { this.bizType = bizType; } }
2. 实现自定义序列化器
根据标记字段决定是否序列化当前字段:
// 控制firstData是否序列化 public class FirstDataConditionalSerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 获取当前资源实例,判断业务类型 ItemResource resource = (ItemResource) serializers.getAttribute("resource"); if ("typeA".equals(resource.getBizType())) { gen.writeObject(value); } else { // 不序列化该字段,Crnk会自动忽略 gen.writeNull(); } } } // 控制secondData是否序列化 public class SecondDataConditionalSerializer extends JsonSerializer<Object> { @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { ItemResource resource = (ItemResource) serializers.getAttribute("resource"); if ("typeB".equals(resource.getBizType())) { gen.writeObject(value); } else { gen.writeNull(); } } }
3. 在Mapper中设置标记字段
public class ItemResourceMapper implements ResourceMapper<Item, ItemResource> { @Override public ItemResource map(Item source, QuerySpec querySpec, RepositoryContext context) { ItemResource resource = new ItemResource(); resource.setId(source.getId()); resource.setFirstData(source.getFirstBizData()); resource.setSecondData(source.getSecondBizData()); // 设置业务类型标记 resource.setBizType(source.getBizType()); return resource; } // 反向映射逻辑类似,根据存在的字段设置实体属性 }
常见问题排查
如果你之前的尝试没成功,可能是以下原因:
- 资源子类没有注册到Crnk配置中,导致Crnk无法识别
- Mapper中没有正确实例化子类,或者业务判断逻辑有误
- 抽象资源类的
@JsonApiResource注解type要和子类一致,避免Crnk识别为不同资源类型
内容的提问来源于stack exchange,提问作者funny-shmunny
相关产品推荐
相关产品推荐

