0

私はRESTコントローラーを持っています:

@RequestMapping(value = "greeting", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
@Transactional(readOnly = true)
@ResponseBody
public HttpEntity<GreetingResource> greetingResource(@RequestParam(value = "message", required = false, defaultValue = "World") String message) {
    GreetingResource greetingResource = new GreetingResource(String.format(TEMPLATE, message));
    greetingResource.add(linkTo(methodOn(AdminController.class).greetingResource(message)).withSelfRel());
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "application/json; charset=utf-8");
    return new ResponseEntity<GreetingResource>(greetingResource, responseHeaders, HttpStatus.OK);
}

ご覧のとおり、コントローラーから返されるコンテンツ タイプを指定しようと懸命に努力しています。

REST クライアントでアクセスします。

public String getGreetingMessage() {
    String message;
    try {
        HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + "mypassword");
        ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
        GreetingResource greetingResource = responseEntity.getBody();
        message = greetingResource.getMessage();
    } catch (HttpMessageNotReadableException e) {
        message = "The GET request FAILED with the message being not readable: " + e.getMessage();
    } catch (HttpStatusCodeException e) {
        message = "The GET request FAILED with the HttpStatusCode: " + e.getStatusCode() + "|" + e.getStatusText();
    } catch (RuntimeException e) {
        message = "The GET request FAILED " + ExceptionUtils.getFullStackTrace(e);
    }
    return message;
}

http ヘッダーはユーティリティによって作成されます。

static public HttpHeaders createAuthenticationHeaders(String usernamePassword) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
    byte[] encodedAuthorisation = Base64.encode(usernamePassword.getBytes());
    headers.add("Authorization", "Basic " + new String(encodedAuthorisation));
    return headers;
}

Web セキュリティの構成とコードは正常に機能します。これは、成功する mockMvc ベースの統合テストを使用して確認します。

失敗する唯一のテストは、REST テンプレートに基づくものです。

@Test
public void testGreeting() throws Exception {
    mockServer.expect(requestTo("/admin/greeting")).andExpect(method(HttpMethod.GET)).andRespond(withStatus(HttpStatus.OK));
    String message = adminRestClient.getGreetingMessage();
    mockServer.verify();
    assertThat(message, allOf(containsString("Hello"), containsString("World")));
}

Maven ビルド コンソールの出力に表示される例外は次のとおりです。

java.lang.AssertionError: 
Expected: (a string containing "Hello" and a string containing "World")
got: "The GET request FAILED org.springframework.web.client.RestClientException : Could not extract response: no suitable HttpMessageConverter found for response type [class com.thalasoft.learnintouch.rest.resource.GreetingR esource] and content type [application/octet-stream]\n\tat org.springframework.web.client.HttpMessageConverte rExtractor.extractData(HttpMessageConverterExtract or.java:107)

Java 1.6 バージョンで Spring Framework 3.2.2.RELEASE バージョンと Spring Security 3.1.4.RELEASE バージョンを使用しています。

最初は、必要最小限の REST テンプレートがありました。

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    return restTemplate;
}

それが役立つことを願って、私は今それに追加しました:

private static final Charset UTF8 = Charset.forName("UTF-8");

@Bean
public RestTemplate restTemplate() {
    List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();        
    mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
    messageConverters.add(mappingJackson2HttpMessageConverter);

    Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
    jaxb2Marshaller.setClassesToBeBound(new Class[] {
        GreetingResource.class
    });
    MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
    messageConverters.add(marshallingHttpMessageConverter);

    messageConverters.add(new ByteArrayHttpMessageConverter());
    messageConverters.add(new FormHttpMessageConverter());
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
    messageConverters.add(stringHttpMessageConverter);
    messageConverters.add(new BufferedImageHttpMessageConverter());
    messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
    messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setMessageConverters(messageConverters);
    return restTemplate;
}

しかし、何も変わっておらず、例外は同じままです。

私の理解では、特定の JSON 構成を必要とするのは REST テンプレートではなく、何らかの理由で、コントローラーがアプリケーション/json コンテンツ タイプではなく、アプリケーション/オクテット ストリーム コンテンツ タイプを吐き出しているということです。

どんな手掛かり?

いくつかの追加情報...

Web テスト構成の管理レスト クライアント Bean:

@Configuration
public class WebTestConfiguration {

    @Bean
    public AdminRestClient adminRestClient() {
        return new AdminRestClient();
    }

    @Bean
    public RestTemplate restTemplate() {
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();        
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
        messageConverters.add(mappingJackson2HttpMessageConverter);

        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setClassesToBeBound(new Class[] {
            Greeting.class
        });
        MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
        messageConverters.add(marshallingHttpMessageConverter);

        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(new FormHttpMessageConverter());
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
        messageConverters.add(stringHttpMessageConverter);
        messageConverters.add(new BufferedImageHttpMessageConverter());
        messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setMessageConverters(messageConverters);
        return restTemplate;
    }

}

