1

I want to provide a base configuration class that will handle creating beans with a generic type that gets defined when you extend the class as seen below. But it never calls the @Bean methods.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Constructor;

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = { TestGenericBean.MyClientCreator.class } )
public class TestGenericBean
{
    /* This throws an error saying no bean provided */
    @Autowired
    private TestClient client;

    public static class ClientConfig<T>
    {
        private Class<T> classCreator;

        public ClientConfig(Class<T> classCreator)
        {
            this.classCreator = classCreator;
        }

        /* This is never called */
        @Bean
        public T createClient(RestTemplate restTemplate) throws Exception
        {
            Constructor<T> constructor = classCreator.getConstructor(
                RestTemplate.class
            );

            return constructor.newInstance( restTemplate );
        }

        /* This is never called */
        @Bean
        public RestTemplate restTemplate()
        {
            return new RestTemplate();
        }
    }

    @Configuration
    public static class MyClientCreator extends ClientConfig<TestClient>
    {
        public MyClientCreator()
        {
            super( TestClient.class );
        }
    }

    public static class TestClient
    {
        public RestTemplate restTemplate;

        public TestClient(RestTemplate restTemplate)
        {
            this.restTemplate = restTemplate;
        }
    }

    @Test
    public void testBean()
    {
        System.out.print( client.restTemplate );
    }
}
Don Rhummy
  • 24,730
  • 42
  • 175
  • 330

2 Answers2

1

Not sure why, but it requires a bean of type MappingJackson2HttpMessageConverter. With that, it works.

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = { TestGenericBean.MyClientCreator.class } )
public class TestGenericBean
{
    @Autowired
    private TestClient client;

    public static class ClientConfig<T>
    {
        private Class<T> classCreator;

        public ClientConfig(Class<T> classCreator)
        {
            this.classCreator = classCreator;
        }

        /**** THIS IS REQUIRED - WHY? ****/
        @Bean
        public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter()
        {
            return new MappingJackson2HttpMessageConverter();
        }

        @Bean
        public T createClient(AsyncRestTemplate asyncRestTemplate) throws Exception
        {
            Constructor<T> constructor = classCreator.getConstructor(
                AsyncRestTemplate.class
            );

            return constructor.newInstance( asyncRestTemplate );
        }

        @Bean
        public AsyncRestTemplate asyncRestTemplate()
        {
            return new AsyncRestTemplate();
        }
    }

    @Configuration
    public static class MyClientCreator extends ClientConfig<TestClient>
    {
        public MyClientCreator()
        {
            super( TestClient.class );
        }
    }

    public static class TestClient
    {
        public AsyncRestTemplate asyncRestTemplate;

        public TestClient(AsyncRestTemplate asyncRestTemplate)
        {
            this.asyncRestTemplate = asyncRestTemplate;
        }
    }

    @Test
    public void testBean()
    {
        System.out.print( client.asyncRestTemplate );
    }
}
Don Rhummy
  • 24,730
  • 42
  • 175
  • 330
0

I haven't used Spring / Spring boot a lot lately but i have a lot of experience with annotation preprocessors. The way spring boot is using them cannot allow any generic method to be used as a bean provider for @autowire. This is because it cannot safely resolve the java class < T > and therefore map it with the field. I would suggest you try using a wrapper class such as Getter< T > or List< T > but can't guarantee that any generic implementation will work.