【问题标题】:JsonIdentityInfo Resolver instantiated in Spring Context在 Spring 上下文中实例化的 JsonIdentityInfo 解析器
【发布时间】:2015-10-18 22:09:03
【问题描述】:

如何将 ObjectIdResolver 注册到 Spring/Jackson 对象映射器,以便 Spring 实例化 ObjectIdResolver 类?我想在我的 ObjectIdResolver 类中使用依赖注入。

ObjectIdResolver.java

@Component
public class UserIdResolver implements ObjectIdResolver {

    @Autowired
    UserConverter userConverter;

    @Override
    public void bindItem(ObjectIdGenerator.IdKey id, Object ob) { }

    @Override
    public Object resolveId(ObjectIdGenerator.IdKey id) {
        return userConverter.convert((Integer)id.key);
    }

    @Override
    public boolean canUseFor(ObjectIdResolver resolverType) {
        return getClass().isAssignableFrom(resolverType.getClass());
    }

    @Override
    public ObjectIdResolver newForDeserialization(Object c) {
        return this;
    }

}

Order.java

public class Order  {

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            resolver = UserIdResolver.class,
            scope = User.class
    )
    @JsonIdentityReference(alwaysAsId=true)
    private User user;

..

}

spring-servlet.xml

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true" p:simpleDateFormat="yyyy-MM-dd'T'HH:mm:ss.SSSZ" />

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"
      p:targetObject-ref="objectMapper" p:targetMethod="registerModule">
    <property name="arguments">
        <list>
            <bean class="com.fasterxml.jackson.datatype.joda.JodaModule" />
        </list>
    </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService">
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
        <bean class="org.springframework.http.converter.ResourceHttpMessageConverter" />
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

