構成済みのすべてのレストエンドポイントをスプリングブートで一覧表示することはできますか?アクチュエータは起動時にすべての既存のパスをリストします。カスタムサービスに似たものが欲しいので、すべてのパスが正しく構成されているかどうかを起動時に確認し、この情報をクライアントコールに使用できます。
どうすればいいですか?サービス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
...
動機:スプリングブートアプリの起動時に、登録されているすべてのコントローラーとそのパスを印刷したいので、使用するエンドポイントの推測を停止できます。
おそらくこれを行う最良の方法は、 ApplicationEventListener
を使用することです。そこから、「アプリケーション終了初期化」イベントをリッスンし、ResourceModel
からApplicationEvent
を取得できます。 ResourceModel
には、すべての初期化されたResource
sが含まれます。その後、他の人が述べたように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つは、ジャージーサーブレットでは起動時のロードがデフォルトで設定されていないことです。これが意味するのは、最初のリクエストまでジャージーが起動時にロードされないということです。したがって、最初のリクエストまでリスナーはトリガーされません。構成プロパティを取得するために 問題 を開きましたが、その間にいくつかのオプションがあります:
サーブレットの代わりに、ジャージーをフィルターとして設定します。フィルターは起動時にロードされます。ジャージーをフィルターとして使用すると、ほとんどの投稿で、実際に動作は変わりません。これを設定するには、application.properties
にSpring Bootプロパティを追加するだけです
spring.jersey.type=filter
もう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で追加されます
すべてのREST=エンドポイントは/actuator/mappings
エンドポイントにリストされます。
プロパティmanagement.endpoints.web.exposure.include
でマッピングエンドポイントをアクティブにします
例:management.endpoints.web.exposure.include=env,info,health,httptrace,logfile,metrics,mappings
アプリケーションが完全に起動したら、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;
}
使える - ResourceConfig#getResources
ResourceConfig
オブジェクトで、 Set<Resource>
戻りますか?
申し訳ありませんが、試してみますが、今のところそれを行うためのResourcesはありません。 :-p
すべてのエンドポイント情報を保持するRequestMappingHandlerMapping
の使用はどうですか。
RESTコントローラーからのAPI? の利用可能なすべてのルートにアクセスする方法)で私の答えを参照してください。