diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index 1220efd351..64a5d7083a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -102,9 +102,9 @@ class CglibAopProxy implements AopProxy, Serializable { /** The configuration used to configure this proxy */ protected final AdvisedSupport advised; - private Object[] constructorArgs; + protected Object[] constructorArgs; - private Class[] constructorArgTypes; + protected Class[] constructorArgTypes; /** Dispatcher used for methods on Advised */ private final transient AdvisedDispatcher advisedDispatcher; diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java index 95e59b34bb..24c8906550 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java @@ -22,9 +22,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; -import org.springframework.objenesis.Objenesis; import org.springframework.objenesis.ObjenesisException; -import org.springframework.objenesis.ObjenesisStd; import org.springframework.objenesis.SpringObjenesis; /** @@ -40,9 +38,7 @@ class ObjenesisCglibAopProxy extends CglibAopProxy { private static final Log logger = LogFactory.getLog(ObjenesisCglibAopProxy.class); - private static final Objenesis cachedObjenesis = new SpringObjenesis(); - - private static final Objenesis nonCachedObjenesis = new ObjenesisStd(false); + private static final SpringObjenesis objenesis = new SpringObjenesis(); /** @@ -57,17 +53,34 @@ class ObjenesisCglibAopProxy extends CglibAopProxy { @Override @SuppressWarnings("unchecked") protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { - try { - Objenesis objenesis = (enhancer.getUseCache() ? cachedObjenesis : nonCachedObjenesis); - Factory factory = (Factory) objenesis.newInstance(enhancer.createClass()); - factory.setCallbacks(callbacks); - return factory; + Class proxyClass = enhancer.createClass(); + Object proxyInstance = null; + + if (objenesis.isWorthTrying()) { + try { + proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache()); + } + catch (ObjenesisException ex) { + logger.debug("Unable to instantiate proxy using Objenesis, " + + "falling back to regular proxy construction", ex); + } } - catch (ObjenesisException ex) { - // Fallback to regular proxy construction on unsupported JVMs - logger.debug("Unable to instantiate proxy using Objenesis, falling back to regular proxy construction", ex); - return super.createProxyClassAndInstance(enhancer, callbacks); + + if (proxyInstance == null) { + // Regular instantiation via default constructor... + try { + proxyInstance = (this.constructorArgs != null ? + proxyClass.getConstructor(this.constructorArgTypes).newInstance(this.constructorArgs) : + proxyClass.newInstance()); + } + catch (Exception ex) { + throw new AopConfigException("Unable to instantiate proxy using Objenesis, " + + "and regular proxy instantiation via default constructor fails as well", ex); + } } + + ((Factory) proxyInstance).setCallbacks(callbacks); + return proxyInstance; } } 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 b0b16bb8f5..bcc90294c2 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 @@ -46,9 +46,7 @@ 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; @@ -85,11 +83,10 @@ class ConfigurationClassEnhancer { private static final String BEAN_FACTORY_FIELD = "$$beanFactory"; + private static final Log logger = LogFactory.getLog(ConfigurationClassEnhancer.class); - private static final Objenesis cachedObjenesis = new SpringObjenesis(); - - private static final Objenesis nonCachedObjenesis = new ObjenesisStd(false); + private static final SpringObjenesis objenesis = new SpringObjenesis(); /** @@ -393,7 +390,7 @@ class ConfigurationClassEnhancer { * it will not be proxied. This too is aligned with the way XML configuration works. */ private Object enhanceFactoryBean(final Object factoryBean, final ConfigurableBeanFactory beanFactory, - final String beanName) throws InstantiationException, IllegalAccessException { + final String beanName) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(factoryBean.getClass()); @@ -403,19 +400,29 @@ class ConfigurationClassEnhancer { // 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(); + Object fbProxy = null; + + if (objenesis.isWorthTrying()) { + try { + fbProxy = objenesis.newInstance(fbClass, enhancer.getUseCache()); + } + catch (ObjenesisException ex) { + logger.debug("Unable to instantiate enhanced FactoryBean using Objenesis, " + + "falling back to regular construction", ex); + } } - factory.setCallback(0, new MethodInterceptor() { + if (fbProxy == null) { + try { + fbProxy = fbClass.newInstance(); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to instantiate enhanced FactoryBean using Objenesis, " + + "and regular FactoryBean instantiation via default constructor fails as well", ex); + } + } + + ((Factory) fbProxy).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) { @@ -425,7 +432,7 @@ class ConfigurationClassEnhancer { } }); - return factory; + return fbProxy; } private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) { diff --git a/spring-core/src/main/java/org/springframework/objenesis/SpringObjenesis.java b/spring-core/src/main/java/org/springframework/objenesis/SpringObjenesis.java index 8a3893d98a..f52ddf4155 100644 --- a/spring-core/src/main/java/org/springframework/objenesis/SpringObjenesis.java +++ b/spring-core/src/main/java/org/springframework/objenesis/SpringObjenesis.java @@ -16,6 +16,7 @@ package org.springframework.objenesis; +import org.springframework.core.SpringProperties; import org.springframework.objenesis.instantiator.ObjectInstantiator; import org.springframework.objenesis.strategy.InstantiatorStrategy; import org.springframework.objenesis.strategy.StdInstantiatorStrategy; @@ -23,18 +24,83 @@ import org.springframework.util.ConcurrentReferenceHashMap; /** * Spring-specific variant of {@link ObjenesisStd} / {@link ObjenesisBase}, - * providing a cache based on {@code Class} keys instead of class names. + * providing a cache based on {@code Class} keys instead of class names, + * and allowing for selective use of the cache. * * @author Juergen Hoeller * @since 4.2 + * @see #isWorthTrying() + * @see #newInstance(Class, boolean) */ public class SpringObjenesis implements Objenesis { - private final InstantiatorStrategy strategy = new StdInstantiatorStrategy(); + /** + * System property that instructs Spring to ignore Objenesis, not even attempting + * to use it. Setting this flag to "true" is equivalent to letting Spring find + * out that Objenesis isn't working at runtime, triggering the fallback code path + * immediately: Most importantly, this means that all CGLIB AOP proxies will be + * created through regular instantiation via a default constructor. + */ + public static final String IGNORE_OBJENESIS_PROPERTY_NAME = "spring.objenesis.ignore"; + + + private final InstantiatorStrategy strategy; private final ConcurrentReferenceHashMap, ObjectInstantiator> cache = new ConcurrentReferenceHashMap, ObjectInstantiator>(); + private volatile Boolean worthTrying; + + + /** + * Create a new {@code SpringObjenesis} instance with the + * standard instantiator strategy. + */ + public SpringObjenesis() { + this(null); + } + + /** + * Create a new {@code SpringObjenesis} instance with the + * given standard instantiator strategy. + * @param strategy the instantiator strategy to use + */ + public SpringObjenesis(InstantiatorStrategy strategy) { + this.strategy = (strategy != null ? strategy : new StdInstantiatorStrategy()); + + // Evaluate the "spring.objenesis.ignore" property upfront... + if (SpringProperties.getFlag(SpringObjenesis.IGNORE_OBJENESIS_PROPERTY_NAME)) { + this.worthTrying = Boolean.FALSE; + } + } + + + /** + * Return whether this Objenesis instance is worth trying for instance creation, + * i.e. whether it hasn't been used yet or is known to work. + *

If the configured Objenesis instantiator strategy has been identified to not + * work on the current JVM at all or if the "spring.objenesis.ignore" property has + * been set to "true", this method returns {@code false}. + */ + public boolean isWorthTrying() { + return (this.worthTrying != Boolean.FALSE); + } + + /** + * Create a new instance of the given class via Objenesis. + * @param clazz the class to create an instance of + * @param useCache whether to use the instantiator cache + * (typically {@code true} but can be set to {@code false} + * e.g. for reloadable classes) + * @return the new instance (never {@code null}) + * @throws ObjenesisException if instance creation failed + */ + public T newInstance(Class clazz, boolean useCache) { + if (!useCache) { + return newInstantiatorOf(clazz).newInstance(); + } + return getInstantiatorOf(clazz).newInstance(); + } public T newInstance(Class clazz) { return getInstantiatorOf(clazz).newInstance(); @@ -44,7 +110,7 @@ public class SpringObjenesis implements Objenesis { public ObjectInstantiator getInstantiatorOf(Class clazz) { ObjectInstantiator instantiator = this.cache.get(clazz); if (instantiator == null) { - ObjectInstantiator newInstantiator = this.strategy.newInstantiatorOf(clazz); + ObjectInstantiator newInstantiator = newInstantiatorOf(clazz); instantiator = this.cache.putIfAbsent(clazz, newInstantiator); if (instantiator == null) { instantiator = newInstantiator; @@ -53,4 +119,35 @@ public class SpringObjenesis implements Objenesis { return (ObjectInstantiator) instantiator; } + protected ObjectInstantiator newInstantiatorOf(Class clazz) { + Boolean currentWorthTrying = this.worthTrying; + try { + ObjectInstantiator instantiator = this.strategy.newInstantiatorOf(clazz); + if (currentWorthTrying == null) { + this.worthTrying = Boolean.TRUE; + } + return instantiator; + } + catch (ObjenesisException ex) { + if (currentWorthTrying == null) { + Throwable cause = ex.getCause(); + if (cause instanceof ClassNotFoundException || cause instanceof IllegalAccessException) { + // Indicates that the chosen instantiation strategy does not work on the given JVM. + // Typically a failure to initialize the default SunReflectionFactoryInstantiator. + // Let's assume that any subsequent attempts to use Objenesis will fail as well... + this.worthTrying = Boolean.FALSE; + } + } + throw ex; + } + catch (NoClassDefFoundError err) { + // Happening on the production version of Google App Engine, coming out of the + // restricted "sun.reflect.ReflectionFactory" class... + if (currentWorthTrying == null) { + this.worthTrying = Boolean.FALSE; + } + throw new ObjenesisException(err); + } + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index 73b1abcbed..9d020e74dc 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -22,11 +22,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; - import javax.servlet.http.HttpServletRequest; import org.aopalliance.intercept.MethodInterceptor; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,7 +41,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.objenesis.Objenesis; +import org.springframework.objenesis.ObjenesisException; import org.springframework.objenesis.SpringObjenesis; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; @@ -97,7 +95,7 @@ public class MvcUriComponentsBuilder { private static final Log logger = LogFactory.getLog(MvcUriComponentsBuilder.class); - private static final Objenesis objenesis = new SpringObjenesis(); + private static final SpringObjenesis objenesis = new SpringObjenesis(); private static final PathMatcher pathMatcher = new AntPathMatcher(); @@ -605,15 +603,39 @@ public class MvcUriComponentsBuilder { factory.addAdvice(interceptor); return (T) factory.getProxy(); } + else { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(type); enhancer.setInterfaces(new Class[] {MethodInvocationInfo.class}); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); - Factory factory = (Factory) objenesis.newInstance(enhancer.createClass()); - factory.setCallbacks(new Callback[] {interceptor}); - return (T) factory; + + Class proxyClass = enhancer.createClass(); + Object proxy = null; + + if (objenesis.isWorthTrying()) { + try { + proxy = objenesis.newInstance(proxyClass, enhancer.getUseCache()); + } + catch (ObjenesisException ex) { + logger.debug("Unable to instantiate controller proxy using Objenesis, " + + "falling back to regular construction", ex); + } + } + + if (proxy == null) { + try { + proxy = proxyClass.newInstance(); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to instantiate controller proxy using Objenesis, " + + "and regular controller instantiation via default constructor fails as well", ex); + } + } + + ((Factory) proxy).setCallbacks(new Callback[] {interceptor}); + return (T) proxy; } }