简体   繁体   English

在Spring Boot应用程序中找不到红宝石宝石

[英]Can't find ruby gem in Spring Boot application

I have created a jar file that contains a Ruby gem (activemerchant) that is wrapped by a Spring service, which uses the JRuby ScriptContainer to invoke a small Ruby script that utilises the ruby gem. 我创建了一个jar文件,其中包含一个由Spring服务包装的Ruby gem(activemerchant),该服务使用JRuby ScriptContainer调用一个利用ruby gem的小型Ruby脚本。 Unit tests for this jar all work fine with the Ruby gem - the gem gets picked up and runs as expected. 这个罐子的单元测试对Ruby宝石都可以正常工作-宝石被拾起并按预期运行。

The jar contains the following folders: 该罐包含以下文件夹:

com
    ... <not relevant> ...
gateways
    ... <not relevant> ...
gems
    activemerchant-1.75.0
    activesupport-5.2.0.beta2
    builder-3.2.3
    concurrent-ruby-1.0.5-java
    i18n-0.9.1
    minitest-5.11.1
    nokogiri-1.8.1-java
    thread_safe-0.3.6-java
    tzinfo-1.2.4
META-INF
    MANIFEST.MF
scripts
    main.rb
specifications
    activemerchant-1.75.0.gemspec
    activesupport-5.2.0.beta2.gemspec
    builder-3.2.3.gemspec
    concurrent-ruby-1.0.5-java.gemspec
    i18n-0.9.1.gemspec
    minitest-5.11.1.gemspec
    nokogiri-1.8.1-java.gemspec
    thread_safe-0.3.6-java.gemspec
    tzinfo-1.2.4.gemspec

The jar is embedded into a Spring Boot application that uses an embedded Tomcat container. 该jar嵌入到使用嵌入式Tomcat容器的Spring Boot应用程序中。 When spinning up the Spring Boot application, I get the error: 在启动Spring Boot应用程序时,出现错误:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'activeMerchantGatewayProvider': Invocation of init method failed; nested exception is org.jruby.embed.EvalFailedException: (LoadError) no such file to load -- activemerchant
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:220)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1088)
    at com.mydomain.financial.gateway.GatewayService.lambda$init$0(GatewayService.java:39)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.mydomain.financial.gateway.GatewayService.init(GatewayService.java:40)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:365)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:310)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
    ... 81 common frames omitted
Caused by: org.jruby.embed.EvalFailedException: (LoadError) no such file to load -- activemerchant
    at org.jruby.embed.internal.EmbedEvalUnitImpl.run(EmbedEvalUnitImpl.java:131)
    at org.jruby.embed.ScriptingContainer.runUnit(ScriptingContainer.java:1307)
    at org.jruby.embed.ScriptingContainer.runScriptlet(ScriptingContainer.java:1352)
    at com.mydomain.financial.gateway.activemerchant.ActiveMerchantGatewayProvider.init(ActiveMerchantGatewayProvider.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:365)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:310)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133)
    ... 108 common frames omitted
Caused by: org.jruby.exceptions.RaiseException: (LoadError) no such file to load -- activemerchant
    at org.jruby.RubyKernel.require(org/jruby/RubyKernel.java:955)
    at uri_3a_classloader_3a_.META_minus_INF.jruby_dot_home.lib.ruby.stdlib.rubygems.core_ext.kernel_require.require(uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/rubygems/core_ext/kernel_require.rb:55)
    at RUBY.<main>(classpath:/scripts/main.rb:2)

How do I get the Spring Boot embedded Tomcat/JRuby to recognise the gems in the embedded jar? 如何获得Spring Boot嵌入式Tomcat / JRuby来识别嵌入式jar中的宝石?

What a PITA. 真是皮塔饼。 I found a work around, but it took a bit of effort. 我找到了解决方法,但是花了点功夫。

The cause for the failure is that that JRuby couldn't scan the inner jar file for its gems. 失败的原因是JRuby无法扫描内部jar文件中的gem。 And it seems like this a problem with Java more than a problem with JRuby. 似乎这是Java的问题,而不是JRuby的问题。 I was also unable to find a way to scan the directories of a jar within a jar, although I could get a file from a jar in a jar. 我也无法找到一种方法来扫描jar中的jar目录,尽管我可以从jar中的jar中获取文件。 My solution was to create a list of the gems in the inner jar during it's build and retrieve that list file during the start up of the Spring Boot app. 我的解决方案是在构建内部jar时创建一个宝石列表,并在Spring Boot应用程序启动期间检索该列表文件。

Specifically: 特别:

In the inner jar, I changed the ScriptContainer logic as follows (GEMS_PATH = /gems): 在内部jar中,我如下更改了ScriptContainer逻辑(GEMS_PATH = / gems):

@PostConstruct
private void init() {
    ScriptingContainer scriptingContainer = new ScriptingContainer(LocalContextScope.CONCURRENT);

    // Locate load paths and add them to the container. This doesn't work in JRuby as expected
    // once this jar is embedded into a Spring Boot uber project, so I need to do it explicitly
    // here.
    List<String> loadPaths = scriptingContainer.getLoadPaths();
    URL resource = getClass().getResource(GEMS_PATH + "/gems.list");
    if (resource == null) {
        throw new RuntimeException("Unable to find " + GEMS_PATH + "/gems.list");
    }

    try {
        log.debug("ActiveMerchant gems root: {}", resource);
        String content = IOUtils.toString(resource, "UTF-8");
        if (StringUtils.isEmpty(content)) {
            throw new RuntimeException(GEMS_PATH + "/gems.list is empty");
        }

        Stream.of(content.split(";")).forEach(gem -> {
            String gemDir = "uri:classloader:" + GEMS_PATH + "/" + gem + "/lib";
            if (!loadPaths.contains(gemDir)) {
                loadPaths.add(gemDir);
                log.debug("ActiveMerchant added gem entry to load paths: {}", gemDir);
            }
        });
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

As you can see this is part of a Spring-managed component in the inner jar. 如您所见,这是内部jar中Spring管理的组件的一部分。

And in it's pom.xml, I added this plugin: 在pom.xml中,我添加了此插件:

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <phase>prepare-package</phase>
            <configuration>
                <target>
                    <dirset id="gems-dir" dir="${project.build.outputDirectory}/gems" includes="*"/>
                    <property name="gem-dirs" refid="gems-dir"/>
                    <echo file="${project.build.outputDirectory}/gems/gems.list">${gem-dirs}</echo>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

This creates the file /gems/gems.list in the inner jar containing all the gems that are packed into the jar. 这将在内部jar中创建文件/gems/gems.list,其中包含打包到jar中的所有宝石。 When Spring Boot is initialising the component, it finds the gems.list and uses this to build the loadpath list. 当Spring Boot初始化组件时,它会找到gems.list并使用它来构建加载路径列表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM