0

In a Java 11/Spring REST API project I have an interface with multiple implementations. I want to choose the implementation in the configuration (in this case, application.yml file):


import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
@Target(TYPE)
public @interface PickableImplementation {
       // This id will match an entry in the config file
       public String id() default "";
}

So I have the two possible "pickable" implementations:

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.mycompany.api.util.PickableImplementation;

@Component
@Service
@PickableImplementation(id = "s3")
public class BatchProcessServiceS3 implements BatchProcessService {
        // Some implementation biz logic
}

// In another file, with the same imports:

@Component
@Service
@PickableImplementation(id = "azure")
public class BatchProcessServiceAzure implements BatchProcessService {
        // Another implementation biz logic
}

In the consumer class which in this case is a Controller, I try to pick the desired implementation:

import com.mycompany.api.util.PickableImplementation;
import java.util.List;

@RestController
@RequestMapping("/batch")
public class BatchController {

    @Autowired private Environment env;
    private BatchProcessService batchProcessService;
    private final List<BatchProcessService> batchProcessImplementations;

    public BatchController(List<BatchProcessService> batchProcessImplementations,
                            Environment environment){
        this.env = environment;
        var activeSvcImplementationId = env.getRequiredProperty("buckets-config.active");
        this.batchProcessImplementations = batchProcessImplementations;
        
        for (var batchProcessService : this.batchProcessImplementations) {
                if(batchProcessService.getClass().isAnnotationPresent(PickableImplementation.class)) {
                        // Verify the id, compare with the one from config file, etc.
                }
        }
    }
}

Expected behavior: inside the loop, I expected to get the annotations of each implementation, traverse the list, verify if it matches with the one with the application.yml and if it does, pick it to populate the service layer (private BatchProcessService batchProcessService).

Actual behavior: not only the isAnnotationPresent() method returns false, but also if I try getAnnotations() I get an empty array, like there are no annotations in the class. And besides my custom one, there are at least two additional annotations (Component, Service and others related to logging and the like). As another puzzling detail, if I run getAnnotations() on the qualified name of the class in the middle of a debugging session, the annotations are present. But in that very moment, running that method on the elements on the list return 0 annotations.

I've run out of ideas, has anyone tried this same combination of autowiring several implementations and at the same time, relying in custom annotations?

Additional references:

  • Use the `AopUtils` to get the actual class. You are probably getting a proxy instead of your class and the proxy doesn't have the annotations. Also why `@Component` *and* `@Service` one of those is enough. – M. Deinum Sep 14 '21 at 18:05
  • @M.Deinum but the classes (or proxies, like you said) are injected through Sprint autowiring, are you suggesting to replace one DI engine with the other? (maybe I'm getting your comment wrong). Duly noted the annotation redundance – Manuel Díaz Sep 14 '21 at 18:25
  • No I'm not stating you should replace the DI, you need the actual class not the proxy class. If you have things like `@Transactional`, `@Async` etc. spring will create a proxy to apply those things using AOP. Those proxies don't have the annotations from the class as it is just generated at runtime. I suspect that when you print the name of the class it looks something like `BatcProcessService$EnhancedBySpring$1` or `Proxy$1`. So what I say is you should use the `AopUtils.getUltimateTargetClass` to get the actual class of the proxy (so the underlying class). – M. Deinum Sep 15 '21 at 06:35
  • @M.Deinum your suggestion served perfectly. Now I get the actual class and can inspect the annotations. And as `org.springframework.aop.support.AopUtils` is part of Spring, the library footprint remains unchanged, so win-win. – Manuel Díaz Sep 15 '21 at 14:51

0 Answers0