'Annotations data is not present for @Service marked class

I'm trying to implement resolver pattern ( strategy pattern ).

So I have one custom annotation @PaymentTypeValue

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface PaymentTypeValue {
    PaymentType[] value();
}

and PaymentType as enum class

public enum PaymentType{
CASH, CARD, NET_BANKING, UPI, WALLET;
}

now I have interface PaymentService

public interface PaymentService {
   void process();
}

and have its 5 implementations, one is like

@Service
@PaymentTypeValue({PaymentTypeValue.CASH})
public class CashPaymentService implements PaymentService {
  @Override
  void process(){
    //some logic
  }
}

And I have resolver which is like this

@Component
public class PaymentServiceResolver {

    private static final Logger log = getLogger(PaymentServiceResolver.class);

    @Autowired
    private List<PaymentService> paymentServices;

    private final Map<PaymentType, PaymentService> paymentServiceMap = new EnumMap<>(PaymentType.class);

    @PostConstruct
    public void init() {
        paymentServices.forEach(paymentService -> {
                    PaymentTypeValue annotation = paymentService.getClass().getAnnotation(PaymentTypeValue.class);
                    if (annotation != null && annotation.value() != null) {
                        PaymentType[] paymentTypes = annotation.value();
                        Arrays.stream(paymentTypes).forEach(paymentType -> {
                            paymentServiceMap.put(paymentType, paymentService);
                        });
                    } else {
                        log.error("paymentService {} is missing a necessary annotation",
                                paymentService.getClass().getName());
                        log.error("This paymentService cannot be initialized");
//                        throw new IllegalStateException("paymentService is missing a necessary annotation for " + paymentService.getClass().getName());
                    }
                }
        );
    }

    public PaymentService resolvePaymentService(PaymentType paymentType) {
        PaymentService paymentService = paymentServiceMap.get(paymentType);
        if (paymentService != null) {
            return paymentService;
        } else {
            String message = "Could not find paymentService for " + paymentType;
            log.error(message);
            throw new RuntimeException(message);
        }
    }
}

Now the problem is in init() method, all the service implementations of PaymentService are present in list but when i'm trying to find annotations its giving null. I checked the value of paymentService.getClass() method and in that annotationData had 0 annotations. If I autowire any specific implementation of PaymentService then I'm able to get the annotation value. Please help me understand what I'm missing here?



Solution 1:[1]

The annotation approach makes things complex, I would try to avoid that. A simpler solution without annotations, assuming that there is a 1:1 mapping between PaymentType and service implementation:

  1. Add a function boolean canHandle(PaymentType) to the service interface.
  2. A resolver uses that information to locate a matching service.

Interface:

public interface PaymentService {

    boolean canHandle(PaymentType paymentType);

    void process();
}

Service implementation:

@Service
public class CashPaymentService implements PaymentService {

    @Override
    public boolean canHandle(PaymentType paymentType) {
        return PaymentType.CASH.equals(paymentType);
    }

    @Override
    public void process() {
        // processing...
    }
}

Service locator:

@Component
public class PaymentServiceResolver {

    private final List<PaymentService> paymentServices;
    private final Map<PaymentType,PaymentService> serviceMapping = new ConcurrentHashMap<>(5);

    @Autowired
    public PaymentServiceResolver(List<PaymentService> paymentServices) {
        this.paymentServices = paymentServices;
    }

    public PaymentService resolve(PaymentType paymentType) {
        return serviceMapping.computeIfAbsent(paymentType, resolveInternal());
    }

    private Function<PaymentType, PaymentService> resolveInternal() {
        return paymentType ->
                paymentServices.stream()
                        .filter(service -> service.canHandle(paymentType))
                        .findFirst()
                        .orElseThrow(() -> new IllegalStateException("Unsupported payment type: " + paymentType));
    }
}

You may add more validation logic to your resolver to check during the Spring's initialization phase whether an implementation is available for each PaymentType.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 seb.wired