Java 列出所有已部署的休息端点(spring-boot、jersey)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32525699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Listing all deployed rest endpoints (spring-boot, jersey)
提问by Jan Galinski
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.
是否可以使用 spring boot 列出我所有配置的休息端点?执行器在启动时列出了所有现有路径,我希望我的自定义服务有类似的东西,所以我可以在启动时检查所有路径是否配置正确,并将此信息用于客户端调用。
How do I do this? I use @Path
/@GET
annotations on my service beans and register them via ResourceConfig#registerClasses
.
我该怎么做呢?我在我的服务 bean 上使用@Path
/@GET
注释并通过ResourceConfig#registerClasses
.
Is there a way to query the Config for all Paths?
有没有办法查询所有路径的配置?
Update:I register the REST Controllers via
更新:我通过注册 REST 控制器
@Bean
public ResourceConfig resourceConfig() {
return new ResourceConfig() {
{
register(MyRestController.class);
}
};
}
Update2:I want to have something like
更新2:我想要类似的东西
GET /rest/mycontroller/info
POST /res/mycontroller/update
...
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.
动机:当 spring-boot 应用程序启动时,我想打印出所有注册的控制器及其路径,这样我就可以停止猜测要使用的端点。
采纳答案by Paul Samsotha
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 Resource
s. 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.
可能最好的方法是使用ApplicationEventListener
. 从那里您可以侦听“应用程序完成初始化”事件,并ResourceModel
从ApplicationEvent
. 在ResourceModel
将所有的初始化Resource
秒。然后你可以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);
}
}
}
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
然后你只需要在 Jersey 中注册监听器。您可以从JerseyProperties
. 您需要在 Spring Bootapplication.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()));
}
}
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 issueto possible get a configuration property, but in the meantime, you have a couple options:
需要注意的一件事是,在 Jersey servlet 上默认情况下未设置启动时加载。这意味着 Jersey 在第一个请求之前不会在启动时加载。所以在第一个请求之前你不会看到监听器被触发。我已经打开了一个问题以获得配置属性,但与此同时,您有几个选择:
Set up Jersey as filter, instead of a servlet. The filter will be loaded on start up. Using Jersey as a filter, for the most post, really doesn't behave any differently. To configure this you just need to add a Spring Boot property in the
application.properties
spring.jersey.type=filter
The other option is to override the Jersey
ServletRegistrationBean
and set itsloadOnStartup
property. Here is an example configuration. Some of the implementation has been taken straight from theJerseyAutoConfiguration
@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()); } } }
将 Jersey 设置为过滤器,而不是 servlet。过滤器将在启动时加载。使用 Jersey 作为过滤器,对于大多数帖子来说,实际上并没有任何不同。要配置它,您只需要在
application.properties
spring.jersey.type=filter
另一种选择是覆盖 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()); } } }
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 将添加load-on-startup
属性,所以我们不必覆盖 Jersey ServletRegistrationBean
。将在 Boot 1.4.0 中添加
回答by Carlos Bribiescas
Can you use ResourceConfig#getResources
on your ResourceConfig
object then get the info you need by iterating through the Set<Resource>
it returns?
你可以ResourceConfig#getResources
在你的ResourceConfig
对象上使用然后通过迭代Set<Resource>
它返回的信息来获取你需要的信息吗?
Apologies, would try it, but I don't have the Resourcesto do it right now. :-p
抱歉,我会尝试一下,但我现在没有资源来做这件事。:-p
回答by Johannes Jander
After the application is fully started, you can ask ServerConfig
:
应用程序完全启动后,您可以询问ServerConfig
:
ResourceConfig instance;
ServerConfig scfg = instance.getConfiguration();
Set<Class<?>> classes = scfg.getClasses();
classes
contains all the cached endpoint classes.
classes
包含所有缓存的端点类。
From the API docsfor javax.ws.rs.core.Configuration
:
从API文档为javax.ws.rs.core.Configuration
:
Get the immutable set of registered JAX-RS component (such as provider or feature) classes to be instantiated, injected and utilized in the scope of the configurable instance.
获取要在可配置实例范围内实例化、注入和使用的已注册 JAX-RS 组件(例如提供者或功能)类的不可变集。
However, you can't do this in the init code of your application, the classes might not yet be fully loaded.
但是,您不能在应用程序的初始化代码中执行此操作,这些类可能尚未完全加载。
With the classes, you can scan them for the resources:
使用这些类,您可以扫描它们以获取资源:
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;
}
回答by Thomas Decaux
What about using RequestMappingHandlerMapping
that hold all endpoints information.
使用RequestMappingHandlerMapping
它保存所有端点信息怎么样。
See my answer at How to access all available routes of a REST API from a controller?.
请参阅我在如何从控制器访问 REST API 的所有可用路由的答案?.
回答by Frischling
All REST endpoints are listed in /actuator/mappings
endpoint.
所有 REST 端点都列在/actuator/mappings
端点中。
Activate the mappings endpoint with the property management.endpoints.web.exposure.include
使用属性激活映射端点 management.endpoints.web.exposure.include
For example: management.endpoints.web.exposure.include=env,info,health,httptrace,logfile,metrics,mappings
例如: management.endpoints.web.exposure.include=env,info,health,httptrace,logfile,metrics,mappings