简体   繁体   中英

Groovy command using inheritance cannot compile in Spring Boot remote shell

I have an abstract groovy class with some utility methods that I want to extend from other groovy command classes (for use in Spring Boot's remote shell). However, when I attempt to run the groovy command class, I get a CommandException .

My groovy abstract class looks like the following.

package commands 
import com.xyz.MyService
import org.crsh.command.InvocationContext
import org.springframework.beans.factory.BeanFactory

abstract class abstractcmd {
    private static final String SPRING_FACTORY = "spring.beanfactory"

    protected MyService getMyService(InvocationContext context) {
        return getBeanFactory(context).getBean(MyService.class);
    }

    private BeanFactory getBeanFactory(InvocationContext context) {
        return context.attributes[SPRING_FACTORY];
    }
}

My groovy command class looks like the following.

package commands
import org.crsh.cli.Command
import org.crsh.cli.Usage
import org.crsh.command.InvocationContext

@Usage("do something commands")
class foo extends abstractcmd {
    @Command
    @Usage("bar")
    def String bar(InvocationContext context) {
        try {
            getMyService(context).bar()
            return "did bar"
        } catch (Exception e) {
            return String.format("could not do bar: %s", e.toString())
        }
    }
}

When I SSH into the shell and execute foo bar I get the following exception.

org.crsh.shell.impl.command.spi.CommandException: Could not create command foo instance
    at org.crsh.lang.impl.groovy.GroovyCompiler$1.getCommand(GroovyCompiler.java:192) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.LanguageCommandResolver.resolveCommand(LanguageCommandResolver.java:101) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSH.getCommand(CRaSH.java:100) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.getCommand(CRaSHSession.java:96) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.PipeLineFactory.create(PipeLineFactory.java:89) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.ScriptRepl.eval(ScriptRepl.java:88) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.createProcess(CRaSHSession.java:163) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.async.AsyncProcess.execute(AsyncProcess.java:172) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.iterate(Console.java:219) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:158) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:135) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.jline.JLineProcessor.run(JLineProcessor.java:204) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.ssh.term.CRaSHCommand.run(CRaSHCommand.java:99) ~[crash.connectors.ssh-1.3.2.jar:?]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]

If I remove the package commands line from both the abstractcmd and foo classes, then the IDE (IntelliJ) and shell both complain about abstractcmd not being found.

2017-03-19 19:34:06 ERROR org.crsh.shell.impl.command.CRaSHProcess.execute:84 - Error while evaluating request 'foo' Could not compile command script foo
org.crsh.shell.impl.command.spi.CommandException: Could not compile command script foo
    at org.crsh.lang.impl.groovy.GroovyClassFactory.parse(GroovyClassFactory.java:65) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.groovy.GroovyCompiler$1.getCommand(GroovyCompiler.java:172) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.LanguageCommandResolver.resolveCommand(LanguageCommandResolver.java:101) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSH.getCommand(CRaSH.java:100) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.getCommand(CRaSHSession.java:96) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.PipeLineFactory.create(PipeLineFactory.java:89) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.lang.impl.script.ScriptRepl.eval(ScriptRepl.java:88) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.command.CRaSHSession.createProcess(CRaSHSession.java:163) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.shell.impl.async.AsyncProcess.execute(AsyncProcess.java:172) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.iterate(Console.java:219) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:158) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.Console.on(Console.java:135) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.console.jline.JLineProcessor.run(JLineProcessor.java:204) ~[crash.shell-1.3.2.jar:?]
    at org.crsh.ssh.term.CRaSHCommand.run(CRaSHCommand.java:99) ~[crash.connectors.ssh-1.3.2.jar:?]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]
Caused by: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
foo: 9: unable to resolve class abstractcmd 
 @ line 9, column 1.
   @Usage("bar")
   ^

1 error

    at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310) ~[groovy-2.4.7.jar:2.4.7]
    at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:946) ~[groovy-2.4.7.jar:2.4.7]
    at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:593) ~[groovy-2.4.7.jar:2.4.7]
    at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:542) ~[groovy-2.4.7.jar:2.4.7]
    at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298) ~[groovy-2.4.7.jar:2.4.7]
    at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268) ~[groovy-2.4.7.jar:2.4.7]
    at org.crsh.lang.impl.groovy.GroovyClassFactory.parse(GroovyClassFactory.java:59) ~[crash.shell-1.3.2.jar:?]
    ... 14 more

The only way that I am able to get a custom command to work properly if is I don't extend abstractcmd (thereby implement the "util" methods in the command class) and remove the package line.

I am using Spring Boot v1.5.1.RELEASE.

Any ideas on what I'm doing wrong?

Looks like CRaSH has issues with groovy classes extending other groovy classes, because if you don't extend one, it works just fine.

I'm sorry but I did not have time to fully investigate how and why (corrections/updates are more than welcome), and didn't find anything in the docs but the good news is that you can extend an abstract java class to achieve the same thing. I stumbled upon such a sample for the cron command that extends a GroovyCommand abstract class with the following javadoc:

/**
 * The base command for Groovy class based commands.
 */
public abstract class GroovyCommand extends BaseCommand implements GroovyObject

So, to cut a long story short, you could use the following workaround:

Abstract SpringAwareCommand java class

package crash.commands;

import org.crsh.command.BaseCommand;
import org.springframework.beans.factory.BeanFactory;

public abstract class SpringAwareCommand extends BaseCommand {

    private static final String SPRING_BEAN_FACTORY = "spring.beanfactory";

    protected <T> T getBean(Class<T> beanClass) {
        return ((BeanFactory) this.context.getAttributes().get(SPRING_BEAN_FACTORY)).getBean(beanClass);
    }

}

Updated foo groovy class

package crash.commands

import com.xyz.MyService
import org.crsh.cli.Command
import org.crsh.cli.Usage

@Usage("do something commands")
class foo extends SpringAwareCommand {
    @Command
    @Usage("bar")
    def String bar() {
        try {
            getBean(MyService.class).bar()
            return "did bar"
        } catch (Exception e) {
            return String.format("could not do bar: %s", e.toString())
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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