-
方案二的实现
1.1 @JsonTypeInfo注解
1.2 @JsonSubTypes注解
1.3 @JsonTypeName注解 -
智能版:扩展@JsonTypeIdResolver的使用
3.1 自定义TypeIdResolver
3.2 实现方案 - 3.2.1 定义解析器
- 3.2.2 使用
-
3.2.3 测试
在Controller定义了入参后,随着业务的扩展,需要业务的扩展,需要不断的为入参新增字段,但是不同的业务需要使用不同的字段。若一直向入参中新增字段(不满足开闭原则)就会导致后期的不可维护性。
方案一:Controller层接收的是String类型,然后通过手动的方式来进行反序列化为子类对象。
方案二:使用Jackson的多态处理。
1. 方案二的实现
使用方式:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = RoleUser.class, name = "1"), @JsonSubTypes.Type(value = TokenUser.class, name = "2"), @Data public class AbstractBaseEntity { private String contentType; private String userName; private String password;jackson允许配置多态处理,当进行反序列化时,JSON数据匹配的对象可能有多个子类型,为了正确的读取对象的类型,我们需要添加一些类型信息。可以通过下面几个注解来实现:
1.1 @JsonTypeInfo注解
作用在接口/类上,被用来开启多态类型的处理,对基类/接口和子类/实现类都有效。
样例:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,include = JsonTypeInfo.As.EXISTING_PROPERTY ,property = "contentType", visible = true)- use:定义使用哪一种类型识别码,它有下面几个可选值:
此属性只有当:
- use为JsonTypeInfo.Id.CLASS(若不指定property则默认为@class)、JsonTypeInfo.Id.MINIMAL_CLASS(若不指定property则默认为@c)、JsonTypeInfo.Id.NAME(若不指定property默认为@type),
- include为JsonTypeInfo.As.PROPERTY、JsonTypeInfo.As.EXISTING_PROPERTY、JsonTypeInfo.As.EXTERNAL_PROPERTY
时才有效。
defaultImpl(可选):如果类型识别码不存在或者无效,可以使用该属性来制定反序列化时使用的默认类型。
visible(可选,默认为false):是否可见
属性定义了类型标识符的值是否会通过JSON流成为反序列化器的一部分,默认为fale,也就是说,jackson会从JSON内容中处理和删除类型标识符再传递给JsonDeserializer。1.2 @JsonSubTypes注解
作用于类/接口,用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,一般是配合
@JsonTypeInfo在基类上使用,比如:@JsonSubTypes的值是一个@JsonSubTypes.Type[]数组,里面枚举了多态类型(value对应子类)和类型的标识符值(name对应@JsonTypeInfo中的property标识名称的值,此为可选值。若不制定需由@JsonTypeName在子类上制定)1.3 @JsonTypeName注解
作用于子类,用来为多态子类指定类型标识符的值
@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = RoleUser.class), @JsonSubTypes.Type(value = TokenUser.class), @Data public class AbstractBaseEntity@Data @JsonTypeName("1") public class RoleUser extends AbstractBaseEntity@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = RoleUser.class, name = "1"), @JsonSubTypes.Type(value = TokenUser.class, name = "2"), @Data public class AbstractBaseEntity2. 案例
基类的使用:
@JsonTypeInfo(use = Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "contentType", visible = true) @JsonSubTypes({ @JsonSubTypes.Type(value = RoleUser.class, name = "1"), @JsonSubTypes.Type(value = TokenUser.class, name = "2"), @Data public class AbstractBaseEntity { * 1. 系列化时 @JsonTypeInfo 使用的是EXISTING_PROPERTY策略(已经存在的字段),故需要手动的填充这个字段。 * 2. 在反序列化对象时,@JsonSubTypes.Type根据name属性的不同,来转化为不同的子类对象 private String contentType; private String userName; private String password;子类的创建:
@Data public class RoleUser extends AbstractBaseEntity{ private String roleName; @Override public String toString() { return "RoleUser{" + "abstractBaseEntity='" + super.toString() + '\'' + "roleName='" + roleName + '\'' +@Data public class TokenUser extends AbstractBaseEntity { private String token; @Override public String toString() { return "TokenUser{" + "abstractBaseEntity='" + super.toString() + '\'' + "token='" + token + '\'' +测试代码:
public class Test { public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); RoleUser roleUser = new RoleUser(); roleUser.setRoleName("role-1"); roleUser.setPassword("rolePwd"); roleUser.setUserName("roleUserName"); roleUser.setContentType("1"); TokenUser tokenUser = new TokenUser(); tokenUser.setToken("token-1"); tokenUser.setPassword("tokenPassword"); tokenUser.setUserName("tokenUserName"); tokenUser.setContentType("2"); //序列化 String roleStr = objectMapper.writeValueAsString(roleUser); String tokenStr = objectMapper.writeValueAsString(tokenUser); System.out.println(roleStr); System.out.println(tokenStr); //反序列化 AbstractBaseEntity roleEntity = objectMapper.readValue(roleStr, AbstractBaseEntity.class); AbstractBaseEntity tokenEntity = objectMapper.readValue(tokenStr, AbstractBaseEntity.class); System.out.println(roleEntity); System.out.println(tokenEntity);Spring上的测试代码:
@Slf4j @RestController public class JacksonController { @RequestMapping("/test/js") @ResponseBody public String testEnvV3(@RequestBody AbstractBaseEntity entity) { RoleUser roleUser = null; if (entity.getContentType().equals("1")) { roleUser = (RoleUser) entity; return JSON.toJSONString(roleUser);3. 智能版:扩展@JsonTypeIdResolver的使用
Jackson 多态序列化可以通过@JsonSubtypes来实现,但总觉得不是很方便,比如新增子类的时候都要去加一下JsonSubTypes。
有没有一个比较智能化的扩展?
有的
@JsonTypeInfo使用JsonTypeInfo.Id.CUSTOM策略,然后自定义解析规则。@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") @JsonTypeIdResolver(JacksonTypeIdResolver.class)3.1 自定义TypeIdResolver
- idFromValueAndType:是序列化的时候告诉序列化器怎么生成标识符。
- typeFromId:是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来。
3.2 实现方案
3.2.1 定义解析器
public class JacksonTypeIdResolver implements TypeIdResolver { private JavaType baseType; @Override public void init(JavaType javaType) { baseType = javaType; @Override public String idFromValue(Object o) { return idFromValueAndType(o, o.getClass()); * 序列化时填充什么对象 @Override public String idFromValueAndType(Object o, Class<?> aClass) { //有出现同名类时可以用这个来做区别 JsonTypeName annotation = aClass.getAnnotation(JsonTypeName.class); if (annotation != null) { return annotation.value(); String name = aClass.getName(); String[] splits = StringUtils.split(name, "."); String className = splits[splits.length - 1]; return className; * idFromValueAndType 是序列化的时候告诉序列化器怎么生成标识符 * typeFromId是反序列化的时候告诉序列化器怎么根据标识符来识别到具体类型,这里用了反射,在程序启动时,把要加载的包通过Reflections加载进来 @Override public JavaType typeFromId(DatabindContext databindContext, String type) { Class<?> clazz = getSubType(type); if (clazz == null) { throw new IllegalStateException("cannot find class '" + type + "'"); return databindContext.constructSpecializedType(baseType, clazz); public Class<?> getSubType(String type) { Reflections reflections = ReflectionsCache.getReflections(); Set<Class<?>> subTypes = reflections.getSubTypesOf((Class<Object>) baseType.getRawClass()); for (Class<?> subType : subTypes) { JsonTypeName annotation = subType.getAnnotation(JsonTypeName.class); if (annotation != null && annotation.value().equals(type)) { return subType; } else if (subType.getSimpleName().equals(type) || subType.getName().equals(type)) { return subType; return null; @Override public String idFromBaseType() { return idFromValueAndType(null, baseType.getClass()); @Override public String getDescForKnownTypeIds() { return null; @Override public JsonTypeInfo.Id getMechanism() { return JsonTypeInfo.Id.CUSTOM;反射相关,引入依赖:
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency>public class ReflectionsCache { private static Reflections reflections; public static void setReflections(Reflections _reflections) { reflections = _reflections; public static Reflections getReflections() { return reflections;@Service public class ClassCacheInitializing implements InitializingBean { private String packages="com.tellme.jackson"; * 反射会有点耗时,所以程序启动的时候加载完放到缓存里面去,后面要用的时候直接去缓存取 * @throws Exception @Override public void afterPropertiesSet() throws Exception { Reflections reflections = new Reflections(packages); if (reflections != null) { ReflectionsCache.setReflections(reflections);3.2.2 使用
@Data @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") @JsonTypeIdResolver(JacksonTypeIdResolver.class) public class Animal { private String name;子类(子类上无需在使用注解):
@Data public class Dog extends Animal { private int age; @Override public String toString() { return "Dog{" + "animal=" + super.toString() + "age=" + age +3.2.3 测试
public class TestEx { public static void main(String[] args) throws Exception { ClassCacheInitializing classCacheInitializing=new ClassCacheInitializing(); classCacheInitializing.afterPropertiesSet(); ObjectMapper objectMapper = new ObjectMapper(); AnimalDto animalDto = new AnimalDto(); Dog dog = new Dog(); dog.setName("dog"); dog.setAge(1); animalDto.setAnimal(dog); String s = objectMapper.writeValueAsString(animalDto); System.out.println(s); animalDto = objectMapper.readValue(s, AnimalDto.class); System.out.println(animalDto);