How to access command line args in a spring bean? - java

Question: how can I access the varargs of the startup method inside a spring #Bean like MyService below?
#SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
#Component
public MyService {
public void run() {
//read varargs
}
}
java -jar [jarfile] [Command Line Arguments]

By analyzing spring source code, it seems that spring registers a singleton bean of type ApplicationArguments in the method prepareContext of the class SpringApplication
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
So I think you can autowire this bean in your service :
#Component
public MyService {
#Autowired
private ApplicationArguments applicationArguments;
public void run() {
//read varargs
applicationArguments.getSourceArgs();
}
}

Thanks to the hint of #pvpkiran:
#Component
public class CommandLineHolder implements CommandLineRunner {
private String[] args;
#Override
public void run(String... args) throws Exception {
this.args = args;
}
public String[] getArgs() {
return args;
}
}

#Configuration
public class CheckArguments {
private ApplicationArguments applicationArguments;
CheckArguments(ApplicationArguments applicationArguments) {
this.applicationArguments = applicationArguments;
}
public void printArguments(){
for(String arg : applicationArguments.getSourceArgs()){
System.out.println(arg);
}
}
}

Related

Calling Spring Boot method from terminal

I am researching how to call a method from the terminal.
#Component
public class ApplicationAdapter implements CommandLineRunner {
#Autowired
private IApplicationPort iApplicationPort;
#Override
public void run(String... args) throws Exception {
iApplicationPort.getAll();
iApplicationPort.deleteStudentById((long) 1);
}
}
This is the main class
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
new ApplicationAdapter();
}
}
I wish to call the 2 methods: getAll(); and deleteStudentById((long) 1); from the terminal. How can I do that?
First you don't need to instantiate the ApplicationAdapter. This will be done by Spring because of the #Component annotation:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// new ApplicationAdapter(); REMOVE
}
}
Then you could use parameters that you pass when staring the application:
#Component
public class ApplicationAdapter implements CommandLineRunner {
#Autowired
private IApplicationPort iApplicationPort;
#Override
public void run(String... args) throws Exception {
if (args[0].equals("all")) {
iApplicationPort.getAll();
} else if (args[0].equals("delete"))
iApplicationPort.deleteStudentById(Long.parseLong(args[1]));
}
}
}
Then you can start your app like:
java -jar yourApp.jar all
java -jar yourApp.jar delete 1

I cannot use Autowired inside my main application

I am trying to instantiate the customer class inside the Running class. Although I ANNOTATED IT AS Component, there is a null pointer exception
#Component
public class Running {
#Autowired
CustomerRepository cRep;
#Autowired InvoiceRepository iRep;
public void run() {
Customer c1=new Customer().setAge(25).setBalance(100).setName("xyz");
Customer c2=cRep.save(c1);
}
#SpringBootApplication
#EnableJpaRepositories
public class HelloWorldSpringBootApp {
#Autowired static
Running r1;
public static void main (String[] args ) {
SpringApplication.run(HelloWorldSpringBootApp.class, args);
r1.run();
}
I am getting a null pointer exception on r1.run().
Spring does not allow you to autowire static fields (see this question). Here is the right way to get a Running instance:
#SpringBootApplication
#EnableJpaRepositories
public class HelloWorldSpringBootApp {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(HelloWorldSpringBootApp.class, args);
Running r1 = applicationContext.getBean(Running.class);
r1.run();
}
}

Spring Boot run controller without request

Is there a way to run a controller for initializing some data before Spring Boot starts the Tomcat?
My current code looks like that:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Controller controller = (Controller) context.getBean("controller");
controller.start();
context.close();
SpringApplication.run(Application.class, args);
}
}
#Controller
#Component("controller")
public class Controller {
#Autowired
private Runner runner;
public void start() {
runner.test();
}
}
#Configuration
#PropertySource("classpath:config.properties")
#Component("runner")
public class Runner {
#Value("${name}")
private String name;
public void test() {
System.out.println("hello " + name)
}
public String getName() {
return name;
}
}
#Controller
public class HelloController {
#Autowired
private Runner runner;
#RequestMapping("/hello")
public CalenderCollection data(#PathVariable("name")String name, Model model) {
model.addAttribute("name", runner.getName());
return "hello";
}
}
#Configuration
#ComponentScan(basePackages = "com.test")
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This prints the correct name into the console. But when I visit the url then runner is null. Then I thought about to change the Application and Controller class to this:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#Controller
#Component("controller")
public class Controller {
#Autowired
private Runner runner;
public Controller() {
runner.test();
}
}
But now I have the problem that runner is null right in the beginning. What would be a right way to collect some data in the beginning and then continue with the process?
Usually you use ApplicationRunner or CommandLineRunner to run some code before application started.
From documentation
If you need to run some specific code once the SpringApplication has
started, you can implement the ApplicationRunner or CommandLineRunner
interfaces. Both interfaces work in the same way and offer a single
run method which will be called just before SpringApplication.run(…​)
completes.
The CommandLineRunner interfaces provides access to application
arguments as a simple string array, whereas the ApplicationRunner uses
the ApplicationArguments interface discussed above.
import org.springframework.boot.*
import org.springframework.stereotype.*
#Component
public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}
If you are just trying to run some code when the application has started, then there is no need to use Controller. Spring provides various application lifecycle hooks for such use cases.
The code would most likely look like this
#Component
public class MyListener
implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Runner runner;
public void onApplicationEvent(ContextRefreshedEvent event) {
runner.test();
}
}
Check out this blog post and this part of the documentation for more information
If you want to initialize some values you can do something like this :
#SpringBootApplication
public class Application implements CommandLineRunner {
#Override
public void run( String... args ) throws Exception {
//initialise your value here
}
}
If this is something that affects only that class you can use
#PostConstruct
on a method of your controller class.
If this is something that is linked to the whole application, you should consider
creating an application listener on ApplicationReadyEvent

Clean code - Where should #Autowired be applied?

I'll start with a simple example. You have a Spring boot application that runs a CommandLineRunner class on initialization.
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
#Autowired //IntelliJ Warning
private DataSource ds;
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.java
#SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner();
}
}
Now, like this, this works, everything is OK. However, IntelliJ reports a warning where #Autowired is located (I marked where in the comment)
Spring team recommends: Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.
Now if I follow this, I have a constructor based dependency injection
#Autowired
public MyCommandLineRunner(DataSource ds) { ... }
This also means that I have to edit Application.java as well, since the constructor needs an argument. In Application.java if I try to use the setter injection, I'll get the same warning. If I refactor that as well, I'll end up with some, in my opinion, nasty code.
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private DataSource ds;
#Autowired // Note that this line is practically useless now, since we're getting this value as a parameter from Application.java anyway.
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.java
#SpringBootApplication
public class Application {
private DataSource ds;
#Autowired
public Application(DataSource ds) { this.ds = ds; }
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
The above code yields the same result, but doesn't report any warnings in IntelliJ.
I'm confused, how is the 2nd code better than the first one? Am I following an incorrect logic? Should this be wired differently?
In short, what's the correct way to do this?
note DataSource is just a pure example, this question applies to anything being autowired.
note 2 Just to say that MyCommandLineRunner.java can't have another, empty, constructor, since DataSource needs to be autowired/initialized. It will report an error and will not be compiled.
There are several ways to improve it.
You can remove #Autowired from your MyCommandLineRunner as you are letting a #Bean method construct an instance of it. Inject the DataSource directly into the method as an argument.
Or remove #Autowired and remove the #Bean and slap a #Component annotation on your MyCommandLineRunner to have it detected and remove factory method.
Inline your MyCommandLineRunner inside your #Bean method as a lambda.
No Autowiring in the MyCommandLineRunner
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
And the application class.
#SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return new MyCommandLineRunner(ds);
}
}
Usage of #Component
#Component
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
And the application class.
#SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
Inline CommandLineRunner
#SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class)
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return (args) -> (logger.info("DataSource: {}", ds);
}
}
All of these are valid ways of constructing your instances. Which one to use, use the one that you feel comfortable with. There are more options (all variations on the ones mentioned here).
Consider making the field ds final, then you don't need #Autowired. See more about dependency injection http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html#using-boot-spring-beans-and-dependency-injection
To keep the code clean, have you considered using Lombok annotations? #RequiredArgsConstructor(onConstructor = #__(#Autowired))
would generate the constructor with #Autowired annotations. See more here
https://projectlombok.org/features/Constructor.html
Your code could look like this:
#Slf4j
#RequiredArgsConstructor
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
//final fields are included in the constructor generated by Lombok
private final DataSource ds;
#Override
public void run(String... args) throws Exception {
log.info("DataSource: {} ", ds.toString());
}
}
// Application.java
#SpringBootApplication
#RequiredArgsConstructor(onConstructor_={#Autowired}) // from JDK 8
// #RequiredArgsConstructor(onConstructor = #__(#Autowired)) // up to JDK 7
public class Application {
private final Datasource ds;
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
#Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
Later edit
Solution without Lombok relies on Spring to inject dependency when the bean is created
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
/**
* dependency ds is injected by Spring
*/
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return new MyCommandLineRunner(ds);
}
}
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds){
this.ds = ds;
}
#Override
public void run(String... args) throws Exception {
logger.info("DataSource: "+ ds.toString());
}
}

Spring Boot Data MongoDB - Repository is null

I want to develop a little test application using Spring Boot and Spring Data MongoDB. So, in this case, I use default configuration (like localhost:27017/test database) and I try to follow the spring guide (https://spring.io/guides/gs/accessing-data-mongodb/).
I launch my application like this:
#SpringBootApplication
public class Application implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) throws Exception {
(new DummyClass()).load();
}
}
The DummyClass is as following:
#org.springframework.stereotype.Component
#ConfigurationProperties(prefix="dummy")
public class DummyClass {
private static String url;
private List<Project> projects;
#Autowired
private ProjectRepository projectRepository;
public void setUrl(String url) {
DummyClass.url = url;
}
#Override
public void load() {
// (...) creating some project objects
projectRepository.deleteAll();
projectRepository.save(this.projects);
}
}
When the projectRepository.deleteAll() statement is executed, I receive un NullPointerException.
For information, below the ProjectRepository interface:
public interface ProjectRepository extends MongoRepository<Project, String>
{
}
And my package structure is:
com.test.dummy
Application.java
com.test.dummy.components
DummyClass.java
com.test.dummy.domain
Project.java
com.test.dummy.repositories
ProjectRepository.java
Can you help me to understand my error?
Note: I use Spring Boot 1.4.1 and Mongo 3.2
Inside your Application.run() method you instantiate your DummyClass outside of the Spring context (new DummyClass()). This way the
#Autowired
private ProjectRepository projectRepository;
is not instantiated correctly.
You should inject your DummyClass via Spring and not create a new instance of it via its constructor.
Example:
#SpringBootApplication
public class Application implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
#Autowired
DummyClass dummyClass;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) throws Exception {
dummyClass.load();
}
}

Categories