Spring console application configured using annotations
I want to create spring console application (running from command line with开发者_C百科 maven for example: mvn exec:java -Dexec.mainClass="package.MainClass").
Is this application I want to have some kind of services and dao layers. I know how to do it for a web application but I have not found any information on how to do in case of a console application (leter maybe with Swing).
I'm trying to create something like:
public interface SampleService {
public String getHelloWorld();
}
@Service
public class SampleServiceImpl implements SampleService {
public String getHelloWorld() {
return "HelloWorld from Service!";
}
}
public class Main {
@Autowired
SampleService sampleService;
public static void main(String [] args) {
Main main = new Main();
main.sampleService.getHelloWorld();
}
}
Is it possible? Can I find somewhere an example of how to do it?
Take a look at the Spring Reference, 3.2.2 Instantiating a container.
In order to use Spring in console application you need to create an instance of ApplicationContext
and obtain Spring-managed beans from it.
Creating a context using XML config is described in the Reference. For completely annotation-based approach, you can do someting like this:
@Component // Main is a Spring-managed bean too, since it have @Autowired property
public class Main {
@Autowired SampleService sampleService;
public static void main(String [] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext("package"); // Use annotated beans from the specified package
Main main = ctx.getBean(Main.class);
main.sampleService.getHelloWorld();
}
}
The Spring Reference suggests using ClassPathXmlApplicationContext in the main
method to create the application context, then calling the getBean
method to get an initial reference to a bean from the application context. After writing this same code a few times, you wind up refactoring the boilerplate into this utility class:
/**
* Bootstraps Spring-managed beans into an application. How to use:
* <ul>
* <li>Create application context XML configuration files and put them where
* they can be loaded as class path resources. The configuration must include
* the {@code <context:annotation-config/>} element to enable annotation-based
* configuration, or the {@code <context:component-scan base-package="..."/>}
* element to also detect bean definitions from annotated classes.
* <li>Create a "main" class that will receive references to Spring-managed
* beans. Add the {@code @Autowired} annotation to any properties you want to be
* injected with beans from the application context.
* <li>In your application {@code main} method, create an
* {@link ApplicationContextLoader} instance, and call the {@link #load} method
* with the "main" object and the configuration file locations as parameters.
* </ul>
*/
public class ApplicationContextLoader {
protected ConfigurableApplicationContext applicationContext;
public ConfigurableApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* Loads application context. Override this method to change how the
* application context is loaded.
*
* @param configLocations
* configuration file locations
*/
protected void loadApplicationContext(String... configLocations) {
applicationContext = new ClassPathXmlApplicationContext(
configLocations);
applicationContext.registerShutdownHook();
}
/**
* Injects dependencies into the object. Override this method if you need
* full control over how dependencies are injected.
*
* @param main
* object to inject dependencies into
*/
protected void injectDependencies(Object main) {
getApplicationContext().getBeanFactory().autowireBeanProperties(
main, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
}
/**
* Loads application context, then injects dependencies into the object.
*
* @param main
* object to inject dependencies into
* @param configLocations
* configuration file locations
*/
public void load(Object main, String... configLocations) {
loadApplicationContext(configLocations);
injectDependencies(main);
}
}
Call the load
method in your application main method. Notice that the Main
class is not a Spring-created bean, and yet you can inject one of its properties with a bean from the application context.
public class Main {
@Autowired
private SampleService sampleService;
public static void main(String[] args) {
Main main = new Main();
new ApplicationContextLoader().load(main, "applicationContext.xml");
main.sampleService.getHelloWorld();
}
}
I'd to figure this out for a project recently. I was building a CLI for a utility that would be run from a scheduled job and reused part of the web application code for the project. I had a problem bootstrapping all of the @Autowired dependencies, and I didn't actually need them all, so I bootstrapped the specific dependencies in the main class using the AnnotationConfigApplicationContext register(java.lang.Class...) method as follows:
@Component
public class SpringAppCLI
{
/**
* Service to be autowired!
*/
@Autowired
private SampleService sampleService;
/**
*
*/
public static void main(String[] args) throws Exception {
final AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// setup configuration
applicationContext.register(SampleServiceConfig.class);
applicationContext.register(SampleServiceRepository.class);
applicationContext.register(JpaConfig.class);
applicationContext.register(CommandLineConfig.class);
applicationContext.register(SampleService.class);
applicationContext.register(SpringAppCLI.class);
// add CLI property source
applicationContext.getEnvironment().getPropertySources()
.addLast(new SimpleCommandLinePropertySource(args));
// setup all the dependencies (refresh) and make them run (start)
applicationContext.refresh();
applicationContext.start();
try {
SpringAppCLI springAppCLI = applicationContext.getBean(SpringAppCLI.class);
springAppCLI.doWhatever();
} catch (Exception e) {
//some handling
} finally {
applicationContext.close();
}
}
}
and here's the configuration class:
@Configuration
@ComponentScan(basePackageClasses = SolrLoadCLI.class, includeFilters = @Filter(Controller.class), useDefaultFilters = false)
class CommandLineConfig implements ApplicationContextAware {
/**
*
*/
private ApplicationContext applicationContext;
/**
*
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
/**
*
* @return
*/
@Bean
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Resource[] resourceArray = new Resource[2];
resourceArray[0] = new ClassPathResource("/SampleService.properties");
resourceArray[1] = new ClassPathResource("/Database.properties");
ppc.setLocations(resourceArray);
return ppc;
}
}
Regarding Chin Huang's answer above...
Your example won't actually work, or at least isn't working for me locally. That's because you're initializing the SampleService
object using @Autowired
, but you specify AutowireCapableBeanFactory.AUTOWIRE_NO
on the properties. Instead set it to AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE
or AutowireCapableBeanFactory.AUTOWIRE_BY_NAME
.
Also, this is odd, so I may be doing something wrong. But it seems that with AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE
, I have to have a setProp()
with @Autowired
for it to work. So instead of this:
public class Main {
@Autowired
private SampleService sampleService;
public static void main(String[] args) {
Main main = new Main();
ApplicationContextLoader loader = new ApplicationContextLoader();
loader.load(main, "applicationContext.xml");
main.sampleService.getHelloWorld();
}
}
I have to do this:
public class Main {
private SampleService sampleService;
public static void main(String[] args) {
Main main = new Main();
ApplicationContextLoader loader = new ApplicationContextLoader();
loader.load(main, "applicationContext.xml");
main.sampleService.getHelloWorld();
}
@Autowired
public void setSampleService(SampleService sampleService) {
this.sampleService = sampleService;
}
}
If I have, as in Chin's original example, private data with @Autowired
, the DI fails. I'm using 3.1.1.RELEASE and I think some of the auto-wiring stuff has changed in 3.1.x, so it may be due to that. But I'm curious as to why this wouldn't work, since that's consistent with earlier releases of Spring.
You can do it this way :
- Do the initialization in your main method
- You can then use the start method as your sudo controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import com.org.service.YourService;
@Component
public class YourApp{
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"ApplicationContext.xml");
YourApp p = context.getBean(App.class);
p.start(args);
}
@Autowired
YourService yourService;
private void start(String[] args) {
yourService.yourMethod();
}
}
This was my solution to run an exit. I use it in a module which works as common ground to all other specific ones: a website and an API one. When I specify the right arguments on the right module it will run the right task.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class CLIApp {
public static void main(String[] args) {
ConfigurableApplicationContext ctx =
new SpringApplicationBuilder(CLIApp.class)
.web(false)
.properties("spring.jmx.enabled=false")
.run(args);
final int exitCode = SpringApplication.exit(ctx);
System.out.println("************************************");
System.out.println("* Console App sucessfully executed *");
System.out.println("************************************");
System.exit(exitCode);
}
}
As you see, I also disabled the unused web environment and JMX. I will focus on scanning the classpath from the package of the class and use the autoconfiguration skills of Spring Boot. After the application finishes doing what it needs it closes like a console app.
I used EliuX's approach with other findings from around the web and came up with this one-class command line application.
It also demonstrates how you can take advantage of the annotation scanning and spring context to pull in, say, spring services from other parts of your application to use in the CLI.
Also note that the API has changed for .web since @EliuX's answer above.
// spring boot imports
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.Banner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
// spring imports
import org.springframework.context.annotation.ComponentScan;
// my import from another package that has spring annotations like
// @Service, @Component, @Autowired, to demonstrate how I can wrap those
// in a command line application
import my.packages.having.annotations.services.MyOtherService;
// This starts the spring container
@SpringBootApplication
// I deliberately scan packages in MY namespace that I know to have
// Spring annotations
@ComponentScan(value = "my.packages.having.annotations.*")
public class MyCliWithSpringAnnotations implements ApplicationRunner
{
// I can autowire other services in since spring instantiates
// this CLI class - as long as these are component-scanned (above)
private MyOtherService _otherService;
@Autowired
public MyCliWithSpringAnnotations(MyOtherService otherService)
{
_otherService = otherService;
}
// This implements the ApplicationRunner interface which Spring is going
// to find, then instantiate, then autowire, and then _run_ when I call
// run() below in the main method.
// This can be be implemented in any other scanned class (or classes)
// on the classpath and will be run, but I'm sticking it all in one
// class for simplicity.
@Override
public void run(ApplicationArguments args) throws Exception
{
// getSourceArgs() returns the original String[] of command
// line args to the main() method
_otherService.toSomethingWithThese(args.getSourceArgs());
}
public static void main(String... args)
{
new SpringApplicationBuilder(MyCliWithSpringAnnotations.class)
.web(WebApplicationType.NONE)
.bannerMode(Banner.Mode.OFF)
.logStartupInfo(false)
.build()
.run(args);
}
}
精彩评论