Spring for POJAs (Plain old java applications) - Revisited

by AlphaGeek

Thu, Oct 25, 2018


Back in the distant past, I wrote this article showing how to use spring to initialize a command line application. I used this technique often to create command line tools that could make use of many of the java components I built for web applications and other J2EE deployments. Now if you want to use Spring Framework in your applications you have many more options. I will start with something that is largely similar to what I presented in my 2006 article.

CommandLineRunner

Command.java

package com.computersfearme.spring.poja;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
public class Command implements ExitCodeGenerator {
    private static final Logger logger = LoggerFactory.getLogger(Command.class);

    private int exitCode = 1;

    public Command() {
    }

    public void run(String... args) {
        logger.info("Running {}", Arrays.toString(args));
        exitCode = 0;
    }

    @Override
    public int getExitCode() {
        return exitCode;
    }
}

This class is a placeholder for the functionality of your command. You can name it whatever you want, and if you care about what the exit code for the command will be you can implement ExitCodeGenerator and return whatever value is appropriate, otherwise the command will always return 0.

SpringPoja1Application.java

package com.computersfearme.spring.poja;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringPoja1Application implements CommandLineRunner {
    private static final Logger logger = LoggerFactory.getLogger(SpringPoja1Application.class);
    private final Command command;

    @Autowired
    public SpringPoja1Application(Command command) {
        this.command = command;
    }

    @Override
    public void run(String... args) throws Exception {
        command.run(args);
    }

    public static void main(String[] args) {
        logger.info("Starting...");
        int exitCode = 1;
        try {
            ApplicationContext applicationContext = SpringApplication.run(SpringPoja1Application.class, args);
            exitCode = SpringApplication.exit(applicationContext);
        } catch(Exception ex) {
            logger.error("error running command:", ex);
        } finally {
            logger.info("Done");
        }
        System.exit(exitCode);
    }
}

This is a bit of code to get things bootstrapped and propagate any exit code while exiting. Command is injected into the application object and called in the run(String... args) are called. The main method is a little different from the default that you get from Spring Intializr. First, if any error occurs, the exit code will be ‘1’. Second, the call to SpringApplication.exit triggers the processing of the ExitCodeGenerators.

You will probably want to either suppress the banner or replace it with something of your design. That can be done by switching to a SpringApplicationBuilder (See below, the Spring Shell example uses this technique.).

Spring Shell

Spring Shell takes a different approach. This project provides a spring configured application shell that a user can interact with. You could create applications like Cassandra’s cqlsh. The idea is that you create methods that are invoked when their “key” is used in the shell and the arguments are passed in from the command line:

Spring Shell Screen Capture

Here is the code that does this:

SpringPojaShellApplication.java

package com.computersfearme.spring.poja.shell;

import org.springframework.boot.Banner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class SpringPojaShellApplication {

	public static void main(String[] args) {
		new SpringApplicationBuilder(SpringPojaShellApplication.class)
			.bannerMode(Banner.Mode.LOG)
			.web(WebApplicationType.NONE)
			.build()
			.run(args);
	}
}

This is very simple. it uses the SpringApplciationBuilder to construct a SpringApplication and then run it with the program’s command line arguments. As you can see, we are sending the banner to the LOG and we are turning off any web features. The default ApplicationRunner, ScriptShellApplicationRunner, will look for arguments that start with ‘@’ and interpret the remainder of the string as a script file name and attempt to run the script.

ShellCommand.java

package com.computersfearme.spring.poja.shell;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class ShellCommand {
    private static final Logger logger = LoggerFactory.getLogger(ShellCommand.class);

    @ShellMethod("testing shell commands")
    public String test(@ShellOption({"-a", "--alpha"}) String a,
                       @ShellOption({"-b", "--beta"}) String b) {
        return String.format("alpha = %s, beta = %s", a, b);
    }
}

This is the “test” command that you saw in the screencast above. All it does is take it two arguments, format a string using them and outputs that string. @Shell... annotations allow you to customize the behavior of the command’s input. Of course, you can add all kinds of good stuff. You can inject any components in the context.

One problem is that there is no way to specify a command on the command line and have the shell application execute the command and exit. This seems like a very common pattern I have seen in programs that can present REPLs and it was present in the 1.x versions of the project. I may spend some time figuring out a solution for this and, if I do, I will it up on this blog.

TL;DR

The Spring Boot framework provides a few different ways to create command line applications. You can either use a CommandLineRunner to run logic after the application context is fully initialized or you can create a REPL using @ShellComponent annotated classes. Either way, you get the full capability of the Spring Framework to initialize and manage your application components. You can include any other of the many Spring Projects or any other library that your use case needs.