Spring 启动应用程序在尝试 return JSON 来自端点的响应时因 UnknownContentTypeException 而停止

Spring boot app stops with UnknownContentTypeException when trying to return JSON response from endpoint

我正在尝试制作一个 Spring 启动应用程序,它使用 SWAPI 的 API 端点并将结果优化到其中的一些端点。为了更好地解释,我想获取用户从 GET 请求中提供的角色的名称,并向他 return 这个角色驾驶的星际飞船。我正在尝试使用 RestTemplate 调用 SWAPI API 并优化这些结果以提供我想要的结果。 从我所做的调试来看,在控制器 class 的 for 循环中调用 resttemplate 时似乎出现了问题,结果是:

2021-05-05 17:13:54.099 ERROR 272 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.example.swapispring.entity.Starship] and content type [text/html]] with root cause

org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.example.swapispring.entity.Starship] and content type [text/html]
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:126) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:778) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:334) ~[spring-web-5.3.6.jar:5.3.6]
    at com.example.swapispring.controller.Controller.getStarships(Controller.java:36) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626) ~[tomcat-embed-core-9.0.45.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.6.jar:5.3.6]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.45.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.6.jar:5.3.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.6.jar:5.3.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.6.jar:5.3.6]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.6.jar:5.3.6]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.45.jar:9.0.45]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

主要方法

package com.example.swapispring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class SwapiWithSpringApplication {

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(SwapiWithSpringApplication.class, args);
    }

}

控制器

package com.example.swapispring.controller;

import com.example.swapispring.entity.Character;
import com.example.swapispring.entity.InitialResponse;
import com.example.swapispring.entity.Starship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/starships")
public class Controller {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/{characterName}")
    public List<Starship> getStarships(@PathVariable("characterName") String characterName) {


        InitialResponse initialResponse = restTemplate.getForObject("https://swapi.dev/api/people/?search=" + characterName, InitialResponse.class);

        ArrayList<Starship> results = new ArrayList<>();

        for (Character tempCharacter:initialResponse.getCharacters()) {
          for (String tempStarship: tempCharacter.getStarships()) {
               Starship resultingObject = restTemplate.getForObject(tempStarship,Starship.class);
               results.add(resultingObject);
          }
        };
        return results;
    }
}

所有实体(InitialResponse、角色、Starship)都是使用 jsonschema2pojo 创建的。由于问题的字符数限制,我无法提供完整的 classes。

P.S。我是新手,正在努力提高有关堆栈溢出问题的质量。如果我的问题格式有误,请随时就此事提供反馈。

经过数小时的调试,问题出现在嵌套 for 循环中的第二次 restTemplate 调用中。调用的 url 是“http”(例如 http://swapi.dev/api/starships/12/)而不是“https”,因此即使 link 在浏览器中自动正常工作,它也无法获得响应重定向到“https”版本。修复了以下内容:

package com.example.swapispring.controller;

import com.example.swapispring.entity.responseOfInhabitantsFromPlanetName.InitialResponseOfPlanets;
import com.example.swapispring.entity.responseOfInhabitantsFromPlanetName.Planet;
import com.example.swapispring.entity.responseOfInhabitantsFromPlanetName.Residents;
import com.example.swapispring.entity.responseOfStarshipsFromCharacterName.Character;
import com.example.swapispring.entity.responseOfStarshipsFromCharacterName.InitialResponseOfCharacters;
import com.example.swapispring.entity.responseOfStarshipsFromCharacterName.Starship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;

@RestController
@RequestMapping("/api")
public class Controller {

    @Autowired
    private RestTemplate restTemplate;

    //Endpoint to return JSON of starships that the given character has piloted
    @RequestMapping("/starships/{characterName}")
    public ArrayList<Starship> getStarships(@PathVariable("characterName") String characterName) {

        //Rest Template get for initial response JSON (The character)
        InitialResponseOfCharacters initialResponseOfCharacters = restTemplate.getForObject("https://swapi.dev/api/people/?search=" + characterName, InitialResponseOfCharacters.class);

        //Array list of starships to be returned as the final response
        ArrayList<Starship> results = new ArrayList<>();
        assert initialResponseOfCharacters != null;
        if (initialResponseOfCharacters.getCharacters().size() == 1) {
            for (Character tempCharacter : initialResponseOfCharacters.getCharacters()) {
                for (String tempStarship : tempCharacter.getStarships()) {
                    tempStarship = tempStarship.replaceAll("http", "https");
                    Starship resultingObject = restTemplate.getForObject(tempStarship, Starship.class);
                    results.add(resultingObject);
                }
            }
            //error handling
        } else {
            Starship starshipError = new Starship();
            starshipError.setAdditionalProperty("Error", "More than one or no characters found please refine your search");
            results.add(starshipError);
        }
        return results;
    }