【问题讨论】:

    标签: java spring spring-mvc jackson jsonidentityinfo


    【解决方案1】:

    您可以尝试另一种解决方案,稍微复杂一些,但您可以完全控制反序列化。

    第 1 步:创建一个 JpaEntityResolver

    public class JpaEntityResolver extends SimpleObjectIdResolver {
    
        private EntityManager em;
    
        private Class<? extends EntityType> entityClass;
    
        public JpaEntityResolver() {
    
        }
    
        public JpaEntityResolver(EntityManager em, Class<? extends EntityType> entityClass) {
            this.em = em;
            this.entityClass = entityClass;
        }
    
        @Override
        public Object resolveId(IdKey id) {
            Object resolved = super.resolveId(id);
            if (resolved == null) {
                resolved = findEntityById(id);
                bindItem(id, resolved);
            }
            return resolved;
        }
    
        private Object findEntityById(IdKey idKey) {
            Long id = (Long) idKey.key;
            return em.find(entityClass, id);
        }
    
        @Override
        public ObjectIdResolver newForDeserialization(Object context) {
            return new JpaEntityResolver(this.em, this.entityClass);
        }
    }
    

    第 2 步:创建一个自定义 SpringHandlerInstantiator,帮助杰克逊动态定义哪个

    @Component
    public class TsqBeanHandlerInstantiator extends SpringHandlerInstantiator {
    
        /** Logger. */
        protected static final Logger LOGGER = LoggerFactory.getLogger(TsqBeanHandlerInstantiator.class.getName());
    
        private final AutowireCapableBeanFactory beanFactory;
    
        private final EntityManager em;
    
        private List<Class<? extends EntityType>> tsqEntities = new ArrayList<>();
    
        /**
         * Create a new SpringHandlerInstantiator for the given BeanFactory.
         * @param beanFactory the target BeanFactory
         */
        public TsqBeanHandlerInstantiator(AutowireCapableBeanFactory beanFactory, EntityManager em) {
            super(beanFactory);
            this.beanFactory = beanFactory;
            this.em = em;
        }
    
        @PostConstruct
        public void init()
        {
            //We record all available entity for futur use.
            for (EntityType<?> entity : em.getMetamodel().getEntities()) {
                Class<? extends EntityType> entityClass = (Class<? extends EntityType>) entity.getJavaType();
                String className = entityClass.getName();
                tsqEntities.add(entityClass);
            }       
        }
    
        @Override
        public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> implClass) {
            Class entityClass =  annotated.getRawType();
            LOGGER.debug("deserializerInstance : " + entityClass);
            return (JsonDeserializer<?>) this.beanFactory.createBean(implClass);
        }
    
        /** @since 4.3 */
        @Override
        public ObjectIdGenerator<?> objectIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
            Class entityClass =  annotated.getRawType();
            LOGGER.debug("objectIdGeneratorInstance : " + entityClass);
            return (ObjectIdGenerator<?>) this.beanFactory.createBean(implClass);
        }
    
        /** @since 4.3 */
        @Override
        public ObjectIdResolver resolverIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) {
            Class entityClass =  annotated.getRawType();
            if(!tsqEntities.contains(entityClass))
            {
                return super.resolverIdGeneratorInstance(config, annotated, implClass);
            }
            LOGGER.debug("resolverIdGeneratorInstance : " + entityClass);
            return new JpaEntityResolver(em, entityClass);
        }
    }
    

    第 3 步:将您的处理程序注册到 jacksonHttpMessageConverter 的 ObjectMapper

    @Configuration
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
    
        private @Autowired MappingJackson2HttpMessageConverter jacksonHttpMessageConverter;
    
        private @Autowired TsqBeanHandlerInstantiator tsqBeanHandlerInstantiator;
    
        @PostConstruct
        public void init(){
            ObjectMapper objectMapper = jacksonHttpMessageConverter.getObjectMapper();
            objectMapper.setHandlerInstantiator(tsqBeanHandlerInstantiator);
        }
    }
    

    第 4 步:享受在您的控制器上使用它

    @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Bean> create(HttpServletRequest  request) throws IOException {
        // getting the posted value
        String body = CharStreams.toString(request.getReader());
        Bean bean = jacksonHttpMessageConverter.getObjectMapper().readValue(body, service.getBeanClass());
        Bean saved = service.save(bean);
    
        URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(saved.getId()).toUri();
        return ResponseEntity.created(location).body(saved);
    }
    

    【讨论】:

      【解决方案2】:

      实际上现在只要在构造函数中添加@Autowired,就可以将@Autowired 用于ObjectIdResolver:

      public class CategoryTaskIdResolver implements ObjectIdResolver {
      
          CategoryTaskRepository categoryTaskRepository;
          
          public CategoryTaskIdResolver(@Autowired CategoryTaskRepository categoryTaskRepo) {
              this.categoryTaskRepository = categoryTaskRepo;
          }
          
          @Override
          public void bindItem(IdKey id, Object pojo) {
          }
      
          @Override
          public Object resolveId(IdKey id) {
              return categoryTaskRepository.findById((Long) id.key).orElse(null);
          }
      
          @Override
          public ObjectIdResolver newForDeserialization(Object context) {
              return this;
          }
      
          @Override
          public boolean canUseFor(ObjectIdResolver resolverType) {
              return getClass().isAssignableFrom(resolverType.getClass());;
          }
      }
      

      我正在使用 Spring Boot 2.3.4 和 Jackson Databind 2.11.2。

      【讨论】:

        【解决方案3】:

        因为 com.fasterxml.jackson.annotation.ObjectIdResolver 不是由 spring 构造和管理的(杰克逊在构造函数上使用反射),所以没有直接的方法来实现这一点。当我想在 jpa EntityListener 中自动装配一些依赖项时遇到了类似的问题,并在以下位置找到了巧妙的技巧: https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/

        /**
         * Helper class which is able to autowire a specified class. It holds a static reference to the {@link org
         * .springframework.context.ApplicationContext}.
         */
        public final class AutowireHelper implements ApplicationContextAware {
        
            private static final AutowireHelper INSTANCE = new AutowireHelper();
            private static ApplicationContext applicationContext;
        
            private AutowireHelper() {
            }
        
            /**
             * Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
             * are null.
             *
             * @param classToAutowire the instance of the class which holds @Autowire annotations
             * @param beansToAutowireInClass the beans which have the @Autowire annotation in the specified {#classToAutowire}
             */
            public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
                for (Object bean : beansToAutowireInClass) {
                    if (bean == null) {
                        applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
                        return;
                    }
                }
            }
        
            @Override
            public void setApplicationContext(final ApplicationContext applicationContext) {
                AutowireHelper.applicationContext = applicationContext;
            }
        
            /**
             * @return the singleton instance.
             */
            public static AutowireHelper getInstance() {
                return INSTANCE;
            }
        
        }
        

        所以你必须添加这个AutowireHelper 类并改变你的方法:

            @Override
            public ObjectIdResolver newForDeserialization(Object c) {
                AutowireHelper.autowire(this, userConverter);
                return this;
            }
        

        【讨论】:

          猜你喜欢
          • 2013-07-31
          • 1970-01-01
          • 1970-01-01
          • 2012-11-19
          • 1970-01-01
          • 2012-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多