列出所有已部署的休息端点(spring-boot、jersey)

2024-01-12

是否可以使用 Spring Boot 列出我配置的所有休息端点?执行器在启动时列出所有现有路径,我想要类似的自定义服务,这样我就可以在启动时检查所有路径是否配置正确,并使用此信息进行客户端调用。

我该怎么做呢?我用@Path/@GET我的服务 bean 上的注释并通过以下方式注册它们ResourceConfig#registerClasses.

有没有办法查询所有路径的配置?

Update:我通过注册 REST 控制器

@Bean
public ResourceConfig resourceConfig() {
   return new ResourceConfig() {
    {  
      register(MyRestController.class);
    }
   };
}

Update2:我想要有类似的东西

GET /rest/mycontroller/info
POST /res/mycontroller/update
...

动机:当 spring-boot 应用程序启动时,我想打印出所有注册的控制器及其路径,这样我就可以停止猜测要使用哪些端点。


可能最好的方法是使用ApplicationEventListener https://jersey.github.io/apidocs/2.28/jersey/org/glassfish/jersey/server/monitoring/ApplicationEventListener.html。从那里您可以监听“应用程序完成初始化”事件,并获取ResourceModel来自ApplicationEvent. The ResourceModel将拥有所有已初始化的Resources。然后你可以遍历Resource正如其他人提到的。下面是一个实现。部分实现取自DropwizardResourceConfig https://github.com/dropwizard/dropwizard/blob/master/dropwizard-jersey/src/main/java/io/dropwizard/jersey/DropwizardResourceConfig.java#L32执行。

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.ResourceModel;
import org.glassfish.jersey.server.monitoring.ApplicationEvent;
import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EndpointLoggingListener implements ApplicationEventListener {

    private static final TypeResolver TYPE_RESOLVER = new TypeResolver();

    private final String applicationPath;

    private boolean withOptions = false;
    private boolean withWadl = false;

    public EndpointLoggingListener(String applicationPath) {
        this.applicationPath = applicationPath;
    }

    @Override
    public void onEvent(ApplicationEvent event) {
        if (event.getType() == ApplicationEvent.Type.INITIALIZATION_APP_FINISHED) {
            final ResourceModel resourceModel = event.getResourceModel();
            final ResourceLogDetails logDetails = new ResourceLogDetails();
            resourceModel.getResources().stream().forEach((resource) -> {
                logDetails.addEndpointLogLines(getLinesFromResource(resource));
            });
            logDetails.log();
        }
    }

    @Override
    public RequestEventListener onRequest(RequestEvent requestEvent) {
        return null;
    }

    public EndpointLoggingListener withOptions() {
        this.withOptions = true;
        return this;
    }

    public EndpointLoggingListener withWadl() {
        this.withWadl = true;
        return this;
    }

    private Set<EndpointLogLine> getLinesFromResource(Resource resource) {
        Set<EndpointLogLine> logLines = new HashSet<>();
        populate(this.applicationPath, false, resource, logLines);
        return logLines;
    }

    private void populate(String basePath, Class<?> klass, boolean isLocator,
            Set<EndpointLogLine> endpointLogLines) {
        populate(basePath, isLocator, Resource.from(klass), endpointLogLines);
    }

    private void populate(String basePath, boolean isLocator, Resource resource,
            Set<EndpointLogLine> endpointLogLines) {
        if (!isLocator) {
            basePath = normalizePath(basePath, resource.getPath());
        }

        for (ResourceMethod method : resource.getResourceMethods()) {
            if (!withOptions && method.getHttpMethod().equalsIgnoreCase("OPTIONS")) {
                continue;
            }
            if (!withWadl && basePath.contains(".wadl")) {
                continue;
            }
            endpointLogLines.add(new EndpointLogLine(method.getHttpMethod(), basePath, null));
        }

        for (Resource childResource : resource.getChildResources()) {
            for (ResourceMethod method : childResource.getAllMethods()) {
                if (method.getType() == ResourceMethod.JaxrsType.RESOURCE_METHOD) {
                    final String path = normalizePath(basePath, childResource.getPath());
                    if (!withOptions && method.getHttpMethod().equalsIgnoreCase("OPTIONS")) {
                        continue;
                    }
                    if (!withWadl && path.contains(".wadl")) {
                        continue;
                    }
                    endpointLogLines.add(new EndpointLogLine(method.getHttpMethod(), path, null));
                } else if (method.getType() == ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR) {
                    final String path = normalizePath(basePath, childResource.getPath());
                    final ResolvedType responseType = TYPE_RESOLVER
                            .resolve(method.getInvocable().getResponseType());
                    final Class<?> erasedType = !responseType.getTypeBindings().isEmpty()
                            ? responseType.getTypeBindings().getBoundType(0).getErasedType()
                            : responseType.getErasedType();
                    populate(path, erasedType, true, endpointLogLines);
                }
            }
        }
    }

    private static String normalizePath(String basePath, String path) {
        if (path == null) {
            return basePath;
        }
        if (basePath.endsWith("/")) {
            return path.startsWith("/") ? basePath + path.substring(1) : basePath + path;
        }
        return path.startsWith("/") ? basePath + path : basePath + "/" + path;
    }

    private static class ResourceLogDetails {

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

        private static final Comparator<EndpointLogLine> COMPARATOR
                = Comparator.comparing((EndpointLogLine e) -> e.path)
                .thenComparing((EndpointLogLine e) -> e.httpMethod);

        private final Set<EndpointLogLine> logLines = new TreeSet<>(COMPARATOR);

        private void log() {
            StringBuilder sb = new StringBuilder("\nAll endpoints for Jersey application\n");
            logLines.stream().forEach((line) -> {
                sb.append(line).append("\n");
            });
            logger.info(sb.toString());
        }

        private void addEndpointLogLines(Set<EndpointLogLine> logLines) {
            this.logLines.addAll(logLines);
        }
    }

    private static class EndpointLogLine {

        private static final String DEFAULT_FORMAT = "   %-7s %s";
        final String httpMethod;
        final String path;
        final String format;

        private EndpointLogLine(String httpMethod, String path, String format) {
            this.httpMethod = httpMethod;
            this.path = path;
            this.format = format == null ? DEFAULT_FORMAT : format;
        }

        @Override
        public String toString() {
            return String.format(format, httpMethod, path);
        }
    }
}

