我会得到我的真正的问题/问题往右走, 有没有什么办法来访问注解控制器的处理方法HttpMessageConverter里面 ? 我敢肯定的答案是否定的(通过Spring的源代码后步行)。
有没有使用任何其他方式杰克逊混入使用时配对MappingJacksonHttpMessageConverter ? 我已经实现了基于MappingJacksonHttpMessageConverter“升级”,它使用2.0杰克逊我自己HttpMessageConverter。
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
是一个自定义注解我想传递给映射器,然后可以直接被自动馈送到ObjectMapper。
MappingJacksonHttpMessageConverter.class
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
...
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) {
//Obviously, no access to the HandlerMethod here.
}
...
}
我已经搜查甚广为这个答案。 到目前为止,我只看到人的序列化对象JSON控制器的处理方法(违反内部DRY原则,多次在每一个方法)。 或注释的数据对象直接(不脱钩或者对如何暴露你的对象的多个配置)。
这可能是因为它不能在HttpMessageConverter来完成。 是否有其他选择? 拦截给访问HandlerMethod但不给处理程序方法返回的对象。
这不是理想的解决方案。 见我的第二个答案。
我解决了这个使用ModelAndViewResolver
。 您可以直接与注册这些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};
}
纵观ModelAndViewResolver
界面,我知道,它包含了扩展一些功能到处理方法是如何工作所需的所有参数。
public interface ModelAndViewResolver {
ModelAndView UNRESOLVED = new ModelAndView();
ModelAndView resolveModelAndView(Method handlerMethod,
Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel,
NativeWebRequest webRequest);
}
看看所有那些好吃的论点resolveModelAndView
! 我不得不几乎所有Spring知道有关请求访问。 下面是如何实现的接口作用非常相似, MappingJacksonHttpMessageConverter
除了单向的方式(向外):
public class JsonModelAndViewResolver implements ModelAndViewResolver {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
public static final MediaType DEFAULT_MEDIA_TYPE = new MediaType("application", "json", DEFAULT_CHARSET);
private boolean prefixJson = false;
public void setPrefixJson(boolean prefixJson) {
this.prefixJson = prefixJson;
}
/**
* Converts Json.mixins() to a Map<Class, Class>
*
* @param jsonFilter Json annotation
* @return Map of Target -> Mixin classes
*/
protected Map<Class<?>, Class<?>> getMixins(Json jsonFilter) {
Map<Class<?>, Class<?>> mixins = new HashMap<Class<?>, Class<?>>();
if(jsonFilter != null) {
for(JsonMixin jsonMixin : jsonFilter.mixins()) {
mixins.put(jsonMixin.target(), jsonMixin.mixin());
}
}
return mixins;
}
@Override
public ModelAndView resolveModelAndView(Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel, NativeWebRequest webRequest) {
if(handlerMethod.getAnnotation(Json.class) != null) {
try {
HttpServletResponse httpResponse = webRequest.getNativeResponse(HttpServletResponse.class);
httpResponse.setContentType(DEFAULT_MEDIA_TYPE.toString());
OutputStream out = httpResponse.getOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setMixInAnnotations(getMixins(handlerMethod.getAnnotation(Json.class)));
JsonGenerator jsonGenerator =
objectMapper.getJsonFactory().createJsonGenerator(out, JsonEncoding.UTF8);
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
objectMapper.writeValue(jsonGenerator, returnValue);
out.flush();
out.close();
return null;
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return UNRESOLVED;
}
}
上面使用的唯一的自定义类是我的注释类@Json
其中包括一个参数调用mixins
。 以下是我实现这个控制器侧。
@Controller
public class Controller {
@Json({ @JsonMixin(target=MyTargetObject.class, mixin=MyTargetMixin.class) })
@RequestMapping(value="/my-rest/{id}/my-obj", method=RequestMethod.GET)
public @ResponseBody List<MyTargetObject> getListOfFoo(@PathVariable("id") Integer id) {
return MyServiceImpl.getInstance().getBarObj(id).getFoos();
}
}
这是一些非常简单真棒。 该ModelAndViewResolver将返回的对象会自动转换成JSON和应用注释的组合插件以及。
在一个“不好的一面”(如果你称呼它)这是有恢复到配置此,因为新的3.0标签不允许直接配置ModelAndViewResolver的Spring 2.5的方式。 也许他们只是忽略了这一点?
我的老配置(使用Spring 3.1风格)
<mvc:annotation-driven />
我的新配置(使用Spring 2.5风格 )
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customModelAndViewResolvers">
<list>
<bean class="my.package.mvc.JsonModelAndViewResolver" />
</list>
</property>
</bean>
^^ 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 {};
}
JsonMixin
public @interface JsonMixin {
public Class<? extends Serializable> target();
public Class<?> mixin();
}
张贴下面的答案后,我改变了我是如何做到这一点。 我用了一个HandlerMethodReturnValueHandle
河 我不得不创建一个纲领性的Web配置覆盖顺序,因为自定义的返回值处理程序被最后触发。 我需要他们的默认值之前被触发。
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
...
}
希望这将导致有人比我的回答如下更好的方向发展。
这让我直接序列化的任何对象到JSON。 在@RequestMapping已经产生=“应用/ JSON”,那么我会永远序列化的返回值成JSON。
我也做了同样的事情参数绑定除了我使用的HandlerMethodArgumentResolver
。 只是你选择的注解注释你的类(我使用JPA @Entity,因为我通常被序列化为型号)。
您现在可以在Spring控制器无缝POJO到JSON德/序列化,而不需要任何boilerplater代码。
奖励:该参数解析器我会为@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;
}
}