starter java rest spring-boot jersey jersey-2.0

java - starter - Listado de todos los puntos finales de descanso desplegados(spring-boot, jersey)



spring boot mybatis mapper xml (5)

¿Es posible enumerar todos mis puntos finales de descanso configurados con el arranque de primavera? El actuador enumera todas las rutas existentes en el inicio, quiero algo similar para mis servicios personalizados, por lo que puedo verificar en el inicio si todas las rutas están configuradas correctamente y usar esta información para las llamadas de los clientes.

¿Cómo hago esto? Uso anotaciones @Path / @GET en mis beans de servicio y las registro a través de ResourceConfig#registerClasses .

¿Hay alguna manera de consultar la configuración para todas las rutas?

Actualización: registro los controladores REST a través de

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

Actualización2: quiero tener algo como

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

Motivación: cuando se inició la aplicación Spring-boot, quiero imprimir todos los controladores registrados y sus rutas, para que pueda dejar de adivinar qué puntos finales utilizar.


¿Puede usar ResourceConfig#getResources en su objeto ResourceConfig luego obtener la información que necesita iterando a través de Set<Resource> que devuelve?

Disculpas, lo intentaría, pero no tengo los recursos para hacerlo ahora. :-pag



Después de que la aplicación se haya iniciado por completo, puede preguntarle a ServerConfig :

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

classes contiene todas las clases de punto final en caché.

De los documentos de API para javax.ws.rs.core.Configuration :

Obtenga el conjunto inmutable de clases de componente JAX-RS registradas (como proveedor o característica) para instanciar, inyectar y utilizar en el ámbito de la instancia configurable.

Sin embargo, no puede hacer esto en el código de inicio de su aplicación, es posible que las clases aún no estén completamente cargadas.

Con las clases, puede escanearlos en busca de los recursos:

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; }


Probablemente la mejor manera de hacerlo es usar un ApplicationEventListener . Desde allí puede escuchar el evento "la aplicación terminó de inicializarse" y obtener el ResourceModel del ApplicationEvent . ResourceModel tendrá todos los Resource inicializados. Entonces puede atravesar el Resource como otros han mencionado. A continuación se muestra una implementación. Parte de la implementación se ha tomado de la implementación 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); } } }

Entonces solo necesita registrar al oyente con Jersey. Puede obtener la ruta de la aplicación de JerseyProperties . Deberá haberlo configurado en la application.properties Spring Boot application.properties bajo la propiedad spring.jersey.applicationPath . Esta será la ruta raíz, como si fuera a usar @ApplicationPath en su subclase 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())); } }

Una cosa a tener en cuenta es que la carga al inicio no está configurada por defecto en el servlet Jersey. Lo que esto significa es que Jersey no se cargará en el inicio hasta la primera solicitud. Por lo tanto, no verá el escucha activado hasta la primera solicitud. Abrí un problema para obtener una propiedad de configuración, pero mientras tanto, tienes un par de opciones:

  1. Configure Jersey como filtro, en lugar de un servlet. El filtro se cargará al inicio. Usar Jersey como filtro, para la mayoría de las publicaciones, realmente no se comporta de manera diferente. Para configurar esto, solo necesita agregar una propiedad Spring Boot en la application.properties

    spring.jersey.type=filter

  2. La otra opción es anular Jersey ServletRegistrationBean y establecer su propiedad loadOnStartup . Aquí hay un ejemplo de configuración. Parte de la implementación se ha tomado directamente de 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()); } } }

ACTUALIZAR

Por lo tanto, parece que Spring Boot agregará la propiedad de load-on-startup , por lo que no tenemos que anular el ServletRegistrationBean Jersey. Se agregará en el arranque 1.4.0


Todos los puntos finales REST se enumeran en /actuator/mappings endpoint.

Active el punto final de las asignaciones con la propiedad management.endpoints.web.exposure.include

Por ejemplo: management.endpoints.web.exposure.include=env,info,health,httptrace,logfile,metrics,mappings