Другой вопрос был назван дубликатом этого:
В C ++ почему результат cout << x
отличается от значения, которое показывает отладчик для x
?
x
в вопросе - это переменная float
.
Одним из примеров может быть
float x = 9.9F;
Отладчик показывает 9.89999962
, вывод работы cout
- 9.9
.
Ответ оказывается, что точность cout
по умолчанию для float
равна 6, поэтому она округляется до шести десятичных цифры
См. здесь для справки
Вероятно, лучший способ сделать это - использовать ApplicationEventListener
. Оттуда вы можете прослушать событие «завершение инициализации приложения» и получить ResourceModel
из ApplicationEvent
. ResourceModel
будет иметь все инициализированные Resource
s. Затем вы можете пересечь 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);
}
}
}
Тогда вам просто нужно зарегистрировать слушателя с Джерси. Вы можете получить путь к программе из JerseyProperties
. Вам нужно будет установить его в Spring Boot 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()));
}
}
. Следует отметить, что загрузка при запуске не задается по умолчанию на сервлете Джерси. Это означает, что Джерси не загружается при запуске до первого запроса. Таким образом, вы не увидите слушателя, вызванного до первого запроса. Я открыл проблему , чтобы получить свойство конфигурации, но в то же время у вас есть несколько вариантов:
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<String, String> entry : jersey.getInit().entrySet()) {
registration.addInitParameter(entry.getKey(), entry.getValue());
}
}
}
Так выглядит Spring Boot добавит свойство load-on-startup
, поэтому нам не нужно переопределять Jersey ServletRegistrationBean
. Будет добавлен в Boot 1.4.0
Можете ли вы использовать ResourceConfig#getResources
на вашем объекте ResourceConfig
, а затем получить нужную информацию, итерации через Set<Resource>
, который он возвращает?
Извиняюсь, попробовал бы это, но у меня нет ресурсов , чтобы сделать это прямо сейчас. :-p
После того, как приложение полностью запущено, вы можете спросить ServerConfig
:
ResourceConfig instance;
ServerConfig scfg = instance.getConfiguration();
Set<Class<?>> classes = scfg.getClasses();
classes
содержит все кэшированные классы конечных точек.
Из API docs для javax.ws.rs.core.Configuration
:
Получите неизменяемый набор зарегистрированных компонентов JAX-RS (таких как поставщик или объект), которые должны быть созданы, введены и использованы в рамках настраиваемый экземпляр.
blockquote>Однако вы не можете сделать это в коде init вашего приложения, классы могут еще не полностью загрузиться.
С помощью классов вы можете сканировать их для ресурсов:
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; }
Как насчет использования RequestMappingHandlerMapping
, который содержит всю информацию о конечных точках.
См. мой ответ в Как получить доступ ко всем доступным маршрутам API REST от контроллера? .