Why do I need to add #Component when I have to schedule the task in spring boot - spring-boot

I didn't understand the proper use of #Componenet, #Configuration,#Bean annotation.
I want to run one method in every 60 seconds.Please check the below code. If I don't give #Component annotation then it doesn't run. so What is the use of #Component in this context?
#EnableScheduling
public class SchedulingProjectApplication {
private static final Logger log =
LoggerFactory.getLogger(SchedulingProjectApplication.class);
public static void main(String[] args) {
SpringApplication.run(SchedulingProjectApplication.class, args);
}
#Scheduled(fixedDelay = 6000)
public void r()
{
log.info("Start- main-job");
log.info("stop-main-job");
}
}

There are several problems with this piece of code:
Your Spring Boot application is not flagged with #SpringBootApplication (or #EnableAutoConfiguration). As a result, the auto-configuration will not kick in at all (Spring Boot will start your app but won't do anything with it besides basic stuff such as env preparation, etc). It's perfectly fine in certains cases but that isn't probably what you want
You've flagged the run task directly on your app. It's ok for demo but it would be better to move that logic in its own class
So to answer your question: SchedulingProjectApplication is the root source of your app but it's just a simple POJO. There's nothing that instructs the container to process it. Usually the app is a #Configuration (you can use one of the #EnableXYZ on it, you can define additional beans, etc.
If you add #SpringBootApplication on your class, it will scan any #Component in the same package of your app (and all the sub-packages).
More details about code structure in the documentation
One basic/simple structure for you would be:
package com.example.foo;
#SpringBootApplication
#EnableScheduling
public class SchedulingProjectApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulingProjectApplication.class, args);
}
}
And
package com.example.foo;
#Component
public class SchedulingLogger {
private static final Logger log =
LoggerFactory.getLogger(SchedulingLogger.class);
#Scheduled(fixedDelay = 6000)
public void r()
{
log.info("Start- main-job");
log.info("stop-main-job");
}
}
There are other things that you should be aware with regards to configuration (such as moving decisions outside of your #SpringBootApplication if you use slicing).

Related

How to execute code in a SpringBootTest before the Application is run?

I have a SpringBoot based command line application. The application creates or deletes some records in a database. It does so not directly via JDBC but rather through a special API (instance variable dbService).
The application class looks like this:
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private DbService dbService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) {
// Do something via dbService based on the spring properties
}
}
Now I'd like to create a SpringBoot test that would run the whole application with a configuration specially prepared for the test.
I run the test with an in-memory DB (H2) which is empty at the test start. Hence I'd like to insert some records into the DB -- as the setup for the test. The code for inserting the records must be executed
After the Spring context has been loaded -- so that I can use the bean dbService.
Before the Application is run -- so that the application runs with the prepared DB.
Somehow I fail to implement the above two points.
What I have so far is this:
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
#ActiveProfiles("specialtest")
public class MyAppTest {
#Autowired
private DbService dbService;
private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
// The expectation is that this method is executed after the spring context
// has been loaded and all beans created, but before the Application class
// is executed.
#EventListener(ApplicationStartedEvent.class)
public void preparedDbForTheTest() {
// Create some records via dbService
logger.info("Created records for the test");
}
// This test is executed after the application has run. Here we check
// whether the DB contains the expected records.
#Test
public void testApplication() {
// Check the DB contents
}
}
My problem is that the the method preparedDbForTheTest does not seem to get executed at all.
According to the SpringBoot docs, the event ApplicationReadyEvent is sent exactly when I want to execute the setup code. But somehow the code is not executed.
If I annotate the method with #Before... (I tried several variants of it) then it gets executed, but after the Application class has run.
What am I doing wrong?
Test classes aren't Spring-managed beans so things like #EventListener methods will be ignored.
The most conventional solution to your problem would be to add some #TestConfiguration that declares the #EventListener:
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class MyAppTest {
private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
#Test
public void testApplication() {
}
#TestConfiguration
static class DatabasePreparation {
#EventListener(ApplicationStartedEvent.class)
public void preparedDbForTheTest() {
logger.info("Created records for the test");
}
}
}
A #TestConfiguration is additive so it'll be used alongside your application's main configuration. The preparedDbForTheTest method will now be called as part of refreshing the application context for the tests.
Note that, due to application context caching, this method won't be called for every test. It will only be called as part of refreshing the context which may then be shared among several tests.

