Reputation: 273
My problem is that I need to get an authentication token before I make a call to an API. The api must be called on start up of application. However I am having trouble in that both calls are made at the same time thus creating an error of not having the authentication token before making the api call.
I basically need the tokenUtilityClass to create the token before instantiating the Paypal class. I have tried the @Preconstruct and the @Lazy annotation but neither are working for me.
I have a boolean value of validToken which returns true once the authentication token has been created.
This is what my Springboot configuration file looks like
@Autowired
private TokenUtilityClass tokenUtilityClass;
@Bean ResourceConfig resourceConfig() {
return new ResourceConfig().registerClasses(Version1Api.class); }
@PostConstruct
public void postConstruct() {
tokenUtilityClass.tokenTimer();
}
@DependsOn("TokenUtilityClass")
@ConditionalOnProperty(name ="tokenUtilityClass.validToken", havingValue ="true")
@Lazy
public Paypal eventPublisherBean() {
return new Paypal();
}
Would anyone have any ideas about initializing the Paypal class only after the authentication token has been generated.
All help would be appreciated
Upvotes: 4
Views: 15318
Reputation: 131346
What you declared cannot work :
@DependsOn("TokenUtilityClass")
@ConditionalOnProperty(name ="tokenUtilityClass.validToken", havingValue ="true")
@Lazy
because tokenUtilityClass.validToken
is not a property but a bean method while
ConditionalOnProperty
expects to a property.
Would anyone have any ideas about initializing the Paypal class only after the authentication token has been generated.
Concretely, you cannot achieve it straightly with Spring because beans are instantiated as soon as these are required by other beans in their dependencies.
But declaring just that :
@Bean
@Lazy
public Paypal eventPublisherBean() {
return new Paypal();
}
could work if that bean is never used as a eager loaded dependency of other beans but rather required at the good time for you : after the token was retrieved.
To achieve that you have two ways :
1) don't use dependency injection for PayPal
instance but use exclusively bean factory.
Not tested but it sounds conform to the @Lazy
javadoc :
If this annotation is not present on a @Component or @Bean definition, eager initialization will occur. If present and set to true, the @Bean or @Component will not be initialized until referenced by another bean or explicitly retrieved from the enclosing BeanFactory.
So inject BeanFactory
in the bean responsible to get the token and use that factoryto initialize the bean after the token was retrieved.
Something like that :
@Service
public class TokenUtility {
private BeanFactory factory;
public TokenUtility(BeanFactory factory){
this.factory = factory;
}
public Token retrieveToken(){
// call service to get the token
//...
// init the bean now -(it will also invoke its @PostConstruct method)
PayPal payPal = beanFactory.getBean(PayPal.class);
}
}
2) A straighter alternative to the BeanFactory is declaring the bean as a lazy dependency :
@Service
public class TokenUtility {
@Lazy
@Autowired
Paypal paypal;
public Token retrieveToken(){
// call service to get the token
//...
// init the bean by invoking any method on that
paypal.init();
}
}
I created just now a very basic project (Java 11) to test that : https://github.com/ebundy/lazy-init-spring-example/.
When you execute spring-boot:run
you should get :
2020-02-26 09:38:05.883 INFO 7002 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 682 ms TokenUtility init with status code=200 PayPal init
Upvotes: 5
Reputation: 2571
You can just have token refreshing functionality and make sure token exists upon creating another bean.
@Component
class TokenUtilityClass {
private String freshToken;
private Instant freshUntil;
public String getToken() {
if (freshUntil != null && Instant.now().isBefore(freshUntil)) {
refreshToken();
}
return freshToken;
}
public void refreshToken() {
// do something
}
}
@Configuration
class ConfigurationClass {
@Bean public Paypal eventPublisherBean(TokenUtilityClass tokenUtilityClass) {
String token = tokenUtilityClass.getToken();
// do something with token and return your bean
return new Paypal();
}
}
Upvotes: 0
Reputation: 2571
You are looking for @Order
annotation.
@Component
@Order(1)
public class First {
@PostConstruct
public void init() {
System.out.println("first");
}
}
@Component
@Order(2)
public class Second {
@PostConstruct
public void init() {
System.out.println("second");
}
}
@Order
works with both types of declarations: @Component
/@Service
/@Repository
/@Controller
and with @Bean
However, if you have a clear dependency of one bean on the other bean, then use @DependsOn
annotation.
@Component("first")
public class First {
public First() { // inject dependencies here if any
System.out.println("The very first thing")
}
@PostConstruct
public void init() {
System.out.println("first");
}
}
@Component
@DependsOn({"first"})
public class Second {
@PostConstruct
public void init() {
System.out.println("second");
}
}
You can find more info here
Upvotes: 1
Reputation: 2255
It looks like you are sharing the code inside class with @Configuration
annotation. It seems you mark TokenUtilityClass
with @Component
(or similar) annotation.
The issue with that is that @PostConstruct
is connected to your Configuration class, not to TokenUtilityClass
. I suggest moving @PostConstruct
method to TokenUtilityClass
.
Upvotes: 0