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
Related
I tried to write test for my spring boot application.
My application has business logic, which start after spring app has been initialized.
There is need to test triggeration of method with annotation #EventListener(ApplicationReadyEvent.class)
Below is a simple example that doesn't work. I expect that inscription "Testing..." appears in the console.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {
MySpringBootTest.MyTestConfig.class
})
public class MySpringBootTest {
#Test
public void test() {
}
#Configuration
public static class MyTestConfig {
#EventListener(ApplicationReadyEvent.class)
public void init() {
System.out.println("Testing...");
}
}
}
how do I make this example work?
Because you are using #ContextConfiguration Spring application Context is partially loaded and you don't have access to every capability of Spring application Context, however There are many ways to achieve something you want to do. One of them is using TextExecutionListener. I will show you how to use that in both Junit 4 and Junit 5 (jupiter):
Junit 5 (jupiter):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes= MySpringBootTest.MyTestConfig.class)
#TestExecutionListeners(listeners = {MySpringBootTest.MyTestConfig.class})
public class MySpringBootTest {
#Test
public void test1() {
....
}
#Test
public void test2() {
...
}
#Configuration
public static class MyTestConfig extends AbstractTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("Testing...");
testContext.getApplicationContext(); //Do anything you want here
}
}
}
Here is a quote from Java doc for beforeTestClass method of TestExecutionListener:
Pre-processes a test class before execution of all tests within
the class. This method should be called immediately before
framework-specific before class lifecycle callbacks.
From the testContext that will be passed to this method you will have access to the application context and test class itself, you can do every thing you want with the test class there.
Junit 4:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MySpringBootTest.MyTestConfig.class})
#TestExecutionListeners(listeners = {MySpringBootTest.CustomTestExecutionListener.class,
SpringBootDependencyInjectionTestExecutionListener.class})
public class MySpringBootTest {
#Test
public void test1() {
....
}
#Test
public void test2() {
...
}
#Configuration
public static class MyTestConfig {
}
public static class CustomTestExecutionListener extends AbstractTestExecutionListener {
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
System.out.println("Testing...");
testContext.getApplicationContext(); //Do anything you want here
}
}
}
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();
}
}
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());
}
}
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();
}
}
I have simple spring boot web service, where for configuration I use .properties files. As example for spring-mail configuration, I have separate file mailing.properties located in src/main/resources/config/ folder.
in main application I include it using:
#PropertySource(value = { "config/mailing.properties" })
The problem appears when it comes to tests, I would like to use the same properties from this file, but when i try to use it, I get fileNotFaundExeption.
Question is:
Should I have separate resources in my src/test folder, or it is possible to access resources from src/main folder, if yes, how?
UPDATE added sources
test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource("classpath:config/mailing.properties")
public class DemoApplicationTests {
#Autowired
private TestService testService;
#Test
public void contextLoads() {
testService.printing();
}
}
service class:
#Service
public class TestService
{
#Value("${str.pt}")
private int pt;
public void printing()
{
System.out.println(pt);
}
}
main app class:
#SpringBootApplication
#PropertySource(value = { "config/mailing.properties" })
public class DemoApplication {
public static void main(String[] args)
{
SpringApplication.run(DemoApplication.class, args);
}
}
You can use #TestPropertySource annotation in your test class.
For example you have this attribute in your mailing.properties file:
mailFrom=fromMe#mail.com
Just annotate #TestPropertySource("classpath:config/mailing.properties") on your test class.
You should be able to read out the property for example with the #Value annotation.
#Value("${fromMail}")
private String fromMail;
To avoid annotate this annotation on multiple test classes you can implement a superclass or meta-annotations.
EDIT1:
#SpringBootApplication
#PropertySource("classpath:config/mailing.properties")
public class DemoApplication implements CommandLineRunner {
#Autowired
private MailService mailService;
public static void main(String[] args) throws Exception {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
String s = mailService.getMailFrom();
System.out.println(s);
}
MailService:
#Service
public class MailService {
#Value("${mailFrom}")
private String mailFrom;
public String getMailFrom() {
return mailFrom;
}
public void setMailFrom(String mailFrom) {
this.mailFrom = mailFrom;
}
}
DemoTestFile:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DemoApplication.class)
#TestPropertySource("classpath:config/mailing.properties")
public class DemoApplicationTests {
#Autowired
MailService mailService;
#Test
public void contextLoads() {
String s = mailService.getMailFrom();
System.out.println(s);
}
}