22

I have a bunch of repository beans that implement type Repository<T ? extends Node>. Now I can get a list of random nodes from the user and I want to get the appropriate repository for each node. Since Spring 4.0RC1 we can autowire repositories like this:

@Autowired Repository<SomeNode> someNodeRepository;

As documented here.

This works fine, but my question is how I can do this dynamically based on the generic type.

What I want to do is something like:

public <T extends Node> T saveNode(T node) {
    Repository<T> repository = ctx.getBean(Repository.class, node.getClass());
    return repository.save(node);
}

Where the second parameter is the generic type. This of course does not work, although it compiles.

I can't find any/the documentation on this.

Lodewijk Bogaards
  • 19,777
  • 3
  • 28
  • 52

4 Answers4

34

You can do something like this:

String[] beanNamesForType = ctx.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, node.getClass()));

// If you expect several beans of the same generic type then extract them as you wish. Otherwise, just take the first
Repository<T> repository = (Repository<T>) ctx.getBean(beanNamesForType[0]);
jhas
  • 587
  • 6
  • 8
  • I am doubting whether your code would compile. The argument type of `getBeanNamesForType` does not line with the return type of `forClassWithGenerics` as far as I can see. – Lodewijk Bogaards Jul 25 '17 at 20:47
  • 3
    I never paste code that I haven't checked. It compiles and works...Note that `getBeanNamesForType` has 3 versions, one of which accepts `ResolvableType` – jhas Jul 26 '17 at 12:42
  • You are right, I was looking at an old version of Spring. This seems like a legitimate answer! Have you tested this? – Lodewijk Bogaards Jul 26 '17 at 16:19
  • 1
    Yes I have, it works great. I also searched how to do this and ended up with this solution – jhas Jul 27 '17 at 06:37
  • 1
    Would be nice if the `getBean()` and `getBeansOfType()` methods also had overloads that accept `ResolvableType`. – jaco0646 Jan 02 '21 at 17:52
  • Your answer is greatly appreciated. Java's implementation of generic continues to astound and frustrate me. https://en.wikipedia.org/wiki/Criticism_of_Java#Generics – granadaCoder Apr 28 '21 at 09:52
3

Since Spring 5.1 you can get a bean of type Repository<T> like that:

public static <T> Repository<T> getRepositoryFor(Class<T> clazz)
{
    ResolvableType type = ResolvableType.forClassWithGenerics(Repository.class, clazz);
    return (Repository<T>) context.getBeanProvider(type).getObject();
}
0

If you could be sure that for every concrete subclass of Node (say SomeNode), every object of type SomeNode will be an actual SomeNode and not a subclass or a proxy, it would be easy. Just use a convention for the repository name (say SomeNodeRepository) and it would be trivial :

Repository<T> repository = ctx.getBean(node.getClass().getSimpleName()
        + "Repository", Repository.class);

But you know that there's a high risk of getting a subclass or proxy ...

So you can try to have each Node subclass to implement a nameForRepo method :

class Node {
    ...
    abstract String getNameForRepo();
}

and then in the subclasses

class SomeNode {
    static private final nameForRepo = "SomeNode";
    ...
    String getNameForRepo() {
        return nameForRepo;
    }
}

That way, even if you get a proxy or subclass, you will be able to do :

public <T extends Node> T saveNode(T node) {
    Repository<T> repository = ctx.getBean(node.getNameForRepository()
            + "Repository", Repository.class);
    return repository.save(node);
}

Alternatively, the method could directly return the repository name.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Thanks, not a bad work around really, but since @Autowire does the job at runtime, what I want is actually possible, so it is unsatisfactory. In the mean time I have my own work around in place, which is just a simple `Map, Class>>`. – Lodewijk Bogaards May 21 '15 at 20:49
0

If I understand well, you want to get an instance of a bean with a Repository class and a different generic type?

I'm afraid you don't have the dynamic way with spring, but I have a workaround solution:

  1. Your generic type should be a field in your class, you must have a constructor in your Repository class for setting your generic type, your Repository class should be like this:

    public class Repository<T>{
        Class<T> nodeClass;
        public Repository(Class<?> clazz){
            this.nodeClass = clazz;
        }
        // your codes...
    }
    
  2. declare a Repository bean for each Node, let's say you have Repository and Repository, if you are using xml configuration, you need to add:

    <bean id="someNodeRep" class="your.package.Repository">
        <constructor-arg>
            <value>your.package.SomeNode</value>
        </constructor-arg>
    </bean>
    <bean id="otherNodeRep" class="your.package.Repository">
        <constructor-arg>
            <value>your.package.OtherNode</value>
        </constructor-arg>
    </bean>
    
  3. 'autowire' your Repository in this way:

    @Autowired
    @Qualifier("someNodeRep")
    Repository<SomeNode> someNodeRepository;
    
    @Autowired
    @Qualifier("otherNodeRep")
    Repository<OtherNode> otherNodeRepository;
    
Saikat
  • 14,222
  • 20
  • 104
  • 125
Qianlong
  • 269
  • 2
  • 12