복붙노트

[SPRING] Spring Boot에서 MappingJackson2HttpMessageConverter를 확장하여 사용자 정의 변환기를 추가하면 기존 변환기를 덮어 쓰는 것처럼 보입니다.

SPRING

Spring Boot에서 MappingJackson2HttpMessageConverter를 확장하여 사용자 정의 변환기를 추가하면 기존 변환기를 덮어 쓰는 것처럼 보입니다.

application / vnd.custom.hal + json과 같은 사용자 지정 미디어 유형에 대한 변환기를 만들려고합니다. 이 대답은 여기에서 보았지만 AbstractHttpMessageConverter (MappingJackson2HttpMessageConverter의 수퍼 클래스)의 보호 된 생성자에 액세스 할 수 없으므로 작동하지 않습니다. 즉, 다음 코드는 작동하지 않습니다.

class MyCustomVndConverter extends MappingJacksonHttpMessageConverter {
    public MyCustomVndConverter (){
        super(MediaType.valueOf("application/vnd.myservice+json"));
    }
}

그러나 다음은 작동하며 기본적으로 생성자가 실제로 수행하는 작업을 모방합니다.

setSupportedMediaTypes(Collections.singletonList(
    MediaType.valueOf("application‌​/vnd.myservice+json")
));

그래서 저는 수업을 위해이 작업을 수행했고, 여기에 Spring Boot의 설명서를 따라 기존 변환기 목록에 변환기를 추가했습니다. 내 코드는 기본적으로 다음과 같습니다.

//Defining the converter; the media-type is simply a custom media-type that is 
//still application/hal+json, i.e., JSON with some additional semantics on top 
//of what HAL already adds to JSON
public class TracksMediaTypeConverter extends MappingJackson2HttpMessageConverter {
    public TracksMediaTypeConverter() {
        setSupportedMediaTypes(Collections.singletonList(
            new MediaType("application‌​", "vnd.tracks.v1.hal+json")
        ));
    }
}

//Adding the message converter
@Configuration
@EnableSwagger
public class MyApplicationConfiguration {

    ...    
    @Bean
    public HttpMessageConverters customConverters() {
        return new HttpMessageConverters(new TracksMediaTypeConverter());
    }
}

설명서에 따라이 작동합니다. 하지만 주목할 점은 application / json; charset = UTF-8과 application / * + json; charset = UTF-8을 처리하는 기존 MappingJackson2HttpMessageCoverter를 대체하는 효과가 있다는 것입니다.

디버거를 내 앱에 연결하고 Spring의 AbstractMessageCoverterMethodProcessor.java 클래스 내에서 중단 점을 단계적으로 실행하여이를 확인했습니다. 여기서 private 필드 messageConverters에는 등록 된 변환기 목록이 들어 있습니다. 일반적으로 변환기를 추가하지 않으면 다음과 같은 표시가 나타납니다.

사용자 지정 미디어 유형을 추가하면 MappingJackson2HttpMessageConverter의 두 번째 인스턴스가 대체됩니다. 즉, 목록은 이제 다음과 같이 보입니다.

왜 이런 일이 일어나고 있는지 나는 완전히 모르겠습니다. 나는 코드를 밟았지만 실제로 발생하는 유일한 것은 MappingJackson2HttpMessageConverter의 no-args 생성자가 호출되어 (처음에 지원되는 미디어 유형을 application / json으로 설정 함), charset = UTF-8 및 application / * + json; charset = UTF-8. 그 후에 목록은 내가 제공 한 미디어 유형으로 덮어 씁니다.

내가 이해할 수없는 것은이 미디어 유형을 추가하면 일반 JSON을 처리하는 MappingJackson2HttpMessageConverter의 기존 인스턴스를 대체해야하는 이유입니다. 이 일을하는 이상한 마법이 있습니까?

현재 해결 방법이 있지만 매우 우아하지 않고 MappingJackson2HttpMessageConverter에 이미 코드가 중복되어 있기 때문에별로 좋아하지 않습니다.

다음 클래스를 만들었습니다 (정규 MappingJackson2HttpMessageConverter의 변경 사항 만 표시됨).

public abstract class ExtensibleMappingJackson2HttpMessageConverter<T> extends AbstractHttpMessageConverter<T> implements GenericHttpMessageConverter<T> {

    //These constructors are not available in `MappingJackson2HttpMessageConverter`, so
    //I provided them here just for convenience.    

    /**
     * Construct an {@code AbstractHttpMessageConverter} with no supported media types.
     * @see #setSupportedMediaTypes
     */
    protected ExtensibleMappingJackson2HttpMessageConverter() {
    }

    /**
     * Construct an {@code ExtensibleMappingJackson2HttpMessageConverter} with one supported media type.
     * @param supportedMediaType the supported media type
     */
    protected ExtensibleMappingJackson2HttpMessageConverter(MediaType supportedMediaType) {
        setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
    }

    /**
     * Construct an {@code ExtensibleMappingJackson2HttpMessageConverter} with multiple supported media type.
     * @param supportedMediaTypes the supported media types
     */
    protected ExtensibleMappingJackson2HttpMessageConverter(MediaType... supportedMediaTypes) {
        setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
    }

    ...

    //These return Object in MappingJackson2HttpMessageConverter because it extends
    //AbstractHttpMessageConverter<Object>. Now these simply return an instance of
    //the generic type. 

    @Override
    protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {

        JavaType javaType = getJavaType(clazz, null);
        return readJavaType(javaType, inputMessage);
    }

    @Override
    public T read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {

        JavaType javaType = getJavaType(type, contextClass);
        return readJavaType(javaType, inputMessage);
    }

    private T readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
        try {
            return this.objectMapper.readValue(inputMessage.getBody(), javaType);
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }

    ...

}

다음과 같이이 클래스를 사용합니다.

public class TracksMediaTypeConverter extends ExtensibleMappingJackson2HttpMessageConverter<Tracks> {
    public TracksMediaTypeConverter() {
        super(new MediaType("application", "application/vnd.tracks.v1.hal+json"));
    }
}

구성 클래스에 변환기 등록은 이전과 동일합니다. 이러한 변경으로 MappingJackson2HttpMessageConverter의 기존 인스턴스를 덮어 쓰지 않고 예상대로 작동합니다.

그래서 모든 것을 끓여 두 가지 질문이 있습니다.

해결법

  1. ==============================

    1.이것이 언제 수정되었는지는 알 수 없지만 1.1.8.RELEASE에서이 문제는 ClassUtils.isAssignableValue를 사용하므로 더 이상 존재하지 않습니다. 정보를 원하면 여기에 원래 답변을 남깁니다.

    이것이 언제 수정되었는지는 알 수 없지만 1.1.8.RELEASE에서이 문제는 ClassUtils.isAssignableValue를 사용하므로 더 이상 존재하지 않습니다. 정보를 원하면 여기에 원래 답변을 남깁니다.

    여기에 여러 가지 문제가있는 것 같아서, 결과로 대답을 요약 해 보겠습니다. 나는 아직도 내가하려고하는 것에 대한 해결책이 없다.하지만 Spring Boot 사람들과 무슨 일이 일어나고 있는지를 의도적으로 이야기 할 것이다.

    이것은 스프링 부트의 버전 1.1.4.RELEASE에 적용됩니다; 다른 버전은 확인하지 않았습니다. HttpMessageConverters 클래스의 생성자는 다음과 같습니다.

    /**
     * Create a new {@link HttpMessageConverters} instance with the specified additional
     * converters.
     * @param additionalConverters additional converters to be added. New converters will
     * be added to the front of the list, overrides will replace existing items without
     * changing the order. The {@link #getConverters()} methods can be used for further
     * converter manipulation.
     */
    public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
        List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
        List<HttpMessageConverter<?>> defaultConverters = getDefaultConverters();
        for (HttpMessageConverter<?> converter : additionalConverters) {
            int defaultConverterIndex = indexOfItemClass(defaultConverters, converter);
            if (defaultConverterIndex == -1) {
                converters.add(converter);
            }
            else {
                defaultConverters.set(defaultConverterIndex, converter);
            }
        }
        converters.addAll(defaultConverters);
        this.converters = Collections.unmodifiableList(converters);
    }
    

    for 루프 내부. indexOfItemClass 메서드를 호출하여 목록의 인덱스를 결정합니다. 그 방법은 다음과 같습니다.

    private <E> int indexOfItemClass(List<E> list, E item) {
        Class<? extends Object> itemClass = item.getClass();
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getClass().isAssignableFrom(itemClass)) {
                return i;
            }
        }
        return -1;
    }
    

    내 수업은 MappingJackson2HttpMessageConverter를 확장하므로 if 문은 true를 반환합니다. 이는 생성자에서 유효한 인덱스가 있음을 의미합니다. 그런 다음 Spring Boot는 기존 인스턴스를 새 인스턴스로 바꿉니다.

    나는 모른다. 그것은 나에게 이상한 것처럼 보이지 않는 것처럼 보입니다.

    일종의. 여길 봐. 그것은 말한다 :

    그러나 기존의 서브 타입이기 때문에 컨버터를 오버라이드하는 것은 도움이되는 행동처럼 보이지 않습니다.

    Spring HATEOAS 라이프 사이클은 Spring Boot와 별개입니다. Spring HATEOAS는 HyperMediaSupportBeanDefinitionRegistrar 클래스에 application / hal + json media-type에 대한 핸들러를 등록합니다. 관련 방법은 다음과 같습니다.

    private List<HttpMessageConverter<?>> potentiallyRegisterModule(List<HttpMessageConverter<?>> converters) {
    
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter) {
                MappingJackson2HttpMessageConverter halConverterCandidate = (MappingJackson2HttpMessageConverter) converter;
                ObjectMapper objectMapper = halConverterCandidate.getObjectMapper();
                if (Jackson2HalModule.isAlreadyRegisteredIn(objectMapper)) {
                    return converters;
                }
            }
        }
    
        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 MappingJackson2HttpMessageConverter();
        halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON)); //HAL_JSON is just a MediaType instance for application/hal+json
        halConverter.setObjectMapper(halObjectMapper);
    
        List<HttpMessageConverter<?>> result = new ArrayList<HttpMessageConverter<?>>(converters.size());
        result.add(halConverter);
        result.addAll(converters);
        return result;
    }
    

    변환기 인수는 동일한 클래스의 postProcessBeforeInitialization 메소드에서이 스 니펫을 통해 전달됩니다. 관련 스 니펫은 다음과 같습니다.

    if (bean instanceof RequestMappingHandlerAdapter) {
        RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
        adapter.setMessageConverters(potentiallyRegisterModule(adapter.getMessageConverters()));
    }
    

    나는 잘 모르겠다. 하위 클래스 인 ExtensibleMappingJackson2HttpMessageConverter (질문에 표시됨)가 당분간 작동합니다. 또 다른 옵션은 사용자 지정 변환기 내부에 MappingJackson2HttpMessageConverter의 개인 인스턴스를 만들고 간단하게 위임하는 것입니다. 어느 쪽이든, 나는 스프링 부트 프로젝트에 관한 문제를 열고 그들로부터 약간의 피드백을 얻을 것이다. 그런 다음 새로운 정보로 답변을 업데이트하겠습니다.

  2. ==============================

    2.스프링 부트 문서에는 명시 적으로 사용자 정의 MappingJackson2HttpMessageConverter를 추가하면 기본값이 대체됩니다.

    스프링 부트 문서에는 명시 적으로 사용자 정의 MappingJackson2HttpMessageConverter를 추가하면 기본값이 대체됩니다.

    문서에서 :

  3. from https://stackoverflow.com/questions/26761845/in-spring-boot-adding-a-custom-converter-by-extending-mappingjackson2httpmessag by cc-by-sa and MIT license