Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring programmatic bean autowired as proxy instead of target

I created an annotation for creating ThreadPoolTaskExecutors populated with values from the environment. However, when I autowire the bean it gives me a proxy and calling the methods on the proxy gives the wrong values.

If I manually access the target class, then I get the correct values.

Executor exec = (Executor) ((Advised) executor).getTargetSource().getTarget();
ThreadPoolTaskExecutor taskExec = (ThreadPoolTaskExecutor) exec;

I have been scratching my head for a while now as to why I'm getting a proxy bean, but can't seem to figure it out.

I am using an annotation to import my registrar class that implements ImportBeanDefinitionRegistrar to register the bean. The registrar code is below:

public class ExecutorEnumerationRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    public static final String CORE_POOL_SIZE = "corePoolSize";
    public static final String MAX_POOL_SIZE = "maxPoolSize";
    public static final String QUEUE_CAPACITY = "queueCapacity";
    public static final String THREAD_NAME_PREFIX = "threadNamePrefix";
    private static final String REJECTED_EXECUTION_HANDLER = "rejectedExecutionHandler";
    private static final String NAMES = "names";
    private static final String REJECTED_HANDLER = "rejectedHandler";
    private Environment env;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = importingClassMetadata.getAnnotationAttributes(ThreadPoolTaskExecutorCreator.class.getName(), true);
        final String[] beanNames = (String[]) attrs.get(NAMES);
        final String[] policyClass = (String[]) attrs.get(REJECTED_HANDLER);
        for (int x = 0; x < beanNames.length; x++) {
            createAndRegisterBean(beanNames[x], policyClass[x], registry);
        }
    }

    private void createAndRegisterBean(String name, String policyClass, BeanDefinitionRegistry registry) {
        GenericBeanDefinition bd = new GenericBeanDefinition();
        bd.setBeanClass(ThreadPoolTaskExecutor.class);
        bd.setAutowireCandidate(true);
        bd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        MutablePropertyValues mpv = bd.getPropertyValues();
        populateProperties(mpv, name, policyClass);
        registry.registerBeanDefinition(name, bd);
    }

    private void populateProperties(MutablePropertyValues mpv, String name, String policyClass) {
        mpv.add(CORE_POOL_SIZE, Integer.valueOf(env.getProperty(name + "." + CORE_POOL_SIZE)));
        mpv.add(MAX_POOL_SIZE, Integer.valueOf(env.getProperty(name + "." + MAX_POOL_SIZE)));
        mpv.add(QUEUE_CAPACITY, Integer.valueOf(env.getProperty(name + "." + QUEUE_CAPACITY)));
        try {
            mpv.add(REJECTED_EXECUTION_HANDLER, Class.forName(policyClass).newInstance());
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        mpv.add(THREAD_NAME_PREFIX, name + "-");
    }

    @Override
    public void setEnvironment(Environment environment) {
        env = environment;
    }

}

Annotation to import the registrar:

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE) 
@Documented 
@Import(ExecutorEnumerationRegistrar.class) 
public @interface ThreadPoolTaskExecutorCreator{ 

    String[] names(); 

    String[] rejectedHandler() default ThreadPoolPolicyHandlers.CALLER_RUNS_POLICY; 

} 

I have tested with the following code: Spring Boot Class:

@EnableDiscoveryClient
@ComponentScan("my.test.classes")
@ThreadPoolTaskExecutorCreator(names = {"testExecutor"}, rejectedHandler = ThreadPoolPolicyHandlers.DISCARD_POLICY) 
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, 
    SessionAutoConfiguration.class,
    DataSourceTransactionManagerAutoConfiguration.class, 
    JpaRepositoriesAutoConfiguration.class,
    JndiDataSourceAutoConfiguration.class, 
    JndiConnectionFactoryAutoConfiguration.class,
    RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class})
public class TestBoot {

    public static void main(String[] args) {
        SpringApplication.run(TestBoot.class, args);
    }   
}

All versions from spring-boot-starter-parent 1.4.5.RELEASE

I wrote a JUnit test that checks the values and it passes. The only time it doesn't work is when I autowire it in a Spring Boot eureka application. Is there anything I can do so that it doesn't autowire a proxy bean? I have searched through the documentation and looked at all the related classes, but I don't see anything related to why it's a proxy. Also, why does it give incorrect values when accessed through the proxy?

like image 599
Jeremy Reed Avatar asked Nov 17 '17 22:11

Jeremy Reed


1 Answers

Seems you are missing the code for registering the instance of your ImportBeanDefinitionRegistrar ( in your example that is ExecutorEnumerationRegistrar ) So there are two ways to register the ImportBeanDefinitionRegistrar use the @Import annotation directly or implement the ImportSelector interface which can give you more generic configuration options.

For your case simply adding the @Import({ExecutorEnumerationRegistrar.class}) on the Configuration class will do the trick.

@EnableDiscoveryClient
@ComponentScan("my.test.classes")
@ThreadPoolTaskExecutorCreator(names = {"testExecutor"}, rejectedHandler = ThreadPoolPolicyHandlers.DISCARD_POLICY)
// THIS IS REQUIRED
@Import({ExecutorEnumerationRegistrar.class})
// See Above
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, 
SessionAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, 
JpaRepositoriesAutoConfiguration.class,
JndiDataSourceAutoConfiguration.class, 
JndiConnectionFactoryAutoConfiguration.class,
RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class})
public class TestBoot {

    public static void main(String[] args) {
        SpringApplication.run(TestBoot.class, args);
    }   
}

and just remember to use the @Qualifier when autowiring the instance of ThreadPoolTaskExecutor. See example component

@Component
public class Component {

    @Autowired
    @Qualifier("testExecutor")
    private ThreadPoolTaskExecutor exec;

    // you methods
}
like image 103
Babl Avatar answered Nov 15 '22 13:11

Babl