Java 将Jackson Mixin与MappingJacksonHttpMessageConverter一起使用&;春季MVC
我马上就要开始真正的问题了,有没有办法访问HttpMessageConverter中控制器处理程序方法上的注释?我很确定答案是否定的(在浏览了Spring的源代码之后) 使用时是否有其他方式使用配对?我已经基于MappingJacksonHttpMessageConverter实现了我自己的HttpMessageConverter,以“升级”它以使用Jackson 2.0 控制器类Java 将Jackson Mixin与MappingJacksonHttpMessageConverter一起使用&;春季MVC,java,json,spring-mvc,jackson,Java,Json,Spring Mvc,Jackson,我马上就要开始真正的问题了,有没有办法访问HttpMessageConverter中控制器处理程序方法上的注释?我很确定答案是否定的(在浏览了Spring的源代码之后) 使用时是否有其他方式使用配对?我已经基于MappingJacksonHttpMessageConverter实现了我自己的HttpMessageConverter,以“升级”它以使用Jackson 2.0 控制器类 @Controller public class Controller { @JsonFilter({
@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();
}
}
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
...
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) {
//Obviously, no access to the HandlerMethod here.
}
...
}
我到处寻找这个答案。到目前为止,我只看到有人在控制器的处理方法中将其对象序列化为JSON(在每个方法中都反复违反JSON)。或者直接注释它们的数据对象(没有关于如何公开对象的解耦或多个配置)
这可能是在HttpMessageConverter中无法完成的。还有其他选择吗?拦截器可以访问HandlerMethod,但不能访问handler方法的返回对象 这不是理想的解决方案。请参阅我的第二个答案。 我使用
modelandviewsolver
解决了这个问题。您可以直接使用AnnotationMethodHandlerAdapter
注册这些属性,因为您知道它们总是在默认处理发生之前首先启动。因此,Spring的文档-
/**
* Set a custom ModelAndViewResolvers to use for special method return types.
* <p>Such a custom ModelAndViewResolver will kick in first, having a chance to resolve
* a return value before the standard ModelAndView handling kicks in.
*/
public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver};
}
看看resolveModelAndView中所有这些有趣的参数
!我几乎可以访问Spring知道的关于该请求的所有信息。下面是我如何实现接口,使其与映射JacksonHttpMessageConverter非常相似,只是以单向方式(向外):
这是非常棒的简单。ModelAndViewResolver将自动将返回对象转换为JSON,并应用带注释的混合插件
这其中的一个“缺点”(如果你这么说的话)是必须恢复到Spring2.5的配置方式,因为新的3.0标签不允许直接配置ModelAndViewResolver。也许他们只是忽略了这一点
我的旧配置(使用Spring 3.1样式)
我的新配置(使用Spring 2.5样式)
^^3.0+无法连接自定义ModelAndViewResolver。因此,切换回旧式
以下是自定义注释:
Json
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {
/**
* A list of Jackson Mixins.
* <p>
* {@link http://wiki.fasterxml.com/JacksonMixInAnnotations}
*/
JsonMixin[] mixins() default {};
}
public @interface JsonMixin {
public Class<? extends Serializable> target();
public Class<?> mixin();
}
@Target({ElementType.METHOD})
@保留(RetentionPolicy.RUNTIME)
public@interface Json{
/**
*杰克逊的混血儿名单。
*
*{@linkhttp://wiki.fasterxml.com/JacksonMixInAnnotations}
*/
JsonMixin[]mixins()默认值{};
}
JsonMixin
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Json {
/**
* A list of Jackson Mixins.
* <p>
* {@link http://wiki.fasterxml.com/JacksonMixInAnnotations}
*/
JsonMixin[] mixins() default {};
}
public @interface JsonMixin {
public Class<? extends Serializable> target();
public Class<?> mixin();
}
public@interface JsonMixin{
公共类mixin();
}
在发布了下面的答案后,我改变了我的做法。我使用了handler方法returnvaluehandle
r。我必须创建一个编程web配置来覆盖顺序,因为自定义返回值处理程序是最后触发的。我需要在默认设置之前触发它们
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
...
}
希望这将引导某人朝着比我下面的答案更好的方向前进
这允许我将任何对象直接序列化为JSON。在@RequestMapping has=“application/json”中,我总是将返回值序列化为json
我对参数绑定做了同样的事情,只是使用了HandlerMethodArgumentResolver
。只需使用您选择的注释对类进行注释(我使用JPA@Entity,因为我通常会序列化到模型中)
现在,在Spring控制器中实现了从POJO到JSON的无缝反序列化,而无需任何样板代码
另外:我的参数解析器将检查@Id标记,以确定参数的类型,如果JSON包含Id的键,则检索实体并将JSON应用于持久化对象。砰
/**
* 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;
}
}
/**
*将JSON反序列化为Java对象。
*
*还提供了简单数据类型验证的处理。如果抛出{@link JsonMappingException},则
*包装为{@link ValidationException}并由MVC/验证框架处理。
*
*@作者约翰·斯特里克勒
*@自2012年8月28日起
*/
公共类EntityArgumentResolver实现HandlerMethodArgumentResolver{
@自动连线
私人会话工厂会话工厂;
私有最终ObjectMapper ObjectMapper=新ObjectMapper();
私有静态最终记录器log=Logger.getLogger(EntityArgumentResolver.class);
//是否记录传入的JSON
私有布尔值doLog=false;
@凌驾
公共布尔支持参数(MethodParameter){
返回参数.getParameterType().getAnnotation(Entity.class)!=null;
}
@凌驾
公共对象resolveArgument(MethodParameter参数、ModelAndViewContainer mavContainer、NativeWebRequest webRequest、WebDataBinderFactory binderFactory)引发异常{
HttpServletRequest=webRequest.getNativeRequest(HttpServletRequest.class);
String requestBody=IOUtils.toString(request.getReader());
Class targetClass=parameter.getParameterType();
Object entity=this.parse(requestBody,targetClass);
对象entityId=getId(实体);
if(doLog){
日志信息(请求主体);
}
if(entityId!=null){
返回CopyObjectToPersistentity(实体、getKeyValueMap(requestBody)、entityId);
}否则{
返回实体;
}
}
/**
*@param rawJson一个json编码的字符串
*@return一个{@link Map}由JSON编码字符串的键/值对组成
*/
@抑制警告(“未选中”)
私有映射getKeyValueMap(字符串rawJson)抛出JsonParseException、JsonMappingException、IOException{
返回对象映射器
public @interface JsonMixin {
public Class<? extends Serializable> target();
public Class<?> mixin();
}
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
...
}
/**
* 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;
}
}
@EnableWebMvc
@Configuration
public class SidekickApplicationConfiguration implements WebMvcConfigurer {
private static Logger logger = LoggerFactory.getLogger(SidekickApplicationConfiguration.class);
@Autowired
private RequestMappingHandlerAdapter requestHandler;
@Bean
RequestResponseBodyMethodProcessor registerReturnValueHandler() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new MappingJackson2HttpMessageConverter(PushCard.getObjectMapperForDTO()));
logger.info("Registering RequestResponseBodyMethodProcessor with DTO ObjectMapper...");
RequestResponseBodyMethodProcessor r = new RequestResponseBodyMethodProcessor(messageConverters);
requestHandler.setReturnValueHandlers(Arrays.asList(r));
return r;
}
}