From 869b0bc7eed62463df87c3052f4fa84c28b56399 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 5 Jun 2015 00:09:42 +0200 Subject: [PATCH] Create enhanced FactoryBean instance before applying method interceptor (preferably via Objenesis) Also removes "throws Exception" declaration from ThreadPoolExecutorFactoryBean's getObject method. Issue: SPR-13095 --- .../ConfigurationClassEnhancer.java | 32 ++++++++- .../ThreadPoolExecutorFactoryBean.java | 4 +- .../ThreadPoolExecutorFactoryBeanTests.java | 67 +++++++++++++++++++ 3 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 7b1f6b2232a..b0b16bb8f54 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -39,12 +39,17 @@ import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.CallbackFilter; import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.cglib.proxy.NoOp; import org.springframework.cglib.transform.ClassEmitterTransformer; import org.springframework.cglib.transform.TransformingClassGenerator; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.objenesis.Objenesis; +import org.springframework.objenesis.ObjenesisException; +import org.springframework.objenesis.ObjenesisStd; +import org.springframework.objenesis.SpringObjenesis; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -82,6 +87,10 @@ class ConfigurationClassEnhancer { private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class); + private static final Objenesis cachedObjenesis = new SpringObjenesis(); + + private static final Objenesis nonCachedObjenesis = new ObjenesisStd(false); + /** * Loads the specified class and generates a CGLIB subclass of it equipped with @@ -388,9 +397,25 @@ class ConfigurationClassEnhancer { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(factoryBean.getClass()); - enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setCallback(new MethodInterceptor() { + enhancer.setCallbackType(MethodInterceptor.class); + + // Ideally create enhanced FactoryBean proxy without constructor side effects, + // analogous to AOP proxy creation in ObjenesisCglibAopProxy... + Class fbClass = enhancer.createClass(); + Objenesis objenesis = (enhancer.getUseCache() ? cachedObjenesis : nonCachedObjenesis); + Factory factory; + try { + factory = (Factory) objenesis.newInstance(fbClass); + } + catch (ObjenesisException ex) { + // Fallback to regular proxy construction on unsupported JVMs + logger.debug("Unable to instantiate enhanced FactoryBean using Objenesis, " + + "falling back to regular construction", ex); + factory = (Factory) fbClass.newInstance(); + } + + factory.setCallback(0, new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if (method.getName().equals("getObject") && args.length == 0) { @@ -399,7 +424,8 @@ class ConfigurationClassEnhancer { return proxy.invoke(factoryBean, args); } }); - return enhancer.create(); + + return factory; } private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java index 274a39510d7..b673d87fec3 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -187,7 +187,7 @@ public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport @Override - public ExecutorService getObject() throws Exception { + public ExecutorService getObject() { return this.exposedExecutor; } diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java new file mode 100644 index 00000000000..08239ddfb6e --- /dev/null +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBeanTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.scheduling.concurrent; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.FutureTask; + +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + */ +public class ThreadPoolExecutorFactoryBeanTests { + + @Test + public void defaultExecutor() throws Exception { + ApplicationContext context = new AnnotationConfigApplicationContext(ExecutorConfig.class); + ExecutorService executor = context.getBean("executor", ExecutorService.class); + + FutureTask task = new FutureTask(new Callable() { + @Override + public String call() throws Exception { + return "foo"; + } + }); + executor.execute(task); + assertEquals("foo", task.get()); + } + + + @Configuration + public static class ExecutorConfig { + + @Bean + public ThreadPoolExecutorFactoryBean executorFactory() { + return new ThreadPoolExecutorFactoryBean(); + } + + @Bean + public ExecutorService executor() { + return executorFactory().getObject(); + } + } + +}