web-dev-qa-db-ja.com

デプロイされたすべてのレストエンドポイント(スプリングブート、ジャージ)のリスト

構成済みのすべてのレストエンドポイントをスプリングブートで一覧表示することはできますか?アクチュエータは起動時にすべての既存のパスをリストします。カスタムサービスに似たものが欲しいので、すべてのパスが正しく構成されているかどうかを起動時に確認し、この情報をクライアントコールに使用できます。

どうすればいいですか?サービスBeanで@Path/@GET注釈を使用し、ResourceConfig#registerClassesを介して登録します。

すべてのパスの構成を照会する方法はありますか?

更新:私はREST

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

pdate2:のようなものが欲しい

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

動機:スプリングブートアプリの起動時に、登録されているすべてのコントローラーとそのパスを印刷したいので、使用するエンドポイントの推測を停止できます。

23
Jan Galinski

おそらくこれを行う最良の方法は、 ApplicationEventListener を使用することです。そこから、「アプリケーション終了初期化」イベントをリッスンし、ResourceModelからApplicationEventを取得できます。 ResourceModelには、すべての初期化されたResourcesが含まれます。その後、他の人が述べたようにResourceを横断できます。以下は実装です。実装の一部は DropwizardResourceConfig 実装から取られています。

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ブートapplication.propertiesプロパティspring.jersey.applicationPathで設定する必要があります。 ResourceConfigサブクラスで@ApplicationPathを使用するかのように、これがルートパスになります

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

注意すべきことの1つは、ジャージーサーブレットでは起動時のロードがデフォルトで設定されていないことです。これが意味するのは、最初のリクエストまでジャージーが起動時にロードされないということです。したがって、最初のリクエストまでリスナーはトリガーされません。構成プロパティを取得するために 問題 を開きましたが、その間にいくつかのオプションがあります:

  1. サーブレットの代わりに、ジャージーをフィルターとして設定します。フィルターは起動時にロードされます。ジャージーをフィルターとして使用すると、ほとんどの投稿で、実際に動作は変わりません。これを設定するには、application.propertiesにSpring Bootプロパティを追加するだけです

    spring.jersey.type=filter
    
  2. もう1つのオプションは、Jersey ServletRegistrationBeanをオーバーライドして、そのloadOnStartupプロパティを設定することです。以下に設定例を示します。実装の一部は 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をオーバーライドする必要はありません。ブート1.4.0で追加されます

14
Paul Samsotha

すべてのREST=エンドポイントは/actuator/mappingsエンドポイントにリストされます。

プロパティmanagement.endpoints.web.exposure.includeでマッピングエンドポイントをアクティブにします

例:management.endpoints.web.exposure.include=env,info,health,httptrace,logfile,metrics,mappings

1
Frischling

アプリケーションが完全に起動したら、ServerConfigに問い合わせることができます。

ResourceConfig instance; 
ServerConfig scfg = instance.getConfiguration();
Set<Class<?>> classes = scfg.getClasses();

classesには、キャッシュされたすべてのエンドポイントクラスが含まれます。

から APIドキュメント for javax.ws.rs.core.Configuration

構成可能インスタンスのスコープ内でインスタンス化、挿入、および使用される登録済みJAX-RSコンポーネント(プロバイダーや機能など)クラスの不変セットを取得します。

ただし、アプリケーションの初期化コードでこれを行うことはできません。クラスがまだ完全にロードされていない可能性があります。

クラスを使用して、リソースをスキャンできます。

public Map<String, List<InfoLine>> scan(Class baseClass) {
    Builder builder = Resource.builder(baseClass);
    if (null == builder)
        return null;
    Resource resource = builder.build();
    String uriPrefix = "";
    Map<String, List<InfoLine>> info = new TreeMap<>();
    return process(uriPrefix, resource, info);
}

private Map<String, List<InfoLine>> process(String uriPrefix, Resource resource, Map<String, List<InfoLine>> info) {
    String pathPrefix = uriPrefix;
    List<Resource> resources = new ArrayList<>();
    resources.addAll(resource.getChildResources());
    if (resource.getPath() != null) {
        pathPrefix = pathPrefix + resource.getPath();
    }
    for (ResourceMethod method : resource.getAllMethods()) {
        if (method.getType().equals(ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR)) {
            resources.add(
                Resource.from(
                    resource.getResourceLocator()
                            .getInvocable()
                            .getDefinitionMethod()
                            .getReturnType()
                )
            );
        }
        else {
            List<InfoLine> paths = info.get(pathPrefix);
            if (null == paths) {
                paths = new ArrayList<>();
                info.put(pathPrefix, paths);
            }
            InfoLine line = new InfoLine();
            line.pathPrefix = pathPrefix;
            line.httpMethod = method.getHttpMethod();
            paths.add(line);
            System.out.println(method.getHttpMethod() + "\t" + pathPrefix);
        }
    }
    for (Resource childResource : resources) {
        process(pathPrefix, childResource, info);
    }
    return info;
}


private class InfoLine {
    public String pathPrefix;
    public String httpMethod;
}
1
Johannes Jander

使える - ResourceConfig#getResourcesResourceConfigオブジェクトで、 Set<Resource> 戻りますか?

申し訳ありませんが、試してみますが、今のところそれを行うためのResourcesはありません。 :-p

1

すべてのエンドポイント情報を保持するRequestMappingHandlerMappingの使用はどうですか。

RESTコントローラーからのAPI? の利用可能なすべてのルートにアクセスする方法)で私の答えを参照してください。

0
Thomas Decaux