列出所有已部署的休息端点(spring-boot、jersey) [英] Listing all deployed rest endpoints (spring-boot, jersey)

查看:23
本文介绍了列出所有已部署的休息端点(spring-boot、jersey)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以使用 spring boot 列出我所有配置的休息端点?执行器在启动时列出了所有现有路径,我希望我的自定义服务有类似的东西,因此我可以在启动时检查所有路径是否配置正确,并将此信息用于客户端调用.

Is it possible to list all my configured rest-endpoints with spring boot? The actuator lists all existing paths on startup, I want something similar for my custom services, so I can check on startup if all paths are configured correctly and use this info for client calls.

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

How do I do this? I use @Path/@GET annotations on my service beans and register them via ResourceConfig#registerClasses.

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

Is there a way to query the Config for all Paths?

更新:我通过

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

Update2:我想要类似的东西

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

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

Motivation: when the spring-boot app started, I want to print out all registered controllers and their paths, so I can stop guessing which endpoints to use.

推荐答案

可能最好的方法是使用 ApplicationEventListener.从那里您可以侦听应用程序完成初始化"事件,并从 ApplicationEvent 获取 ResourceModel.ResourceModel 将拥有所有初始化的 Resource.然后你可以像其他人提到的那样遍历 Resource .下面是一个实现.部分实现取自 DropwizardResourceConfig 实现.

Probably the best way to do this, is to use an ApplicationEventListener. From there you can listen for the "application finished initializing" event, and get the ResourceModel from the ApplicationEvent. The ResourceModel will have all the initialized Resources. Then you can traverse the Resource as others have mentioned. Below is an implementation. Some of the implementation has been taken from the DropwizardResourceConfig implementation.

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("
All endpoints for Jersey application
");
            logLines.stream().forEach((line) -> {
                sb.append(line).append("
");
            });
            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.jersey.applicationPath 下的 Spring Boot application.properties 中设置它.这将是根路径,就像您在 ResourceConfig 子类上使用 @ApplicationPath 一样

Then you just need to register the listener with Jersey. You can get the application path from the JerseyProperties. You will need to have set it in the Spring Boot application.properties under the property spring.jersey.applicationPath. This will be the root path, just as if you were to use @ApplicationPath on your ResourceConfig subclass

@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 以获得可能的配置属性,但在同时,您有几个选择:

One thing to note, is that the load-on-startup is not set by default on the Jersey servlet. What this means is that that Jersey won't load on startup until the first request. So you will not see the listener triggered until the first request. I have opened an issue to possible get a configuration property, but in the meantime, you have a couple options:

  1. 将 Jersey 设置为过滤器,而不是 servlet.过滤器将在启动时加载.使用 Jersey 作为过滤器,对于大多数帖子来说,实际上并没有什么不同.要配置它,您只需要在 application.properties

spring.jersey.type=filter

  • 另一个选项是覆盖 Jersey ServletRegistrationBean 并设置它的 loadOnStartup 属性.这是一个示例配置.一些实现直接取自 JerseyAutoConfiguration

  • The other option is to override the Jersey ServletRegistrationBean and set its loadOnStartup property. Here is an example configuration. Some of the implementation has been taken straight from the JerseyAutoConfiguration

    @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());
            }
        }
    }
    

  • <小时>

    更新

    所以看起来 Spring Boot 将要添加load-on-startup 属性,所以我们不必覆盖 Jersey ServletRegistrationBean.将在 Boot 1.4.0 中添加


    UPDATE

    So it looks like Spring Boot is going to add the load-on-startup property, so we don't have to override the Jersey ServletRegistrationBean. Will be added in Boot 1.4.0

    这篇关于列出所有已部署的休息端点(spring-boot、jersey)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