I am searching for a way to read and parse a lot of data when the spring boot app is starting and be able to use these data later in other classes.
I started with a class DataRepository.java and annotated it with #Service to be able to inject it later. I'm planning to read the data here and to inject it in any other class I need the data.
But how can I achieve to parse the data just once and at app startup? The spring boot app should only be reachable if the parsing is done.
Your approach with #Service is 100% appropriate.
By default all beans are singletons, so if you parse data on bean creation (in constructor) it will be parsed only once, and this info can be used in other beans by simple injection.
Please note that if during data parsing you have to use other beans, you should be confident that all beans are completely constructed. For that you should use approach proposed by #jreznot:
https://stackoverflow.com/a/51783858/5289288
You can use ContextStartedEvent and handle it:
#Component
public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> {
#Override
public void onApplicationEvent(ContextStartedEvent cse) {
System.out.println("Handling context start event. ");
}
}
See also: https://www.baeldung.com/spring-events
By default all beans in spring context are singletons. Spring guarantees that it will creates a bean just ones during context loading.
For example if you will have few contexts in your application it creates one instance for every context.
If you have just one context you can use these approaches:
initialize data in constructor. Data will initialized and ready to
use just after bean's instance creation.
#Component
public class DataRepository {
public DataRepository() {
... init data
}
}
use #Bean annotation withinit method. Allows you don't stick to Spring in
your data repository and initialize data after all beans were created.
public class DataRepository {
public void init() {
... init data
}
}
#Configuration
public class DataRepositoryConfiguration {
#Bean(initMethod = "init")
public DataRepository dataRepository() {
return new DataRepository();
}
use #Bean annotation and invoke init method. Allows you don't stick to
Spring in your data repository, but #Autowired field will uninitialized.
public class DataRepository {
public void init() {
... init data
}
}
#Configuration
public class DataRepositoryConfiguration {
#Bean
public DataRepository dataRepository() {
DataRepository dr = new new DataRepository();
dr.init();
return dr;
}
}
use #PostConstruct annotation. Initialize data after all beans was
created.
public class DataRepository {
#PostConstruct
public void init() {
... init data
}
}
Exception thrown during initializing will stop Spring's context initializing
You can use PostConstruct on any bean. For example
#Component
class DataLoad {
......
......
#PostConstruct
public void parseData() {
...... do your stuff here.......
}
}
With this the code inside parseData will be called only once. This is a very common way to do things in scenarios like when you want to load some configuration data from database at the start of the application and do it only once. In these cases you can #Autowired the repository class to the same class and use that in your #PostConstruct method and get data
Related
I have Service class and Repository interface (Spring Data). I have also one abstract class:
public abstract class TestingMethod {
public TestingMethod() {
timeSum = 0;
}
protected long timeSum;
}
And class that extends it:
#Component
public class LimitTestingMethod extends TestingMethod {
#Autowired
private GeoTestDataRepository geoTestDataRepository;
private final int limitSize;
public LimitTestingMethod(int limitSize) {
super();
this.limitSize = limitSize;
}
}
In my Service I want to create instance of LimitTestingMethod and set its argument limitSize.
Something like:
LimitTestingMethod ltm3 = new LimitTestingMethod(3);
LimitTestingMethod ltm10 = new LimitTestingMethod(10);
But I got error:
Description: Parameter 0 of constructor in
com.exence.postgiscma.testingMethod.LimitTestingMethod required a bean
of type 'int' that could not be found. Action: Consider defining a
bean of type 'int' in your configuration.
Is that possible to achieve something like I want?
All best!
//EDIT
As I can see in comments it's bad approach. So maybe someone will give me advise how to project this better?
Is this good solution to pass repo as argument in constructor (I guess that not, but I can't get the idea how to do this better)?
LimitTestingMethod ltm3 = new LimitTestingMethod(3, geoTestDataRepository);
Is there a good and elegant solution?
As you are creating instances outside the scope of Spring your current solution won't work. The error comes from the fact that you have annotated it with #Component, it will detect it at startup and tries to create a bean, and fails.
To solve this you can do 1 of 2 things.
Let Spring handle the creation of the beans by using the ApplicationContext as a factory, providing additional arguments and make the bean prototype scoped.
Let Spring handle the injection after you manually created the instance using the ApplicationContext.
Use ApplicationContext as a factory
First make your bean a prototype so that it will be constructed when needed.
#Component
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class LimitTestingMethod extends TestingMethod { ... }
Now an instance won't be created during startup. In your service inject the ApplicationContext and use the getBean method to get your desired instance.
public class Service {
#Autowired
private ApplicationContext ctx;
public void yourMethod() {
LimitTestingMethod ltm3 = ctx.getBean(LimitTestingMethod.class, 3);
LimitTestingMethod ltm10 = ctx.getBean(LimitTestingMethod.class, 10);
}
}
This will let Spring create the instance using the value passed in for the constructor and do the autowiring.
Injection after creation
Another solution is to manually create the instances and after that let Spring handle the auto wiring. You will lose the AOP abilities with this and will get only auto wiring.
First remove the #Component annotation from your LimitTestingMethod so it won't get detected during startup.
public class LimitTestingMethod extends TestingMethod { ... }
Now in your service autowire the ApplicationContext and after creating your bean use that to inject the dependencies.
public class Service {
#Autowired
private ApplicationContext ctx;
public void yourMethod() {
LimitTestingMethod ltm3 = new LimitTestingMethod(3);
LimitTestingMethod ltm10 = new LimitTestingMethod(10);
ctx.getAutowireCapableBeanFactory().autowireBean(lmt3);
ctx.getAutowireCapableBeanFactory().autowireBean(lmt10);
}
}
Both will achieve what you want, however, now your code directly depends on the Spring API. So instead of doing this, you are probably better of with another option and that is to inject everything for the LimitTestingMethod through the constructor and pass the repository yourself.
Use constructor to create an instance
public class LimitTestingMethod extends TestingMethod {
private final GeoTestDataRepository geoTestDataRepository;
private final int limitSize;
public LimitTestingMethod(int limitSize, GeoTestDataRepository geoTestDataRepository) {
this.limitSize=limitSize;
this.geoTestDataRepository=geoTestDataRepository;
}
}
Then you can simply autowire the repository in your service class and create the instances as needed (or create a factory which contains the complexity of creating this object).
public class Service {
#Autowired
private GeoTestDataRepository repo;
public void yourMethod() {
LimitTestingMethod ltm3 = new LimitTestingMethod(3, repo);
LimitTestingMethod ltm10 = new LimitTestingMethod(10, repo);
}
}
It is a class which instance is connected to the external service and it is listening constantly of it.
#Component
public class Service extends PollingBot {
#Value("${token}")
private String token;
#Override
public void onUpdateReceived(Update update) {
if (update.hasMessage()) {
}
}
public void sendMessageToUser(String message) {
try {
execute(sendMessage);
} catch (ApiException e) {
}
}
}
You could see that there is a method called sendMessageToUser which send message. It could not be static because execute method not allow static context. This method could not be separeted to other class. /
So, I have to call this method from other class. However I don't want to create additional instance of Service class otherwise I have two instances which are listen for updates, but I want it is sole class instance doing so.
I have tried to run a Application Context and run method from it, but it was not worked.
So, my question is very simple. How could I run this class non-static(!) method from other class?
By default all spring managed beans are singleton. You need to use #Autowired to inject the bean into other and then you can call the methods of that bean.
#Autowired
private Service service;
public void sendMessage(String message){
service.sendMessageToUser(message);
}
You can use #Autowired annotation to call a method of a bean class(component) in Spring. Also, as mentioned by default beans are singleton in spring so you don't need to worry about creating a single instance explicitly every time.
Try to use the below code in the calling class:
#Autowired
private Service service;
public void sendText() {
service.sendMessage(message);
}
I was trying to update the table row data from outside the controller (Inside some threads) and getting 'NullPointerException' always.
Thread code:
public class S3Thread implements Runnable {
#Autowired
private IAutomationService automationService;
#Override
public void run() {
Automation config = new Automation("user1","success");
automationService.updateAutomation(config);
}
}
NullPointer exception thrown on below line:
automationService.updateAutomation(config);
Note: I was able to create/update from the controller class.Only in Thread.
Well, this is the classical Why is my Spring #Autowired field null case. You create the S3Thread instance by yourself, and thus, no beans are injected into it.
Considering you're trying to just do something in a separate thread, you can consider using #Async:
#Async
public void updateAutomationConfiguration() {
Automation config = new Automation("user1", "success");
automationService.updateAutomation(config);
}
Notes:
You have to add the #EnableAsync annotation to any configuration class (eg. your main class) to make this work.
Spring uses proxying by default, which means that you can't add this updateAutomationConfiguration() class to your controller itself. Direct calls to methods within the same bean bypass the proxied logic. The solution is to put this method in a separate bean which can be autowired and invoked from within the controller. I've provided more detailed answers about alternative solutions in this answer.
Spring also has a getting started guide for creating asynchronous methods.
Alternatively, there are also some ways to execute asynchronous calls within controllers, for example by using CompletableFuture within a controller:
#PutMapping("/automation/configuration")
public CompletableFuture<String> updateAutomationConfiguration() {
return CompletableFuture.supplyAsync(() -> {
Automation config = new Automation("user1", "success");
return automationService.updateAutomation(config);
});
}
Related: How to create a non-blocking #RestController webservice in Spring?
Spring does not scan your runnable as it is not annotated with #Component.Try annotating it with #Component/#Service.
Don't forget to set scope required scope though!
There are 2 potential solutions to your problem:
Either you need to make S3Thread class a service by annotating it with #Service or #Component and autowiring it on the calling class, or you can alternatively use the constructor for initializing your automationService, e.g. private IAutomationService automationService = new AutomationService();
Since your thread class is not managed by spring you will not be able to inject the spring managed beans in the S3Thread class.
In order to do that you need to create a class or factory which should be hooked into the spring life cycle.
Once you have the hold of that class you can get the appropriate bean and pass the reference onto/or used in the S3Thread class directly. Something like this
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
{
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
public class S3Thread implements Runnable {
#Override
public void run() {
Automation config = new Automation("user1","success");
IAutomationService automationService=
ApplicationContextUtils.getApplicationContext().getBean(IAutomationService .class);
automationService.updateAutomation(config);
}
}
I have a component Login that depends on ValidatorService. ValidatorService is being injected/autowired in the Login constructor. ValidationServiceImpl is provided by an external API, so I can't just annotate it as #Service.
#Component
class Login {
#Autowire
public Login (ValidatorService validator) {
}
}
#SpringBootApplication
public class Starter {
public static void main(String[] args)
{
SpringApplication.run(Starter.class, args);
}
}
I'm looking for a way to register ValidatorService as a bean before #Components get scanned. Is there a way to get ApplicationContext instance before starting the application?
SpringBoot 2.0.4.RELEASE
UPDATE
I need to pass a validationId that I'll get from main(args) to this external API.
public static void main(String[] args) {
String validationId = args[0];
ValidatorService service = ExternalValidationAPI.getValidationServiceImp(validationId);
}
You should be able to declare it as a bean as such in one of your configuration classes:
#Bean
public ValidatorService validatorService(){
return new ValidatorServiceImpl();
}
This will then autowire in the ValidatorService implementation class at the point it is needed. This method needs to go in an #Configuration class (your Starter class is one).
There's a good example of how to do this here.
I believe you can solve your problem with the help of the #Configurable annotation.
Annotate your Login class with #Configurable instead of #Componenet, and when the ValidatorService object becomes available, you can initiate the Login object with it.
You need to define a ValidationService bean :
#Configuration
public class ValidationServiceConfig {
#Bean
public ValidationService validationService(#Value("${validationId}") String validationId) {
return new ValidationServiceImpl(validationId);
}
}
and run the program this way : java -jar program.jar --validationId=xxx
I solved my problem creating a Configuration class and declaring a Bean to handle the instantiation of the external service that will be injected later (as some people have suggested). In order to retrieve the program arguments I autowired DefaultApplicationArguments to retrieve program arguments with getSourceArgs():
#Configuration
public class ValidatorConfig {
#Autowired
DefaultApplicationArguments applicationArguments;
#Bean
public ValidatorService validatorService()
{
String validationId = applicationArguments.getSourceArgs()[0];
return ExternalValidationAPI.getValidationServiceImp(validationId);
}
I have a Java Project with Spring MVC.
I need to start TimerTasks already after my application is initialized, so I implemented the WebApplicationInitializer Interface and I call it SystemInitializer. Inside that class I have a #Autowired property, that #Autowired property is a DAO class.
I need it cause I want to execute some tasks based in recordings from my data base. But that Autowired property is ever null.
public class SystemInitializer implements WebApplicationInitializer {
#Autowired
private DomainResearchDao domainResearchDao;
#Override
public void run() {
if (this.domainResearchDao != null) {
System.out.println("OK");
}
// always here
else{
System.out.println("NO OK");
}
}
You can not use #Autowired inside of WebApplicationInitializer.
Your Beans are not ready (not scanned yet) to be injected. Your Application has no idea what DomainResearchDao is at that moment.
Spring can autowire beans only after your application is initialized and all (singletone) instances (#Component, #Service etc.) are created.
If you want to do some job after your application is started, use Spring Event for doing this:
#Component
public class DoOnStart{
#Autowired
private IYourService service;
#EventListener
public void handleContextRefresh(ContextRefreshedEvent e) {
// your CODE
}
}
Just implement this class, no need to autowire it.