I am using the annotation #PostContruct to initialize some data in #Component using spring.
The problem is the property is initialized only one time. The code inside #Component is something like this.
private int x;
#Inject Repository myRepo;
#PostConstruct
private void init () {
this.x = myRepo.findAll().size();
}
Variable "x" will be initilized on build, and if my data change in my DB, "x will not be updated. Is there a way that I could inject service in a class that do not belong to spring? Without #Component, for example.
MyClass newclass = new MyClass();
So, findAll() will always be called when I initialize the class.
If you do
#Component
#Scope('prototype') // thats the trick here
public class MyClass(){
#Autowired Repository myRepo;
#PostConstruct
private void init () {
this.x = myRepo.findAll().size();
}
}
Instances of bean scoped as prototype are created everytime they are requested by eithed CDI context or when directly requested from factory.
Alternatively you can do
#Component()
public class MyClass(){
public MyClass(#Autowired Repository myRepo){
this.x = myRepo.findAll().size();
}
}
In both cases you will have to use Spring's CDI to get new instance of MyClass.
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);
}
}
I am new to spring framework. I have to use spring boot and have a rest controller as below :-
#RestController
public class StatisticsController {
private TransactionCache transactionCache;
public StatisticsController(TransactionCache transactionCache) {
this.transactionCache = transactionCache;
}
#PostMapping("/tick")
public ResponseEntity<Object> addInstrumentTransaction(#Valid #RequestBody InstrumentTransaction instrumentTransaction) {
transactionCache.addTransaction(instrumentTransaction);
return new ResponseEntity<>(HttpStatus.CREATED);
}
and I have a class which needs to be singleton :-
#Component
public class TransactionStatisticsCacheImpl implements TransactionCache {
private static TransactionStatisticsCacheImpl instance;
public static TransactionStatisticsCacheImpl getInstance(){
if(Objects.isNull(instance)){
synchronized (TransactionStatisticsCacheImpl.class) {
if(Objects.isNull(instance)){
instance = new TransactionStatisticsCacheImpl();
}
}
}
return instance;
}
private TransactionStatisticsCacheImpl() {}
I want to know the correct way to call this singleton class in my rest controller. I know that by default the scope of a bean in spring is singleton. Is this the correct way to call the singleton class in rest controller?
#RestController
public class StatisticsController {
private TransactionCache transactionCache;
public StatisticsController(TransactionCache transactionCache) {
this.transactionCache = transactionCache;
}
#PostMapping("/tick")
public ResponseEntity<Object> addInstrumentTransaction(#Valid #RequestBody InstrumentTransaction instrumentTransaction) {
transactionCache.addTransaction(instrumentTransaction);
return new ResponseEntity<>(HttpStatus.CREATED);
}
or
We need to call it using the getInstance() method? Also do we need to explicitly have the getInstance method in the TransactionStatisticsCacheImpl class?
One of the major advantages of container injection is that you can get the benefits of singleton semantics without all the serious problems of "hard" singletons (such as difficulty testing). Get rid of the getInstance manual business and let Spring take care of ensuring that a single instance is created and used for the context.
Just for clarification: By default, the spring IOC container will create only one instance per bean definition, unless if you specified otherwise using the #Scope stereotype. But if you create an instance using getInstance() the bean pre-processors and post-processors will not work correctly on that bean definition. And also you can use the #Autowired stereotype to inject a bean definition as needed and if you have different implementations for the same definition you can use the #Qualifier stereotype to specify the implementation that you need to inject, alternatively, you can use the constructor injection to inject your bean definition as needed without auto wiring as mentioned here Spring #Autowire on Properties vs Constructor
I would stick to the answers above. However, if you want to preserve further instantiation of the class in your code (or you want to keep your specific implementation of singleton), you can do it with getInstance().
Firstly, get rid of #Component annotation in your class:
// #Component
public class TransactionStatisticsCacheImpl implements TransactionCache {
private static TransactionStatisticsCacheImpl instance;
public static TransactionStatisticsCacheImpl getInstance(){
if(Objects.isNull(instance)){
synchronized (TransactionStatisticsCacheImpl.class) {
if(Objects.isNull(instance)){
instance = new TransactionStatisticsCacheImpl();
}
}
}
return instance;
}
private TransactionStatisticsCacheImpl() {}
}
Then, you may instantiate your singleton #Bean by defining #Configuration class - this way your bean would get managed by spring container.
#Configuration
public class SingletonConfiguration {
#Bean
public TransactionCache transactionCache() {
return TransactionCacheImpl.getInstance();
}
}
Eventually, you can have your singleton injected in your RestController using #Autowired.
#RestController
public class StatisticsController {
private TransactionCache transactionCache;
#Autowired
public StatisticsController(TransactionCache transactionCache) {
this.transactionCache = transactionCache;
}
#PostMapping("/tick")
public ResponseEntity<Object> addInstrumentTransaction(#Valid #RequestBody InstrumentTransaction instrumentTransaction) {
transactionCache.addTransaction(instrumentTransaction);
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
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
I have an old class with configured constructor
public class Outer
{
...
public Outer(OldService oldService) { this.oldService = oldService;}
}
I need to add new field with a new service, but can't change constructor (too many legacy code depends on it). So, I want to get something like
public class Outer
{
private NewService newService; // Need injection here
public Outer(OldService oldService) { this.oldService = oldService;}
}
#Component
public class NewService
{
public NewService(Dependency dependency){this.dependency = dependency;}
}
I've tried to apply #Autowired and #Inject attributes for Outer.newService, but it doesn't help. I can create Outer.Initiate(NewService newService) method, but that's will add some s**t to the project already smelled.
So, can I inject field in Spring?
Upd1 Outer constructor is executed manually now (like var outer = new outer(service);).
As Outer.class is not instantiated by Spring container but by new, it cannot be known by Spring container. This is why, Spring cannot perform dependency injection for NewService.
Now, if you give instantiation of Outer to Spring, in addition to autowiring NewService, you are required to Autowire or use any spring wiring to wire OldService into Outer.
You can use field level injection:
public class Outer{
#Autowired
private NewService newService;
public Outer(OldService oldService) { this.oldService = oldService;}
}
I'm not a fan of this method, because it makes testing much more cumbersome and hides dependencies.
Instead of this, just use a setter and annotate it with Autowired, which does the same thing:
public class Outer{
private NewService newService;
public Outer(OldService oldService) { this.oldService = oldService;}
#Autowired
public setNewService(NewService newService){
this.newService = newService;
}
}
I've a Component as follows:
#Component
class A(){
private s;
public A(){}
public A(String s){this.s=s;}
}
Here is the other class Where I'm auto wiring the above class:
#Component
class B(){
#Autowire
private A a;
}
In the above autowiring, I need to use the parameterized constructor. How can I pass the constructor args?
You can't, at least not via #Autowired in B but there are other ways to do it:
Wire the parameter into A's constructor:
One constructor is annotated with #Autowired because:
As of Spring Framework 4.3, the #Autowired constructor is no longer
necessary if the target bean only defines one constructor. If several
constructors are available, at least one must be annotated to teach
the container which one it has to use.
#Component
class A(){
private s;
public A(){}
#Autowired
public A(#Value("${myval}") String s){this.s=s;}
}
Expose A as a #Bean
Straight from the docs:
#Configuration
public class AppConfig {
#Bean
public A a(#Value("${myval}") String s) {
return new A(s);
}
}
Construct A in B using an initialization callback
Docs
#Component
class B(){
private A a;
#Value("${myval}")
private String myval;
#PostConstruct
private void init()
{
a = new A(myval);
}
}
There is a concept of prototype bean which I think you require in your case. #Component will create a singleton bean and changing it in one place will change in all parent classes where this was injected.
You need to understand how to inject a prototype bean in singleton bean.
Follow this example
https://stackoverflow.com/a/25165971/949912
Just use setters instead of constructor.
If you want to create object by yourself with new keyword then this object will not be managed by container.