从 Spring Boot 1.3.7 升级到 1.4.0 后,单个 jar 启动失败

SIngle jar startup failed after upgrade from Spring Boot 1.3.7 to 1.4.0

从 Spring Boot 1.3.7 升级到 1.4.0 后,我们无法再使用 Spring Boot Maven 插件将我们的应用程序作为单个 jar 构建启动。我们的应用程序是一个使用 Jersey 和 Jetty 的小型 REST 接口。我们使用 Maven,我们的 pom 文件非常标准 Spring Boot.

我们仍然可以使用 mvn spring-boot:run 并从 Eclipse 中 运行 应用程序,但是当 运行 作为单个 jar Jersey ResourceFinder 抱怨它找不到 .jar!/BOOT-INF/classes.

当我解压缩 jar 时,文件夹 BOOT-INF/classes 存在并且包含预期的 类 和资源。

感谢任何帮助。

2016-08-10 14:58:31.162 ERROR 16071 --- [           main] o.s.boot.SpringApplication               
: Application startup failed

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'jerseyConfig' defined in URL
[jar:file:/acmesource/acme/acme-core/acme-core-api/target/acme-core-api-0.1
SNAPSHOT.jar!/BOOT-INF/classes!/com/acme/core/api/JerseyConfig.class]: Bean 
instantiation via constructor failed; nested exception is 
org.springframework.beans.BeanInstantiationException: Failed to instantiate 
[com.acme.core.api.JerseyConfig]: Constructor threw exception; nested 
exception is
org.glassfish.jersey.server.internal.scanning.ResourceFinderException:
java.io.FileNotFoundException: /acmesource/acme/acme-core/acme-core
api/target/acme-core-api-0.1-SNAPSHOT.jar!/BOOT-INF/classes (No such file or directory)

来自Spring Boot 1.4 release notes

The change to the layout of executable jars means that a limitation in Jersey's classpath scanning now affects executable jar files as well as executable war files. To work around the problem, classes that you wish to be scanned by Jersey should be packaged in a jar and included as a dependency in BOOT-INF/lib. The Spring Boot launcher should then be configured to unpack those jars on start up so that Jersey can scan their contents.

使用 Spring Boot (+ Jersey 2) 它可以看起来像单独的配置 class(实现单独的资源注册):

@Configuration
public class RestBeansConfiguration {
    private static final Logger LOG = LoggerFactory.getLogger(RestBeansConfiguration.class);

    @Inject
    private ApplicationContext appCtx;

    @Bean
    public ResourceConfigCustomizer jersey() {
        return config -> {
            LOG.info("Jersey resource classes found:");
            appCtx.getBeansWithAnnotation(Path.class).forEach((name, resource) -> {
                LOG.info(" -> {}", resource.getClass().getName());
                config.register(resource);
            });
        };
    }
}

另一个解决方案:

虽然 Jersey 无法在新版本的 fat boot jar 中扫描 类,但您可以使用 Spring 类路径扫描工具达到相同的效果。这样您就可以像 ResourceConfig.packages():

一样扫描包裹
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(Path.class));
config.registerClasses(scanner.findCandidateComponents("your.package.to.scan").stream()
            .map(beanDefinition -> ClassUtils.resolveClassName(beanDefinition.getBeanClassName(), config.getClassLoader()))
            .collect(Collectors.toSet()));

注:请查看org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener的来源。这是库存解决方案,您可以看到它做同样的事情:它扫描用 @Path@Provider 注释的 类 (但由于扫描损坏而无法找到任何东西机制)。

顺便说一句,lanwen 发布的基于 bean 的方法可能更清楚:) 也只需添加 @Provider 即可。