【问题标题】:Running SpingBoot app on multiple ports with different controllers在具有不同控制器的多个端口上运行 Spring Boot 应用程序
【发布时间】:2021-03-14 11:16:49
【问题描述】:

我目前正在 Spring Boot 2.4.0 中编写一个应用程序,该应用程序需要侦听多个端口(具体来说是 3 个,但将来可能是 4 个)。这个想法是每个端口都为其他服务/应用程序提供不同的 API 来连接它。

所以,对于一个最小的工作示例,我会说我们有一个这样的 SpringBootApp:

@SpringBootApplication
public class MultiportSpringBoot {

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

现在,我希望在 3 个不同的端口上进行监听,例如 8080、8081 和 8082。对于对其中一个端口的所有(!)请求,应该由特定的控制器“负责”。此要求的原因之一是一个控制器需要处理(常规)Web 前端,另一个控制器需要处理 API。如果收到无效请求,API 控制器需要提供与前端不同的错误消息。因此,给出的要求是明确的分离。

所以我想象不同端口有多个控制器,例如:

@Controller
public class Controller8080
{
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView test8080()
    {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("test8080");
        return modelAndView;
    }
}

其他端口使用类似的控制器:

@Controller
public class Controller8081
{
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ResponseEntity test8081()
    {
        JSONObject stuff = doSomeStuffForPort8081();

        return new ResponseEntity<String>(stuff, HttpStatus.OK);
    }
}

我希望类似于@RequestMapping 的注解能够匹配和修复控制器的端口号,但这似乎是不可能的,因为似乎不存在这样的注解。

现在,这个主题似乎有点具体,这可能是您在网络上找不到太多信息的原因。我找到了Starting Spring boot REST controller in two ports,但我也只能运行一个实例。我查看了https://tech.asimio.net/2016/12/15/Configuring-Tomcat-to-Listen-on-Multiple-ports-using-Spring-Boot.html,但这对于 Spring Boot 2.4.0 来说已经过时了,而且 JavaMelody 示例有点臃肿。

任何人都可以为此提供解决方案的最低工作示例吗?

--

编辑:

澄清一点:我需要多个单独的 RESTController,每个 RESTController 处理不同端口上的请求。 IE。对domain.com:8080/ 的请求应由与对domain.com:8081/ 的请求不同的控制器处理。

例如,考虑以下两个控制器,它们应该分别处理端口 8080 和 8081 上的请求:

//controller for port 8080
@RestController
public class ControllerA
{
    @GetMapping("/")
    String helloA(HttpServletRequest request)
    {
        return "ControllerA at port " + request.getLocalPort();
    }
}

//controller for port 8081
@RestController
public class ControllerB
{
    @GetMapping("/")
    String helloB(HttpServletRequest request)
    {
        return "ControllerB at port " + request.getLocalPort();
    }
}

【问题讨论】:

  • 如前所述,追求这样的设计有多种原因。清晰分离 API 的便利性只是其中之一。无论如何,我无权推翻整个架构。
  • 你如何使用像 nginx 这样的反向代理,将不同端口的调用转发到你的应用程序?一个 nginx 实例用于侦听端口并转发到您的 api 和一个应用程序实例。
  • 感谢您的想法,但我仅限于带有嵌入式 Tomcat 的 SpringBoot 2.4.0,没有其他外部服务

标签: java spring-boot controller


【解决方案1】:

tomcat 类名发生了一些变化,因此您提供的链接具有旧代码,但对于新代码来说已经足够了。下面的代码展示了如何在 spring boot 2.4 中打开多个端口

@Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(additionalConnector());
        return tomcat;
    }

private Connector[] additionalConnector() {
    if (!StringUtils.hasLength(this.additionalPorts)) {
        return null;
    }
    String[] ports = this.additionalPorts.split(",");
    List<Connector> result = new ArrayList<>();
    for (String port : ports) {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(Integer.valueOf(port));
        result.add(connector);
    }
    return result.toArray(new Connector[]{});
} 

对于使用不同的控制器响应不同的端口,您可以实现检查 getLocalPort 之类的逻辑并做出相应的响应。

@GetMapping("/hello")
String hello(HttpServletRequest request) {
    return "hello from " + request.getLocalPort();
}