然后你只需要向 Jersey 注册监听器即可。您可以从以下位置获取应用程序路径JerseyProperties。您需要在 Spring Boot 中设置它application.properties财产下spring.jersey.applicationPath。这将是根路径,就像您要使用@ApplicationPath在你的ResourceConfig子类

@Bean
public ResourceConfig getResourceConfig(JerseyProperties jerseyProperties) {
    return new JerseyConfig(jerseyProperties);
}
...
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig(JerseyProperties jerseyProperties) {
        register(HelloResource.class);
        register(new EndpointLoggingListener(jerseyProperties.getApplicationPath()));
    }
}

需要注意的一件事是,Jersey servlet 上默认未设置启动时加载。这意味着 Jersey 在第一次请求之前不会在启动时加载。因此,在第一个请求之前您不会看到侦听器被触发。我已经打开了an issue https://github.com/spring-projects/spring-boot/issues/5100以获得配置属性,但与此同时,您有几个选择:

  1. 将 Jersey 设置为过滤器,而不是 servlet。过滤器将在启动时加载。对于大多数帖子来说,使用 Jersey 作为过滤器的行为实际上没有任何不同。要配置它,您只需在中添加一个 Spring Boot 属性application.properties

    spring.jersey.type=filter
    
  2. 另一种选择是覆盖 JerseyServletRegistrationBean并设置其loadOnStartup财产。这是一个配置示例。一些实现是直接取自JerseyAutoConfiguration https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java#L77

    @SpringBootApplication
    public class JerseyApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(JerseyApplication.class, args);
        }
    
        @Bean
        public ResourceConfig getResourceConfig(JerseyProperties jerseyProperties) {
            return new JerseyConfig(jerseyProperties);
        }
    
        @Bean
        public ServletRegistrationBean jerseyServletRegistration(
            JerseyProperties jerseyProperties, ResourceConfig config) {
            ServletRegistrationBean registration = new ServletRegistrationBean(
                    new ServletContainer(config), 
                    parseApplicationPath(jerseyProperties.getApplicationPath())
            );
            addInitParameters(registration, jerseyProperties);
            registration.setName(JerseyConfig.class.getName());
            registration.setLoadOnStartup(1);
            return registration;
        }
    
        private static String parseApplicationPath(String applicationPath) {
            if (!applicationPath.startsWith("/")) {
                applicationPath = "/" + applicationPath;
            }
            return applicationPath.equals("/") ? "/*" : applicationPath + "/*";
        }
    
        private void addInitParameters(RegistrationBean registration, JerseyProperties jersey) {
            for (Entry<String, String> entry : jersey.getInit().entrySet()) {
                registration.addInitParameter(entry.getKey(), entry.getValue());
            }
        }
    }
    

