Spring console application configured using annotations

后端 未结 7 1865
谎友^
谎友^ 2021-01-30 13:33

I want to create spring console application (running from command line with maven for example: mvn exec:java -Dexec.mainClass=\"package.MainClass\").

Is this application

相关标签:
7条回答
  • 2021-01-30 13:56

    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();
        }
    }
    
    0 讨论(0)
  • 2021-01-30 14:03

    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.

    0 讨论(0)
  • 2021-01-30 14:09

    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.

    0 讨论(0)
  • 2021-01-30 14:09

    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();
    
        }
    
    }
    
    0 讨论(0)
  • 2021-01-30 14:10

    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();
        }
    }
    
    0 讨论(0)
  • 2021-01-30 14:12

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题