#Autowired notation is not working as expected

#SpringBootApplication
public class MainApplication {
#Autowired
static BibliographyIndexer bi;
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
bi.reindex();
}
}
#Repository
public class BibliographyIndexer {
...
}
Whenever I access the properties of bi I get a NullPointerException. I know the #Autowired notation didn't work. But why?
Note: both classes are under the same package.
Additional question: Since I want to run a method upon the start of the spring application. Is this the best approach since #pepevalbe's answer already gave me the workaround I needed. Is there another way to run a method upon the start of the spring application?
Because you can't #Autorwire an static class. It doesn't get initialized so you get a NPE when trying to use it.
There are workarounds to wire a bean into a static class, but it is strongly discouraged.
EDIT:
If you need to execute code after initilization you could add an event listener:
#SpringBootApplication
public class MainApplication {
#Autowired
BibliographyIndexer bi;
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
#EventListener(ApplicationReadyEvent.class)
public void doAfterStartUp() {
bi.reindex();
}
}
There are several reasons #Autowired might not work.
When a new instance is created not by Spring but by for example manually calling a constructor, the instance of the class will not be registered in the Spring context and thus not available for dependency injection. Also when you use #Autowired in the class of which you created a new instance, the Spring context will not be known to it and thus most likely this will also fail.
Another reason can be that the class you want to use #Autowired in, is not picked up by the ComponentScan. This can basically be because of two reasons.
The package is outside the ComponentScan search path. Move the package to a scanned location or configure the ComponentScan to fix this.
The class in which you want to use #Autowired does not have a Spring annotation. Add one of the following annotatons to the class: #Component, #Repository, #Service, #Controller, #Configuration. They have different behaviors so choose carefully........
Your problem is that you cannot use bi in main because main is static.
Making bi static doesn't help because static fields will not be #Autowired (It is possible but does not make sense in the concepts of Dependency Injection).
Remove static and move bi.reindex() to a new method annotated with #PostConstruct. It will be executed after the MainApplication-bean is fully initialized and here you can use your injected bi.
In main method you can refer to context and from it get access to bean BibliographyIndexer. In static main spring can not creates and injects bean so this is how you can get it from context.
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(MainApplication.class, args);
BibliographyIndexer bibliographyIndexer = context.getBean(BibliographyIndexer.class);
bibliographyIndexer.reindex();
}
You can also do as in answer from pepevalbe and execute this code after initialization.

how to conditionally not create beans in spring boot?

In my application, I have a component that reads data from other system when the application is started.
However, during testing, I don't want this component to be created
#Component
#Slf4j
public class DeviceStatisticsSyncHandler {
#EventListener
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
#Value("${test.mode:false}")
public boolean serviceEnabled;
}
I can use condition to solve this, but other code readers need to understand, so I don't think this is a very good method:
#EventListener(condition = "#deviceStatisticsSyncHandler .isServiceEnabled()")
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
public boolean isServiceEnabled() {
return !serviceEnabled;
}
#Value("${test.mode:false}")
public boolean serviceEnabled;
My application doesn't use Profiles, is there any other method to solve this problem.
Spring Boot version:2.1.3
One possible option is not to load the DeviceStaticsticsSyncHandler at all if you're in a test mode.
The "test.mode" is not a good name here, because the production code contains something tightly bound to the tests.
How about the following approach:
#Component
#ConditionalOnProperty(name ="device.stats.handler.enabled", havingValue = "true", matchIfMissing=true)
public class DeviceStatisticsSyncHandler {
// do whatever you need here, but there is no need for "test.mode" enabled related code here
}
Now in Tests you can define a test property "device.stats.handler.enabled=false" on the test itself or even place that definition in src/test/reources/application.properties so it will be false for all tests in the module.
An obvious advantage is that this definition is pretty much self explanatory and can be easy understood by other project maintainers.
for me, it's not the case of the condition rather environment-related. I will solve this problem using spring profile.
Step 1: Create an Interface first
public interface DeviceStatisticsSyncHandler {
public void handle(ApplicationReadyEvent event);
}
Step 2: Create an Implementation for production
#Component
#Profile("!test")
public class DeviceStatisticsSyncHandlerImpl implements DeviceStatisticsSyncHandler {
#EventListener
#Override
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
}
step 3: create an implementation of test
#Component
#Profile("test")
public class DeviceStatisticsSyncHandlerTestImpl implements DeviceStatisticsSyncHandler {
#EventListener
#Override
public void handle(ApplicationReadyEvent event) {
//do Nothing
}
}
final step
All you need to do is set/toggle the property
-Dspring.profiles.active=test
or
-Dspring.profiles.active=prod
I found a way to achieve this without any further external configuration required.
The idea is to create a general configuration that applies to all integration tests and use #MockBean there to replace the real bean. So one should create a class like this under the test classpath (i.e. that is not scanned during normal application launch):
#Configuration
public class IntegrationTestConfiguration
{
#MockBean
public DeviceStatisticsSyncHandler deviceStatisticsSyncHandler;
}
I was actually surprised that #MockBean can be used here, but the Javadoc explicitly points that out: Can be used as a class level annotation or on fields in either #Configuration classes, or test classes that are #RunWith the SpringRunner..

