Перечисление всех развернутых конечных точек restа (весенняя загрузка, трикотаж)

Можно ли перечислить все сконфигурированные конечные точки restа с весенней загрузкой? Привод перечисляет все существующие пути при запуске, я хочу что-то подобное для своих пользовательских сервисов, поэтому я могу проверить запуск, если все пути настроены правильно и использовать эту информацию для клиентских вызовов.

Как мне это сделать? Я использую annotations @Path / @GET для своих сервисов и регистрирую их через ResourceConfig#registerClasses .

Есть ли способ запросить Config для всех путей?

Обновление: я регистрирую controllerы REST через

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

Update2: Я хочу иметь что-то вроде

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

Мотивация: при запуске приложения для загрузки весны я хочу распечатать все зарегистрированные controllerы и их пути, поэтому я могу перестать гадать, какие конечные точки использовать.

Вероятно, лучший способ сделать это – использовать 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 getLinesFromResource(Resource resource) { Set logLines = new HashSet<>(); populate(this.applicationPath, false, resource, logLines); return logLines; } private void populate(String basePath, Class klass, boolean isLocator, Set endpointLogLines) { populate(basePath, isLocator, Resource.from(klass), endpointLogLines); } private void populate(String basePath, boolean isLocator, Resource resource, Set 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 COMPARATOR = Comparator.comparing((EndpointLogLine e) -> e.path) .thenComparing((EndpointLogLine e) -> e.httpMethod); private final Set 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 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); } } } 

Тогда вам просто нужно зарегистрировать слушателя с Джерси. Вы можете получить путь к программе из JerseyProperties . Вам нужно будет установить его в Spring Boot application.properties под свойством spring.jersey.applicationPath . Это будет корневой путь, как если бы вы использовали @ApplicationPath в подclassе 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())); } } 

Следует отметить, что load-on-startup по умолчанию не установлен на сервлете Джерси. Это означает, что Джерси не загружается при запуске до первого запроса. Таким образом, вы не увидите слушателя, вызванного до первого запроса. Я открыл проблему, чтобы получить свойство конфигурации, но в то же время у вас есть несколько вариантов:

  1. Настройте Джерси как фильтр, а не сервлет. Фильтр будет загружен при запуске. Использование Джерси в качестве фильтра, для большинства сообщений, действительно не ведет себя иначе. Чтобы настроить это, вам просто нужно добавить свойство Spring Boot в application.properties

     spring.jersey.type=filter 
  2. Другой вариант – переопределить 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 entry : jersey.getInit().entrySet()) { registration.addInitParameter(entry.getKey(), entry.getValue()); } } } 

ОБНОВИТЬ

Поэтому похоже, что Spring Boot добавит свойство load-on-startup , поэтому нам не нужно переопределять ServletRegistrationBean . Будет добавлен в Boot 1.4.0

Можете ли вы использовать ResourceConfig#getResources на свой ResourceConfig затем получить необходимую информацию, итерации через Set он возвращает?

Извиняюсь, попробовал бы, но у меня нет ресурсов, чтобы сделать это прямо сейчас. :-п

После того, как приложение полностью запущено, вы можете задать ServerConfig :

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

classes содержат все кэшированные classы конечных точек.

Из документов API для javax.ws.rs.core.Configuration :

Получите неизменяемый набор зарегистрированных компонентов JAX-RS (например, поставщика или функции) для создания, ввода и использования в области настраиваемого экземпляра.

Однако вы не можете сделать это в коде init вашего приложения, classы могут еще не полностью загрузиться.

С помощью classов вы можете сканировать их для ресурсов:

 public Map> scan(Class baseClass) { Builder builder = Resource.builder(baseClass); if (null == builder) return null; Resource resource = builder.build(); String uriPrefix = ""; Map> info = new TreeMap<>(); return process(uriPrefix, resource, info); } private Map> process(String uriPrefix, Resource resource, Map> info) { String pathPrefix = uriPrefix; List 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 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; } 

Как насчет использования RequestMappingHandlerMapping который содержит всю информацию о конечных точках.

См. Мой ответ в разделе Как получить доступ ко всем доступным маршрутам API REST от controllerа? ,

  • Обновление статического содержимого с помощью Spring MVC и Boot
  • Как обрабатывать запросы HTTP OPTIONS в Spring Boot?
  • Как обрабатывать redirect Spring Boot в / error?
  • Формат JSON Java 8 LocalDateTime в Spring Boot
  • Как получить доступ к значению, определенному в файле application.properties в Spring Boot
  • Разверните Spring Boot для Tomcat
  • Ресурс API для облачных сообщений Firebase
  • SpringBoot - создание файлов jar - нет classов автоматической настройки, найденных в META-INF / spring.factories
  • Spring Boot перенаправляет HTTP на HTTPS
  • Возrotation объекта JSON в качестве ответа в Spring Boot
  • Spring-Boot Jersey: разрешить Джерси обслуживать статический контент
  • Давайте будем гением компьютера.