简体   繁体   中英

Spring Inject Generic Type

Say we have an interface like this one:

public interface Validator<T> {
  boolean isValid(T data);
}

And this is part of a core module. Multiple apps can use that same core module, with a different value for the generic T. An example implementation is (from a specific module of the application):

@Component
public class AppValidator implements Validator<String> {
  @Override
  public boolean isValid(String data) {
    return false;
  }
}

And then in the controller (which is part of the core module):

@RestController
public class ValidateController {
  @Autowired
  private Validator validator;

  @RequestMapping("/")
  public void index() {
    validator.validate("");
  }
}

IntelliJ is complaining that I'm using raw types; as you can see, I'm actually doing that in the controller.

My question: is there a way to inject the dependency in a bounded way (instead of injecting Validator , injecting Validator<String> )? But of course, the bound type could change depending on the application using the core module?

If not possible (probably due to type erasure), what's the best practice for this? Is it just to use Object ? Is there no nicer alternative that still provides type-safety?

I've seen somewhere people saying it's possible to do some magic at compile-time to change the types, but I'm not sure how, or even if I read it correctly?

I am using Spring, so I'm hoping Spring can provide something to help me here! Some magic is welcome!

The answer is simple: you don't need any magic, it just works in Spring. You have your AppValidator and then you just do (it's injected by looking at generic type):

@Autowired 
private Validator<String> appValidator;

Well it's all fine, now imagine you have two Validator<String> , what then? required a single bean but found two exception - that's what. That's why it's a horrible practice, don't ever ever do that.

At my work one person created generic interface with 3 generic types and then based injection on those generic types, people still hate him. It looked like this, and yes, it works, as long as you don't have the exact same 3 generic types in the exact same order in multiple implementations:

@Autowired
private Invoker<String, Integer, Person> personInvoker;

@Autowired
private Invoker<Integer, String, Animal> animalInvoker;

Even if you don't have multiple Validator<String> in your code, and you don't plan on having more - someone else may come in and add them, or many other scenarios.

Here the relationships between your modules (applications and core) :

Application 1       Application 2      Application 3
     |                   |                   |
Validator<Foo>     Validator<Bar>     Validator<FooBar>
     |                   |                   |  
     |                   |                   |  
     |__ __ __ __ __ __ _| __ __ __ __ __ __ | 
                         |
                         | <<uses>>
                         |
                        \ /
                     Core Module    
                         |
                 ValidateController  (not generic rest controller)                   

Something is wrong here since you want that a shared component ValidateController relies on a specific application generic Validator class but ValidateController is not a generic class, so you could only stick with Object as generic type where you will use the Validator field.
To make things consistent, you should create this missing link. In fact you need distinct subclasses of controller because each controller needs to use a specific instance of validator.
You could for example define an abstract class/interface ValidateController in the shared/code module and leave each subclass extends it and defines itself the generic Validator class to use.

Here the target relationships between your modules :

Application 1        Application 2        Application 3
     |                   |                      |
Validator<Foo>       Validator<Bar>       Validator<FooBar>
FooController(bean)  BarController(bean)  FooBarController(bean)
     |                   |                      |  
     |                   |                      |  
     |__ __ __ __ __ ___ | __ ___ __ __ __ __ __| 
                         |
                         | <<uses>>
                         |
                        \ /
                     Core Module    
                         |
                 ValidateController<T>  (abstract class and not a bean)                   

For example in the core/shared module :

public abstract class ValidateController<T> {

  private Validator<T> validator;

  ValidateController(Validator<T> validator){
     this.validator = validator;
  }

  @RequestMapping("/")
  public void index(T t) {
    boolean isValid = validator.validate(t);
  }

}

In the application, define your validator implementation :

@Component
public class AppValidator implements Validator<String> {
  @Override
  public boolean validate(String data) {
    return ...;
  }
}

And define also the StringController subclass (or @Bean as alternative) to set the right Validator :

@RestController
public class StringController extends ValidateController<String>{

   public ValidateControllerApp(Validator<String> validator){
       this.validator = validator;
   }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM