是否可以列出所有使用Spring Boot配置的其余端点?执行器列出了启动时的所有现有路径,我希望为自定义服务提供类似的内容,因此我可以在启动时检查所有路径的配置是否正确,并将此信息用于客户端调用。
我该怎么做呢?我在服务bean上使用@Path/ @GET批注,并通过进行注册ResourceConfig#registerClasses。
@Path
@GET
ResourceConfig#registerClasses
有没有一种方法可以查询所有路径的配置?
更新: 我通过注册REST控制器
@Bean public ResourceConfig resourceConfig() { return new ResourceConfig() { { register(MyRestController.class); } }; }
Update2: 我想要类似的东西
GET /rest/mycontroller/info POST /res/mycontroller/update ...
动机:启动spring-boot应用程序时,我想打印出所有已注册的控制器及其路径,因此我可以停止猜测要使用的端点。
可能最好的方法是使用ApplicationEventListener。从那里,你可以听的“应用程序完成初始化”事件,并获得ResourceModel从ApplicationEvent。在ResourceModel将所有的初始化Resource秒。然后,您可以遍历Resource其他人提到的。下面是一个实现。一些实施已取自DropwizardResourceConfig实施。
ApplicationEventListener
ResourceModel
ApplicationEvent
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 Boot中application.properties的属性下进行设置spring.jersey.applicationPath。这将是根路径,就像您要@ApplicationPath在ResourceConfig子类上使用一样
JerseyProperties
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才会在启动时加载。因此,在第一个请求之前,您不会看到侦听器被触发。我打开了一个可能获取配置属性的问题,但与此同时,您有两种选择:
将Jersey设置为过滤器,而不是servlet。过滤器将在启动时加载。对于大多数帖子,使用Jersey作为过滤器,实际上并没有什么不同。要配置它,您只需要在application.properties
spring.jersey.type=filter
另一个选项是覆盖Jersey ServletRegistrationBean并设置其loadOnStartup属性。这是一个示例配置。一些实施是直接从JerseyAutoConfiguration
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。将在Boot 1.4.0中添加
load-on-startup