使用Jackson Mixins和MappingJacksonHttpMessageConverter& Spring MVC [英] Using Jackson Mixins with MappingJacksonHttpMessageConverter & Spring MVC

查看:75
本文介绍了使用Jackson Mixins和MappingJacksonHttpMessageConverter& Spring MVC的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我马上就会回答我真正的问题/问题,有没有办法在HttpMessageConverter 中访问控制器处理程序方法的注释?我很确定答案是否定的(在浏览Spring的源代码之后)。

I'll get to my real question/issue right away, is there any way to access annotations on the controller's handler method inside of a HttpMessageConverter? I'm pretty sure the answer is no (after walking through Spring's source code).

还有其他方法可以使用 Jackson Mixins 使用 MappingJacksonHttpMessageConverter ?我已经基于MappingJacksonHttpMessageConverter实现了我自己的HttpMessageConverter来升级它以使用Jackson 2.0。

Is there any other way to use Jackson Mixins paired when using MappingJacksonHttpMessageConverter? I've already implemented my own HttpMessageConverter based on MappingJacksonHttpMessageConverter to "upgrade" it to use Jackson 2.0.

Controller.class

@Controller
public class Controller {

    @JsonFilter({ @JsonMixin(target=MyTargetObject.class, mixin=MyTargetMixin.class) })
    @RequestMapping(value="/my-rest/{id}/my-obj", method=RequestMethod.GET, produces="application/json")
    public @ResponseBody List<MyTargetObject> getListOfFoo(@PathVariable("id") Integer id) {
        return MyServiceImpl.getInstance().getBarObj(id).getFoos();
    }
}

@JsonFilter 是我希望传递给mapper的自定义注释,然后可以自动将其直接提供给ObjectMapper。

@JsonFilter is a custom annotation I wish to pass to the mapper, which then can be fed directly to the ObjectMapper automatically.

MappingJacksonHttpMessageConverter.class

public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    ...

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) {

            //Obviously, no access to the HandlerMethod here.

    }

    ...
}

我为这个答案进行了广泛的搜索。到目前为止,我只看到人们在Controller的处理方法中将他们的对象序列化为JSON(违反了 DRY)原则在每种方法中反复进行)。或直接注释其数据对象(没有关于如何公开对象的解耦或多种配置)。

I've searched far and wide for this answer. So far I've only seen people serialize their object to JSON inside of the Controller's handling method (violating the DRY principle repeatedly in every method). Or annotation their data objects directly (no decoupling or multiple configurations on how to expose your objects).

可能无法在HttpMessageConverter中完成。还有其他选择吗?拦截器可以访问HandlerMethod,但不能访问处理程序方法的返回对象。

It may be that it can't be done in a HttpMessageConverter. Are there other options? Interceptors give access to the HandlerMethod but not to the returned object of the handler method.

推荐答案

在发布下面的答案之后,我改变了我的做法。我使用了 HandlerMethodReturnValueHandle r。我必须创建一个程序化的Web配置来覆盖订单,因为最后会触发自定义返回值处理程序。我需要在默认值之前触发它们。

After posting the answer below, I changed how I did this. I used a HandlerMethodReturnValueHandler. I had to create a programmatic web config to override the order because custom return value handlers are triggered last. I needed them to be triggered before the defaults.

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
   ...
}

希望这会导致某人的方向比我下面的回答更好。

Hopefully this will lead someone in a better direction than my answer below.

这允许我将任何对象直接序列化为JSON。在@RequestMapping中有produce =application / json然后我总是将返回值序列化为JSON。

This allowed me to serialize any object directly into JSON. In the @RequestMapping has produces="application/json" then I'd always serialized the return value into JSON.

我为参数绑定做了同样的事情,除了我用过a HandlerMethodArgumentResolver 。只需使用您选择的注释注释您的类(我使用JPA @Entity,因为我通常会序列化为模型)。