或者您可以在过滤器中编写逻辑控制器。下面的示例代码

 @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        if (req.getLocalPort() == 8882 && req.getRequestURI().startsWith("/somefunction")) {
            res.sendError(HttpServletResponse.SC_FORBIDDEN);
        } else {
            fc.doFilter(request, response);
        }
    }

你可以在这里找到所有运行的例子https://github.com/ozkanpakdil/spring-examples/tree/master/multiport

这就是它在我本地的样子

为了与不同的控制器有相同的路径,你可以在类(check) 的顶部使用@RequestMapping("/controllerNO"),NO 应该是数字 1 、 2,否则 spring 会抱怨“你有相同的路径”,会给你这个例外

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testController2' method 
com.mascix.multiport.TestController2#hello(HttpServletRequest)
to {GET [/hello]}: There is already 'testController1' bean method

因为从设计上spring只允许一个路径对应一个控制器,在requestmapping之后你可以将过滤器更改为this。关于反射的好处是你会学到非常不同的例外。 java.lang.NoSuchMethodExceptionjava.lang.IllegalArgumentException

最新代码如何在我的本地工作

我必须说这种做法是不正确的 并且违背了spring的设计,为了不同的端口与不同的控制器,有多个JVM。如果你混合逻辑,你将更难解决未来的问题和实现新功能。

如果你必须在一个jvm中做,写一个服务层,从一个控制器单独调用函数,编写如下逻辑

@GetMapping("/hello")
    String hello(HttpServletRequest request) {
        if (request.getLocalPort() == 8888) {
            return service.hellofrom8888();
        }
        if (request.getLocalPort() == 8889) {
            return service.hellofrom8889();
        }

        return "no repsonse ";
    }

至少这将易于维护和调试。不过看起来仍然“丑陋”:)

【讨论】:

  • 感谢您的详细解答。无论如何,我看不到这是如何根据收到请求的端口将请求映射到不同的控制器我需要的是单独的控制器,它们用于不同端口上的请求。 IE。在端口 8080 上传入的“/x”请求需要由不同的(!)控制器处理,而不是在端口 8081 上接收到的对“/x”的相同请求。我玩过你提供的代码,但我可以'看不到这是如何实现所需的解决方案的,因为通过所有端口的所有请求都由同一个控制器处理。我错过了什么吗?
  • 您好,再次感谢您的详细解答。为我抛出异常,但我会进一步研究。
  • 感谢建议的代码;我注意到 this.additionalPorts 没有在任何地方定义......它是一个数组,来自属性的字符串,还是什么?请问您是否删除了一个类名如Connector 的bean,它有助于总结您所指的导入。
【解决方案2】:

Özkan 已经提供了有关如何通过基于 TomcatServletWebServerFactory 提供您自己的 ServletWebServerFactory @Bean 来让 Tomcat 监听多个端口的详细信息。

至于映射,这个方法怎么样:

将@RequestMapping("/8080") 添加到您的控制器(方法保留其特定的@RequestMapping)

@Controller
@RequestMapping("/8080")
public class Controller8080
{
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView test8080()
    {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("test8080");
        return modelAndView;
    }
}

定义你自己的 RequestMappingHandlerMapping 为

public class PortBasedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Override
    protected HandlerMethod lookupHandlerMethod(final String lookupPath, final HttpServletRequest request) throws Exception {
        return super.lookupHandlerMethod(request.getLocalPort() + lookupPath, request);
    }
}

并使用它

@Bean
public WebMvcRegistrations webMvcRegistrationsHandlerMapping() {
    return new WebMvcRegistrations() {

        @Override
        public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
            return new PortBasedRequestMappingHandlerMapping();
        }
    };
}

这将尝试将请求映射到端口 8080 上的 /foobar 到 /8080/foobar。

【讨论】:

【解决方案3】:

另一种方法是使用org.springframework.web.servlet.mvc.condition.RequestCondition,我认为它更干净https://stackoverflow.com/a/69397870/6166627

【讨论】:

    猜你喜欢
    • 2019-07-29
    • 1970-01-01
    • 2018-04-07
    • 2016-02-15
    • 2015-07-26
    • 2020-05-17
    • 1970-01-01
    • 2020-10-26
    • 2020-04-02
    相关资源
    最近更新 更多