vendredi 27 janvier 2023

Spring inject component into non-spring managed interface/abstract class and its subclasses

TLDR: I need an interface/abstract class and all classes implementing it to have access to a Spring managed bean. Can Spring inject a bean into an interface/abstract-class and its subclasses simply via @Autowired ?

I am working on an API built with Spring Webflux + Cloud Gateway that depending on the cookie JWT authorized party, identifies the User's policy group and assign an Attribute ENUM "InterfaceID" to the ServerWebExchange via exchange.getAttribute().put("InterfaceID",InterfaceID.A) after the JWT is validated, and currently uses "InterfaceID" to represent the different groups of users/different interface the user entered from.

JWTValidationFilter.java [Current]

switch(JWTValidator.validate(jwt).get("AZP")){
    //if user is from company A or its partners
    case "a":
    case "aa":
        exchange.getAttribute().put(InterfaceID.COMPANY_A_ACCESS);
        break;
    case "b":
        exchange.getAttribute().put(InterfaceID.NORMAL_ACCESS);
    ...
}

For certain API endpoints (say /api/getSessionDocument), different "InterfaceID" fetches data from different DB/apis, as well as have different permission checking on top of that.

RequestController.java [Current]

@Autowired
APICallerUtil apiCallerUtil;

switch(exchange.getAttribute.get(InterfaceID)){
    case "NORMAL_ACCESS":
        apiCallerUtil.getDataFromApiA();
        break;
    case "COMPANY_A_ACCESS":
        // call api B but check for permission from api D first
    ...
}

The endpoint's controller now has another switch statement, and to many code analyzers this have been a code smell. I have been trying to refactor this entire bit of code to use polymorphism to handle the different "getSessionDocument" flows, but i run into issues regarding the injection of util classes that calls specific APIs.

APICallerUtil.java class, exisiting class from the project, would prefer not to refactor this.

@Component
public class APICallerUtil{
    @Value("${some uri to some API}") //different by environment and therefore cant be static final
    private String uri1;

    @Value("${some auth to some API}") //confidential
    private String uri1AuthHeader;
    //...

    public JSONObject getDataFromApiA(String somekey){ //cant be static since uri1 is not static
        //Some code that uses uri1 and apache httpclient
        return data;
    }

    ...
}

IBaseAccess.java

interface IBaseAccess{
    default Mono<JSONObject> getSesssionDocument(ServerWebExchange e){return Mono.error("not implemented");}
}

RequestController.java [new]

@Autowired
APICallerUtil apiCallerUtil;

return exchange.getAttribute.get(InterfaceID).getSessionDocument(exchange);

NormalAccess.java

public class NormalAccess implements IBaseAccess{
    //can i autowire APICallerUtil here?
    //use constructor to pass the Util class reference here?

    Mono<JSONObject> getSesssionDocument(ServerWebExchange e){
        //need to call ApiA here
        //need to call ApiC here
    }
}

NormalAccess needs to call APICaller.getDataFromApiA(), but it needs a reference to the Spring managed instance of APICaller. What would be the "correct" way to pass the reference/autowire API caller into NormalAccess, or even better IBaseAccess (so that the implementing classes can use the Util bean)?

JWTValidationFilter.java [new]

switch(JWTValidator.validate(jwt).get("AZP")){
    //if user is from company A or its partners
    case "a":
    case "aa":
        exchange.getAttribute().put("InterfaceID",new CompanyAAccess(/*pass the util class here?*/));
        break;
    case "b":
        exchange.getAttribute().put("InterfaceID",new NormalAccess(/*pass the util class here?*/));
    ...
}

I have tried several methods, but either I lack the knowledge on the specific Spring feature, or that method is deeemed a bad design choice by some, including:

  1. Making the methods and fields in APICallerUtil static, via suggestions from Spring: How to inject a value to static field? and Assigning private static final field member using spring injection , then the Access classes can call the static methods.

  2. Creating a contructor for IBaseAccess that consumes the APICallerUtil reference and store it inside. The JWTfilter would hold an autowired APICallerUtil and pass it in when the attribute is assigned.

  3. Create a static class that provides the application context and Access classes use applicationContext.getBean("APICallerUtil"); to obtain the bean.

  4. Use the @Configurable annotation? I could not find much documentation on how this works for interfaces/abstract-class.

I understand that there might not exist an absolute answer for this question, but regardless I'd like suggestion/feedback on which of these approaches are viable/good. Especailly concerning whether the APIUtil class should be static or not.

Aucun commentaire:

Enregistrer un commentaire