3

如何将 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;
    }

}

订单.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>
4

3 回答 3

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);
}
于 2017-05-19T12:55:17.057 回答
1

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

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。

于 2021-01-15T18:31:39.307 回答
0

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

/**
 * 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;
    }
于 2016-04-20T14:31:05.180 回答