I did the same thing for parameter binding except I used a HandlerMethodArgumentResolver. Just annotate your classes with an annotation of your choosing (I used JPA @Entity because I'd normally be serializing into models).

您现在可以使用无缝POJO到JSON de在Spring控制器中进行/序列化,不需要任何样板代码。

You now have seamless POJO to JSON de/serialization in your Spring controllers without any boilerplater code necessary.

Bonus:我有的参数解析器将检查参数的@Id标签,如果JSON包含对于Id的键,然后检索实体并将JSON应用于持久对象。 Bam。

Bonus: The argument resolver I have will check for @Id tags to of the parameter, if the JSON contains a key for an Id then the entity is retrieved and the JSON is applied TO the persisted object. Bam.

/**
 * De-serializes JSON to a Java Object.
 * <p>
 * Also provides handling of simple data type validation.  If a {@link JsonMappingException} is thrown then it
 * is wrapped as a {@link ValidationException} and handled by the MVC/validation framework.
 *
 * @author John Strickler
 * @since 2012-08-28
 */
public class EntityArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private SessionFactory sessionFactory;

    private final ObjectMapper objectMapper = new ObjectMapper();

    private static final Logger log = Logger.getLogger(EntityArgumentResolver.class);

    //whether to log the incoming JSON
    private boolean doLog = false;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().getAnnotation(Entity.class) != null;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        String requestBody = IOUtils.toString(request.getReader());
        Class<?> targetClass = parameter.getParameterType();
        Object entity = this.parse(requestBody, targetClass);
        Object entityId = getId(entity);

        if(doLog) {
            log.info(requestBody);
        }

        if(entityId != null) {
            return copyObjectToPersistedEntity(entity, getKeyValueMap(requestBody), entityId);
        } else {
            return entity;
        }
    }


    /**
     * @param rawJson a json-encoded string
     * @return a {@link Map} consisting of the key/value pairs of the JSON-encoded string
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> getKeyValueMap(String rawJson) throws JsonParseException, JsonMappingException, IOException {
        return objectMapper.readValue(rawJson, HashMap.class);
    }


    /**
     * Retrieve an existing entity and copy the new changes onto the entity.
     *
     * @param changes a recently deserialized entity object that contains the new changes
     * @param rawJson the raw json string, used to determine which keys were passed to prevent
     *                copying unset/null values over to the persisted entity
     * @return the persisted entity with the new changes copied onto it
     * @throws NoSuchMethodException
     * @throws SecurityException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    private Object copyObjectToPersistedEntity(Object changesObject, Map<String, Object> changesMap, Object id) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {

        Session session = sessionFactory.openSession();

        Object persistedObject =
                session.get(changesObject.getClass(), (Serializable) id);

        session.close();

        if(persistedObject == null) {
            throw new ValidationException(changesObject.getClass().getSimpleName() + " #" + id + " not found.");
        }

        Class<?> clazz = persistedObject.getClass();

        for(Method getterMethod : ReflectionUtils.getAllDeclaredMethods(clazz)) {

            Column column = getterMethod.getAnnotation(Column.class);

            //Column annotation is required
            if(column == null) {
                continue;
            }

            //Is the field allowed to be updated?
            if(!column.updatable()) {
                continue;
            }

            //Was this change a part of JSON request body?
            //(prevent fields false positive copies when certain fields weren't included in the JSON body)
            if(!changesMap.containsKey(BeanUtils.toFieldName(getterMethod))) {
                continue;
            }

            //Is the new field value different from the existing/persisted field value?
            if(ObjectUtils.equals(getterMethod.invoke(persistedObject), getterMethod.invoke(changesObject))) {
                continue;
            }

            //Copy the new field value to the persisted object
            log.info("Update " + clazz.getSimpleName() + "(" + id + ") [" + column.name() + "]");

            Object obj = getterMethod.invoke(changesObject);

            Method setter = BeanUtils.toSetter(getterMethod);

            setter.invoke(persistedObject, obj);

        }

        return persistedObject;
    }


    /**
     * Check if the recently deserialized entity object was populated with its ID field
     *
     * @param entity the object
     * @return an object value if the id exists, null if no id has been set
     */
    private Object getId(Object entity) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {

        for(Method method : ReflectionUtils.getAllDeclaredMethods(entity.getClass())) {
            if(method.getAnnotation(Id.class) != null) {
                method.setAccessible(true);
                return method.invoke(entity);
            }
        }

        return null;
    }


    private <T> T parse(String json, Class<T> clazz) throws JsonParseException, IOException {
        try {
            return objectMapper.readValue(json, clazz);
        } catch(JsonMappingException e) {
            throw new ValidationException(e);
        }
    }

    public void setDoLog(boolean doLog) {
        this.doLog = doLog;
    }

}

这篇关于使用Jackson Mixins和MappingJacksonHttpMessageConverter&amp; Spring MVC的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