UPDATE

所以看起来 Spring Boot 将会add the load-on-startup财产 https://github.com/spring-projects/spring-boot/issues/5100#event-606583303,所以我们不必覆盖 JerseyServletRegistrationBean。将在Boot 1.4.0中添加

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

列出所有已部署的休息端点(spring-boot、jersey) 的相关文章

  • 在 Java 中将日期从 UTC 转换为 PST

    我需要将日期从 Google App Engine 本地服务器时区转换为 Java 中的太平洋时间 我尝试使用 Calendar calstart Calendar getInstance calstart setTimeZone Time
  • setSize() 不起作用?

    我有一个程序 需要两个按钮 一个是常规按钮 另一个具有根据鼠标悬停而变化的图片 目前 由于图片很大 JButton自定义也很大 我可以更改自定义的大小并保持图像 和翻转图像 成比例吗 我尝试过 setSize 但它没有任何作用 对于任何反馈
  • spring-data-neo4j 基本一对多关系不持久

    EDIT 示例项目可在github https github com troig neo4jCustomRepository 我在后端项目中使用 Neo4J Rest 图形数据库 托管在 grapheneDb 中 和 Spring Data
  • Spring JSON序列化、Gson反序列化

    我目前在某些内部对象的反序列化方面遇到问题 在春天 我在使用输出之前初始化所有对象 ResponseBody 例如 这是一个响应 id 1 location id 1 extra location data id 2 location 1
  • 无法解析 Java 中的方法

    我有一个Question具有 4 个的对象Answer里面的物体 在 Question java 我有一个方法是 public Answer getA return a 在另一种方法中我有 if questions get randomNu
  • 在java中查找OSX的版本

    我需要测试 java 中 osx 的版本是否 Try System getProperty os name and or System getProperty os version 它返回字符串 HERE https docs oracle
  • Android 上 WebRTC 的自定义视频源

    Overview 我想使用自定义视频源通过 WebRTC Android 实现来直播视频 如果我理解正确的话 现有的实现仅支持 Android 手机上的前置和后置摄像头 以下类与此场景相关 Camera1Enumerator java ht
  • [重复]

    这个问题在这里已经有答案了 有什么区别List
  • 由于 maven-surefire-plugin,Maven 构建失败

    我这里有类似的问题eclipse 中缺少 maven surefire plugin https stackoverflow com questions 23588957 maven surefire plugin missing in e
  • 使用 Jquery Ajax 将数据从 jsp 发送到 struts2 操作类

    我需要使用 jquery Ajax 将表单数据从 jsp 传递到 struts2 并从 Struts2 操作类接收回 JSON 数据 我已经给出了下面的代码 当我传递 AJAX 数据时 url search action searchTex
  • “传输协议线程失败” – “套接字为 EOF”,使用 Java 进行 J2SSH 连接

    我正在尝试通过我的 Java 代码建立 SSH 连接 但遇到异常 我通过 Putty Winscp 工具测试了我的连接 它工作正常 问题出在我的 Java 代码上 SEVERE The Transport Protocol thread f
  • 我可以直接在 Maven 中使用 GitHub 项目吗?

    我有兴趣使用GitHub 上的项目 https github com toelen spymemcached jcache作为我的项目中的依赖项 GitHub 项目有一个pom文件 我可以修改我的pom文件来使用这个项目 如果是这样 怎么办
  • 无法为对象堆保留足够的空间

    每次尝试运行该程序时 我都会重复出现以下异常 VM初始化期间发生错误 无法为对象堆保留足够的空间 无法创建Java虚拟机 我尝试增加虚拟内存 页面大小 和 RAM 大小 但无济于事 我怎样才能消除这个错误 运行 JVM XX MaxHeap
  • 预检响应中的 Access-Control-Allow-Headers 不允许请求标头字段 Access-Control-Allow-Origin

    当我尝试加载网页时 出现以下错误 预检响应中的 Access Control Allow Headers 不允许请求标头字段 Access Control Allow Origin 我查看了针对此问题的其他答案 它们表明缺乏 CORS 支持
  • 无法实例化类对象的类型 (Java)

    这是我收到错误的代码 在 new 之后的第二个 Killer 处 String classes new String 5 kills 0 Brian Moser kills 1 James Doakes kills 2 Lila Tourn
  • 为什么 writeObject 抛出 java.io.NotSerializedException 以及如何修复它?

    我有这个异常 我不明白为什么会抛出它 或者我应该如何处理它 try os writeObject element catch IOException e e printStackTrace Where element is a Transf
  • JPA中如何连接多个数据库?

    我有一个 Spring Boot 应用程序 当前使用 JPA 连接到单个数据库 application properties 文件中的连接详细信息 spring datasource url jdbc oracle thin localho
  • 运行外部进程的非阻塞线程

    我创建了一个 Java GUI 应用程序 它充当许多低级外部进程的包装器 该实用程序按原样运行 但迫切需要一项重大改进 我希望我的外部进程以非阻塞方式运行 这将允许我并行服务其他请求 简而言之 我希望能够在生成数据时处理来自外部进程的数据
  • Java 和 SQL Server 中的精度噩梦

    我一直在与 Java 和 SQL Server 中的精确噩梦作斗争 直到我不再知道了 就我个人而言 我理解这个问题及其根本原因 但向地球另一端的客户解释这一点是不可行的 至少对我来说 情况是这样的 我在 SQL Server 中有两列 Qt
  • 指定不带组件的GridBagLayout的网格参数

    我试图帮助另一个用户 但遇到了一个问题 我想用 GridBagLayout 来做到这一点 c1 c2 c3 10 80 10 v v r1 B1

