3

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

Edward Muldrew
  • 253
  • 1
  • 6
  • 20
  • Show full relevant code, how the token is created? – Oleg Feb 25 '20 at 20:44
  • Make the token created by the utility class available as a bean and pass it to the publisher bean method. It's not clear what `tokenTimer() ` does, sadly. – daniu Feb 25 '20 at 21:19
  • you can implement `ApplicationRunner` and run `api call` before the application startup completes, for your case `The api must be called on start up of application`, in this case, it doesn’t matter which bean was created first - `bean that receives the token` or `paypal client`, in `ApplicationRunner` you can receive token first and then call paypal api immediately, the next steps - where to store the token, update or not update the token, how to add the token for each request, etc. ... depends on your requirements and implementation of `Paypal ` – tsarenkotxt Feb 26 '20 at 00:06

4 Answers4

5

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
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Great answer and knowledge of spring in general – Alan Sereb Feb 25 '20 at 21:48
  • 1
    @Alan Sereb Thanks for your good words. I found another way. And I added a concrete example on GitHub. – davidxxx Feb 26 '20 at 08:41
  • 1
    Yeah, that works too. But I have a feeling it becomes "spaghetti-like"... As long as it works :D – Alan Sereb Feb 26 '20 at 14:15
  • 1
    @Alan Sereb. A shared feeling. The wished requirement is different enough from the default Spring behavior, so we are constraint to find add some bypasses to achieve that. – davidxxx Feb 26 '20 at 16:51
1

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

Alan Sereb
  • 2,358
  • 2
  • 17
  • 31
  • I didn't downvote this, however spring will still instantiate the Paypal method faster than the response and setting of the authentication token – Edward Muldrew Feb 25 '20 at 21:01
  • then use `@DependsOn` https://www.baeldung.com/spring-depends-on – Alan Sereb Feb 25 '20 at 21:02
  • The issue with this is the TokenUtilityClass will be initialized before attempting an initialization of the PaypalClass. However it is the method within the TokenUtilityClass - the authentication call and response has to be complete before the initialization of the Paypal class – Edward Muldrew Feb 25 '20 at 21:06
  • Then just place token initialization inside of your constructor, so initialization is not complete before a token is not generated (not `@PostContruct`) – Alan Sereb Feb 25 '20 at 21:08
0

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.

Uladzislau Kaminski
  • 2,113
  • 2
  • 14
  • 33
  • I have moved this post construct method to the TokenUtilityClass however the Paypal class still gets instantiated quicker than the response of AuthenticationToken call – Edward Muldrew Feb 25 '20 at 21:02
  • 1
    Try to add `TokenUtilityClass tokenUtilityClass` as a argument to `Paypal eventPublisherBean()`. It will show that `Paypal` depends on `TokenUtilityClass` – Uladzislau Kaminski Feb 25 '20 at 21:21
0

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();
    }

}
Alan Sereb
  • 2,358
  • 2
  • 17
  • 31