Edward Muldrew
Edward Muldrew

Reputation: 273

How to delay Spring Bean Initialization based on variable

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

Answers (4)

davidxxx
davidxxx

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

Ilya Sereb
Ilya Sereb

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

Ilya Sereb
Ilya Sereb

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

Uladzislau Kaminski
Uladzislau Kaminski

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

Related Questions