Question

I've read a lot about getting generic type at runtime and I've understood that to prevent full type erasure and get generic type without giving it to constructor I can use an anonymous class plus an utility method, i.e.

interface Generic<T> {
    public Class<T> getGenericType();
}

@Component
class GenericImpl<T> extends AbstractGenericImpl<T> {

}

abstract class AbstractGenericImpl<T> implements Generic<T> {

    protected Class<T> klass;

    @SuppressWarnings("unchecked")
    public Class<T> getGenericType() {
        if (klass == null) {
            // this is a spring utility method
            klass = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractGenericImpl.class);
        }
        return klass;
    }
}

Now using the previous class hierarchy I can have a working getGenericType method if and only if I instantiate a Generic<Anything> using an anonymous class. In fact in this test only the first two assertions are working:

@Test
public void testGeneric() throws Exception {
    Generic<String> anonymous = new AbstractGenericImpl<String>() {};
    Generic<String> anonymous2 = new GenericImpl<String>() {};
    Generic<String> concrete = new GenericImpl<String>();
    // assertion
    assertThat("Anonymous of abstract class", anonymous.getGenericType(), equalTo(String.class));
    assertThat("Anonymous of concrete subclass", anonymous2.getGenericType(), equalTo(String.class));
    assertThat("With non anonymous class it fails", concrete.getGenericType(), equalTo(String.class));
}

The third one is failing with Expected: <class java.lang.String> but: was <class java.lang.Object>

Now I'd like to use the Generic class with spring @Autowired annotation i.e.

@Autowired Generic<String> auto;

@Test
public void testAutowiring() {
    assertThat(auto, instanceOf(Generic.class));
    assertThat(auto.getGenericType(), equalTo(String.class));
}

but the second assertion fails with the same error as above (Object instead of String), because spring container internally instantiate it with new GenericImpl<String>()

I've already tried to make constructor of GenericImpl<T> protected and also to declare GenericImpl<String> itself abstract but in both cases spring fail with a Cannot instantiate bean exception.

Is there any simple way to tell spring to instantiate classes using anonymous classes?

Additional details

The final class will convert a json stream into a POJO with Jackson and the Jackson library needs the Class<T> field to unmarshal objects.

// here I convert json stream to a POJO and I need the generic type
mapper.readValue(hit.source(), getGenericType());

Since I have multiple POJO classes to convert from to JSON I've implemented all the logic in a common class with generics called Retriever. At the end I'll have one Retriever for each POJO and often those retrievers are autowired in other classes.

@Autowired Retriever<Artifact> retriever;

Currently I've a constructor in Retriever which takes a Class<T> parameter and use it later to perform conversion. In the spring context I've this for autowiring

<!-- Since retriever has a Class<T> constructor this is the only way I found to resolve its dependency -->
<bean id="artifactRetriever" class="a.b.c.RetrieverImpl">
    <constructor-arg value="a.b.c.Artifact"/>
</bean>

and I need one of this for each POJO for which I need conversion. This approach works but it's a little verbose and it clutters the application context with useless lines. So I was looking for a way to get rid of all this noise in application context.

Was it helpful?

Solution 2

Ok, final solution for my use case will use the approach described in this answer. It would be better because it will be possible to track usages and I'll get rid of every problem I'm having with the current approach.

In that way I can do the following

@Component
public class ArtifactImpl extends AbstractGenericImpl<Artifact> {
}

@Component
public class MaterialImpl extends AbstractGenericImpl<Material> {
}

@Component
class Usage {
  @Autowired ArtifactImpl foo;
  @Autowired MaterialImpl bar;
}

In this way everything is checked at compile time and I got rid of Class<T> constructor in fact I have autowiring in place (without @Qualifier) and the following test is working:

@RunWith(SpringJUnit4ClassRunner.class)
public class AutowiringTest {
  @Autowired Usage test;

  public void testAutowiring() {
    assertThat(test.foo.getGenericType(), equalTo(Artifact.class));
    assertThat(test.bar.getGenericType(), equalTo(Material.class));
  }
}

Original answer

Ok, I've found out that what I'm asking will be useless because autowiring happens at runtime and so having two autowired object with different objects will lead to spring errors, i.e. this won't work:

@Configuration
class RetrieverProvider {
    @Bean 
    Retriever<Artifact> getArtifact() {
        return new RetrieverImpl<Artifact>() {};
    }

    @Bean 
    Retriever<Material> getMaterial() {
        return new RetrieverImpl<Material>() {};
    }
}

class InjectedAttempt {
    // at injection time, i.e. runtime, type erasure prevent spring to distinguish
    @Autowired Retriever<Artifact> foo; // this type
    @Autowired Retriever<Material> bar; // from this type
    // so it cannot perform injection by type
}

The only way to get that working is to use qualifiers in this way, but I don't like this approach, so I'll remain with xml configuration and constructor arguments.

@Configuration
class RetrieverProvider {

    @Bean @Qualifier("artifact") Retriever<Artifact> getArtifact() {
        return new RetrieverImpl<Artifact>() {};
    }

    @Bean @Qualifier("material")
    Retriever<Material> getMaterial() {
        return new RetrieverImpl<Material>() {};
    }


}

class Injected {

    @Autowired @Qualifier("artifact") Retriever<Artifact> foo;

    @Autowired @Qualifier("material") Retriever<Material> bar;
}

As a side note guice has support for generic injections, maybe spring has something similar.

OTHER TIPS

It's not possible to create and instantiate anonymous classes in-place with Spring, not with XML configuration (since it needs class name, and you don't have one).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top