java - resource - Cómo configurar MappingJackson2HttpMessageConverter registrado por spring-hateoas
spring boot hateoas (3)
Encontré una solución fea para mi problema:
Estoy usando un BeanPostProcessor y mucha magia de reflexión, para reemplazar el Servicio de Conversión interno de Spring HATEOAS con el mío, que se ha agregado al contexto de Spring anteriormente. De esta manera, me aseguro de que Spring HATEOAS use exactamente el mismo Servicio de Conversión que Spring MVC.
/**
* This is a HACK to work around a not yet implemented feature. At the moment Spring Hateoas uses a
* {@link ConversionService}, which is hold in a private static final field and hence cannot be accessed to add more
* Converters<br/>
*
* <ul>
* <li><a href="https://github.com/spring-projects/spring-hateoas/issues/118">Spring Hateoas Issue</a></li>
* <li><a
* href="http://stackoverflow.com/questions/22240155/converter-from-pathvariable-domainobject-to-string-using-controllerlinkbuilde">
* Solution on Stackoverflow</a></li>
* </ul>
*/
public static class HateoasConversionServicePostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof ConversionService) {
try {
Class<?> clazz = Class.forName(
"org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor$BoundMethodParameter");
Field field = clazz.getDeclaredField("CONVERSION_SERVICE");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, bean);
modifiersField.setInt(field, field.getModifiers() & Modifier.FINAL);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
return bean;
}
}
Me gusta usar spring-hateoas en mi proyecto y configurarlo con @EnableHypermediaSupport
. El problema ahora es que esta anotación de configuración mágica registra su propio MappingJackson2HttpMessageConverter
y mi propio convertidor personalizado se ignorará.
Antecedentes: agregué algunos módulos de Jackson (como JodaModule
) a mi proyecto y quiero que se registren usando objectMapper.findAndRegisterModules();
. Esto se hace anulando a configureMessageConverters(List<HttpMessageConverter<?>> converters)
en WebMvcConfigurationSupport
o WebMvcConfigurer
.
Mi configuración actual se ve así:
@Configuration
@EnableHypermediaSupport(type = HAL)
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.getObjectMapper().findAndRegisterModules();
converters.add(converter);
}
}
¿Hay alguna manera de personalizar MappingJackson2HttpMessageConverter
o el ObjectMapper
que es utilizado por spring-hateoas?
Tuve que hacer lo mismo. Con HATEOAS .16 pude hacer funcionar esto ... pero realmente muy feo.
La clave fue que en el HypermediaSupportBeanDefinitionRegistrar la parte que registra el convertidor HAL comprueba si ya hay un convertidor HAL antes de que intente agregar otro. Así que acabo de agregar un HAL Converter en mi WebMVCConfig :: configureMessageConverters a mí mismo.
Algo como:
private static final String DELEGATING_REL_PROVIDER_BEAN_NAME = "_relProvider";
private static final String LINK_DISCOVERER_REGISTRY_BEAN_NAME = "_linkDiscovererRegistry";
private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";
@Autowired
private ListableBeanFactory beanFactory;
private static CurieProvider getCurieProvider(BeanFactory factory) {
try {
return factory.getBean(CurieProvider.class);
} catch (NoSuchBeanDefinitionException e) {
return null;
}
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> baseConverters = new ArrayList<HttpMessageConverter<?>>();
super.configureMessageConverters(baseConverters);
//Need to override some behaviour in the HAL Serializer...so let''s make our own
CurieProvider curieProvider = getCurieProvider(beanFactory);
RelProvider relProvider = beanFactory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
halObjectMapper.registerModule(new Jackson2HalModule());
halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider));
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
halConverter.setObjectMapper(halObjectMapper);
converters.add(halConverter);
}
Esto depende claramente de la implementación, utiliza los detalles de la implementación y realmente no te permite modificar el que @EnableHyperMediaSupport crea para ti ... pero funciona por ahora.
Yo uso el siguiente enfoque
@Configuration
@EnableHypermediaSupport(type = HypermediaType.HAL)
public class MvcConfig {
@Bean
public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {