Перечисление всех развернутых конечных точек restа (весенняя загрузка, трикотаж)
Можно ли перечислить все сконфигурированные конечные точки restа с весенней загрузкой? Привод перечисляет все существующие пути при запуске, я хочу что-то подобное для своих пользовательских сервисов, поэтому я могу проверить запуск, если все пути настроены правильно и использовать эту информацию для клиентских вызовов.
Как мне это сделать? Я использую annotations @Path
/ @GET
для своих сервисов и регистрирую их через ResourceConfig#registerClasses
.
Есть ли способ запросить Config для всех путей?
- Spring Boot & JPA: реализация поисковых запросов с дополнительными критериями дальности
- Как создать контекст JNDI в Spring Boot с встроенным контейнером Tomcat
- java.lang.NoClassDefFoundError: org / springframework / core / env / ConfigurableEnvironment
- Как запустить приложение Spring-Boot без зависимости от базы данных?
- Как сообщить Spring Boot, какой основной class использовать для исполняемого банку?
Обновление: я регистрирую controllerы REST через
@Bean public ResourceConfig resourceConfig() { return new ResourceConfig() { { register(MyRestController.class); } }; }
Update2: Я хочу иметь что-то вроде
GET /rest/mycontroller/info POST /res/mycontroller/update ...
Мотивация: при запуске приложения для загрузки весны я хочу распечатать все зарегистрированные controllerы и их пути, поэтому я могу перестать гадать, какие конечные точки использовать.
- org.hibernate.HibernateException: доступ к DialectResolutionInfo не может быть нулевым, если «hibernate.dialect» не установлен
- Как настроить встроенный Tomcat, интегрированный с Spring, для прослушивания запросов на IP-адрес, кроме localhost?
- Подключение к Db умирает после> 4 <24 в спящем режиме java
- Spring-Boot: Как установить свойства пула JDBC, например максимальное количество соединений?
- Запустите главную роль Spring-boot с помощью IDE
- Добавить сервлет-фильтр в приложении Spring Boot
- Рассмотрим определение компонента типа «package» в вашей конфигурации
- Spring Boot, Spring Data JPA с несколькими источниками данных
Вероятно, лучший способ сделать это – использовать 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 по умолчанию не установлен на сервлете Джерси. Это означает, что Джерси не загружается при запуске до первого запроса. Таким образом, вы не увидите слушателя, вызванного до первого запроса. Я открыл проблему, чтобы получить свойство конфигурации, но в то же время у вас есть несколько вариантов:
-
Настройте Джерси как фильтр, а не сервлет. Фильтр будет загружен при запуске. Использование Джерси в качестве фильтра, для большинства сообщений, действительно не ведет себя иначе. Чтобы настроить это, вам просто нужно добавить свойство Spring Boot в
application.properties
spring.jersey.type=filter
-
Другой вариант – переопределить
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а? ,