基本テストクラス:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration( classes = { ApplicationConfiguration.class, WebSecurityConfig.class, WebConfiguration.class, WebTestConfiguration.class })
@Transactional
public abstract class AbstractControllerTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    @Autowired  
    protected RestTemplate restTemplate;  

    protected MockRestServiceServer mockServer;

    @Before
    public void setup() {
        this.mockServer = MockRestServiceServer.createServer(restTemplate);
    }

}

ウェブ初期化クラス:

public class WebInit implements WebApplicationInitializer {

    private static Logger logger = LoggerFactory.getLogger(WebInit.class);

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        registerListener(servletContext);

        registerDispatcherServlet(servletContext);

        registerJspServlet(servletContext);

        createSecurityFilter(servletContext);
    }

    private void registerListener(ServletContext servletContext) {
        // Create the root application context
        AnnotationConfigWebApplicationContext appContext = createContext(ApplicationConfiguration.class, WebSecurityConfig.class);

        // Set the application display name
        appContext.setDisplayName("LearnInTouch");

        // Create the Spring Container shared by all servlets and filters
        servletContext.addListener(new ContextLoaderListener(appContext));
    }

    private void registerDispatcherServlet(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext webApplicationContext = createContext(WebConfiguration.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(webApplicationContext));
        dispatcher.setLoadOnStartup(1);

        Set<String> mappingConflicts = dispatcher.addMapping("/");

        if (!mappingConflicts.isEmpty()) {
          for (String mappingConflict : mappingConflicts) {
            logger.error("Mapping conflict: " + mappingConflict);
          }
          throw new IllegalStateException(
              "The servlet cannot be mapped to '/'");
        }
    }

    private void registerJspServlet(ServletContext servletContext) {
    }

    private AnnotationConfigWebApplicationContext createContext(final Class... modules) {
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(modules);
        return appContext;
    }

    private void createSecurityFilter(ServletContext servletContext) {
        FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
        springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
    }

}

ウェブ構成:

@Configuration
@EnableWebMvc
@EnableEntityLinks
@ComponentScan(basePackages = "com.thalasoft.learnintouch.rest.controller")
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        PageableArgumentResolver resolver = new PageableArgumentResolver();
        resolver.setFallbackPageable(new PageRequest(1, 10));
        resolvers.add(new ServletWebArgumentResolverAdapter(resolver));
        super.addArgumentResolvers(resolvers);
    }

}

アプリケーション構成は今のところ空です:

@Configuration
@Import({ ApplicationContext.class })
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {

    // Declare "application" scope beans here, that is, beans that are not only used by the web context

}
4

2 に答える 2

1

これはバグでMockHttpServletRequestあり、説明しようとします。トラッカーの問題https://jira.springsource.org/browse/SPR-11308#comment-97327 バージョン 4.0.1 で修正済み

バグ

DispatcherServletいくつかの RequestConditions を使用してそれを呼び出すメソッドを探しているとき。それらの1つはですConsumesRequestCondition。以下はコードの一部です。

@Override
    protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
        try {
            MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
                    MediaType.parseMediaType(request.getContentType()) :
                    MediaType.APPLICATION_OCTET_STREAM;
            return getMediaType().includes(contentType);
        }
        catch (IllegalArgumentException ex) {
            throw new HttpMediaTypeNotSupportedException(
                    "Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
        }
    }

私たちはピースに興味がありrequest.getContentType()ます。お願いがありMockHttpServletRequestます。メソッド getContentType() を見てみましょう。

public String getContentType() {
    return this.contentType;
}

の値を返すだけですthis.contentTypeヘッダーから値を返しません。そしてthis.contentType常に NULL です。次にcontentTypematchMediaTypeメソッド内は常にになりますMediaType.APPLICATION_OCTET_STREAM

解決

私は多くの方法を試しましたが、機能する方法は 1 つしか見つかりませんでした。

  • org.springframework.test.web.clientテスト ディレクトリにパッケージを作成します。
  • のコピーを作成しますが、名前をorg.springframework.test.web.client.MockMvcClientHttpRequestFactory変更します。たとえば、名前を に変更しFixedMockMvcClientHttpRequestFactoryます。
  • 行を検索:

    MvcResult mvcResult = MockMvcClientHttpRequestFactory.this.mockMvc.perform(requestBuilder).andReturn();
    
  • 次のコードに置き換えます。

    MvcResult mvcResult = FixedMockMvcClientHttpRequestFactory.this.mockMvc.perform(new RequestBuilder() {
        @Override
        public MockHttpServletRequest buildRequest(ServletContext servletContext) {
            MockHttpServletRequest request = requestBuilder.buildRequest(servletContext);
            request.setContentType(request.getHeader("Content-Type"));
            return request;
        }
    }).andReturn();
    
  • ClientHttpReque を登録します

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(MockMvc mockMvc) {
        return new FixedMockMvcClientHttpRequestFactory(mockMvc);
    }
    

私はそれが美しい解決策ではないことを知っていますが、うまくいきます。

于 2014-01-14T14:10:12.917 に答える