How can I run a specific class / utility in a Spring Boot application with wiring?

I have my standard Spring Boot application working. I have situations where I want to run a "job" which is basically some specific method normally run via a user doing something in their browser but I want to run it from command line.
I'm able to run an arbitrary class with gradlew;
./gradlew -PmainClass=kcentral.backingservices.URLMetaExtractor execute
However when run this way none of the "autowiring" works. What is a better way to execute an arbitrary class (that has a main method) such that it also works with any Autowiring?
EDIT:
I got some advice to use a CommandLineRunner and some args, which work to execute the command via:
./gradlew bootRun -Pargs=--reloadTestData
However, the Autowiring of my Repo is failing. What I have is:
#EnableAutoConfiguration
#EnableMongoAuditing
#EnableMongoRepositories(basePackageClasses=KCItemRepo.class)
#ComponentScan(basePackages = {"kcentral"})
public class ReloadTestData implements CommandLineRunner {
#Autowired
AddItemService addItemService;
#Autowired
KCItemRepo itemRepo;
#Autowired
KCItemRatingRepo itemRatingRepo;
private static final Logger log = LoggerFactory.getLogger(ReloadTestData.class);
public void reloadData(){
log.info("reloadData and called");
if (itemRepo == null){
log.error("Repo not found");
return;
}
long c = itemRepo.count();
log.warn("REMOVING ALL items "+c);
itemRepo.deleteAll();
log.warn("REMOVING ALL ratings");
itemRatingRepo.deleteAll();
}
itemRepo is always null even though I wire the same way in my 'regular' spring boot app without an issue. What do I need to do to have it wire properly?
The fact that you say you want to run a "job" suggests that you might want to use a scheduled task within your application, rather than trying to run it through the command line. e.g. Scheduling tasks in Spring
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
If you want to make a command line application work with Autowiring, you can make a command line application by making your Application class implement the CommandLineRunner interface, e.g. Spring Boot Console App
#SpringBootApplication
public class SpringBootConsoleApplication
implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Override
public void run(String... args) {
}
}
And add spring.main.web-application-type=NONE to the properties file.
If you want to stop the application after running you can use SpringApplication.exit(ctx). Don't know about your auto-wiring problem though, maybe try printing out the list of available beans which might give some insight. Example:
#Component
public class DoThenQuit implements CommandLineRunner {
#Autowired
private ApplicationContext ctx;
#Override
public void run(String[] args) {
// do some other stuff before quitting
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);
// then quit the application
SpringApplication.exit(ctx);
}
}

springboottest how to prevent running application

I have the standard Application class which runs a Spring batch ETL:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
with my Junit test doing something like:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MyServiceTest {
#Autowired
MyService myService;
#Test
public void testInsertions() {
//do stuff with assertions
}
}
My problem is that when I execute the junit test, the application also kicks off the ETL then executes the test. How to prevent the application from running?
I think there are a lot of alternatives and it really depends on what you are trying to achieve.
One of the options would be to run your tests with a specific profile, like testing, and configure your ETLs (I'm assuming they are just jobs) to be configured based on some property or specific profile.
For example:
Testing class
#ActiveProfiles("testing")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class MyServiceTest {
...
}
Job/ETL classes
#Component
#Profile("!testing")
public class JobEtlService {
}
Hope it helps.

Resources