随机推荐

  • 如何检查脚本是否通过 dofile() 包含或直接在 Lua 中运行? [复制]

    这个问题在这里已经有答案了 可能的重复 Lua 中的 main 函数 https stackoverflow com questions 4521085 main function in lua 在Python中 您可以很容易地检查脚本是否
  • Primefaces 日历不会在窗口旋转时重新调整其位置

    我正在努力解决移动设备 平板电脑 上呈现的网页上的 primefaces 日历问题 似乎由 primefaces 计算来显示弹出日历的 css 值不会在窗口旋转时重新计算 您需要再次单击它来重新计算 这是一个例子 I have my web
  • 使用 Oslo 和 M 有什么优势?您何时会使用它?

    在参加 Oslo M 的演讲后 我很难看出使用它相对于现有方法的优势以及它在什么情况下有用 我知道它很新 并非所有细节都已发布等 但有人可以给我一些优势以及您何时可以使用它吗 Thanks Alex 这个问题似乎有您正在寻找的答案 模型驱动
  • SwiftUI:minimumScaleFactor 未均匀应用于堆栈元素

    我有两段文本的水平堆栈 第二段以蓝色突出显示 它非常适合 iPhone XR 但在较小的设备 如 iPhone X 上时 文本不适合 我尝试通过使用minimumScaleFactor来缩放文本来解决这个问题 然而 SwiftUI 似乎决定
  • 重温PHP中如何从多维数组中删除重复值

    如何从 PHP 中的多维数组中删除重复值 初始数组 array 0 gt array following userid gt 88 1 gt array following userid gt 89 2 gt array following
  • 判断一系列日期是否覆盖一个区间

    我有两个对象日历 Calendar startCalendar new GregorianCalendar 2013 0 31 Calendar endCalendar new GregorianCalendar 我想知道上面列出的两个日期
  • HTTP 处理程序与 HTTP 模块

    有人能用不到两句话解释两者之间的区别吗 是的 我知道谷歌可以提供数百个答案 但不能提供二分之一的清晰句子 HttpHandler 是请求队列的去向 HttpModule是沿途的一个站
  • CodePipeline 构建规范和多个构建操作

    一个简单的构建规范 例如 version 0 2 phases install commands cd lambda src npm install aws cloudformation package template file lamb
  • mod_rewrite 的小问题

    我有一个分类广告网站 每个分类最初都是这样链接的 mydomain com ad php ad id Bmw M3 M tech 113620829 我应该使用什么 RewriteRule 来使此链接看起来像 mydomain com Bm
  • Node js 向 Gmail 发送会议/日历邀请

    我正在尝试使用 Node js 发送日历邀请 我已经尝试过 nodemailer 库 并且正在发送带有日历邀请的邮件 就像参考this https stackoverflow com questions 45097141 nodemaile
  • ASP.NET - Unity - 从外部配置文件读取配置部分

    我想将 Unity 集成到我的应用程序中 并且希望它使用外部配置文件 Unity初始化代码是 var fileMap new ExeConfigurationFileMap ExeConfigFilename unity config Sy
  • 将本地图像添加到 IJulia 笔记本

    如何将本地图像添加到 IJulia 笔记本 该图像与 IPYNB 文件位于同一本地网络文件夹中 该文件夹可通过符号链接访问 我尝试过相对文件名和绝对文件名 img src test png alt Image Test style widt
  • PHP 中变量周围的括号有什么作用?

    我在旧平台上使用 PHP 5 3 中的 ImageMagick 我偶然发现了一段代码 当变量周围使用括号时 该代码不起作用 但当删除这些括号时 该代码却起作用 变量两边的括号有什么作用 im new imagick im gt readIm
  • 在哪里可以找到 jdk 7 中的 jnlp api jar? [复制]

    这个问题在这里已经有答案了 可能的重复 在 JDK 1 7 中找不到 jnlp jar https stackoverflow com questions 10830317 cant find jnlp jar in jdk 1 7 对于j
  • 转换具有相同名称但不同扩展名的图像

    例如 我有两个文件 aaa jpg with cat aaa png with dog 正如您所看到的 尽管名称相同 但图像是不同的 我想将这些图像转换为一种格式 这项任务的基本尝试是 mogrify format jpg png 但由于显
  • 如何捕获flask_restful应用程序中引发的所有异常

    我确实有简单的 Restful 应用程序与 Flask Restful from flask import Flask from flask restful import Api app Flask name api Api app api
  • Ncurses:如何刷新菜单而不丢失当前位置?

    如何在不丢失当前位置的情况下重新加载菜单中的数据 例如 当用户 选择 某个项目时 执行的操作会更改数据 更新项目 删除项目或向菜单添加新项目 我想重新加载菜单 但仍将光标保留在刚刚选择的项目上 最好的方法是什么 作为参考 这个问题似乎非常接
  • 如何将用户表连接到角色表(使用表user_roles)?

    我需要创建一个用于用户身份验证的基本数据库 到目前为止 每个用户都有一个名称 密码和角色 我在互联网上的某个地方找到了这个 看起来很有希望 create table if not exists users id int unsigned n
  • 使用 Windows 服务运行 UI

    我计划使用 Windows 服务 用 C 编写 运行启动 监视 UI 应用程序 选中 允许服务与桌面交互 这在 Windows XP 上工作正常 但在 Windows 7 上弹出如下所示 当我点击查看消息时 整个屏幕一片空白 只显示 UI
  • 列出所有已部署的休息端点(spring-boot、jersey)

    是否可以使用 Spring Boot 列出我配置的所有休息端点 执行器在启动时列出所有现有路径 我想要类似的自定义服务 这样我就可以在启动时检查所有路径是否配置正确 并使用此信息进行客户端调用 我该怎么做呢 我用 Path GET我的服务