diff --git a/build.gradle b/build.gradle index e333a05629..195fce663a 100644 --- a/build.gradle +++ b/build.gradle @@ -166,10 +166,12 @@ project("spring-core") { // further transformed by the JarJar task to depend on org.springframework.asm; this // avoids including two different copies of asm unnecessarily. def cglibVersion = "3.0" + def objenesisVersion = "2.0" configurations { jarjar cglib + objenesis } task cglibRepackJar(type: Jar) { repackJar -> @@ -195,8 +197,28 @@ project("spring-core") { } } + task objenesisRepackJar(type: Jar) { repackJar -> + repackJar.baseName = "spring-objenesis-repack" + repackJar.version = objenesisVersion + + doLast() { + project.ant { + taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask", + classpath: configurations.jarjar.asPath + jarjar(destfile: repackJar.archivePath) { + configurations.objenesis.each { originalJar -> + zipfileset(src: originalJar) + } + // repackage org.objenesis => org.springframework.objenesis + rule(pattern: "org.objenesis.**", result: "org.springframework.objenesis.@1") + } + } + } + } + dependencies { cglib("cglib:cglib:${cglibVersion}@jar") + objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") jarjar("com.googlecode.jarjar:jarjar:1.3") compile(files(cglibRepackJar)) @@ -216,6 +238,11 @@ project("spring-core") { from(zipTree(cglibRepackJar.archivePath)) { include "org/springframework/cglib/**" } + + dependsOn objenesisRepackJar + from(zipTree(objenesisRepackJar.archivePath)) { + include "org/springframework/objenesis/**" + } } } @@ -237,6 +264,7 @@ project("spring-aop") { dependencies { compile(project(":spring-core")) compile(files(project(":spring-core").cglibRepackJar)) + compile(files(project(":spring-core").objenesisRepackJar)) compile(project(":spring-beans")) compile("aopalliance:aopalliance:1.0") optional("com.jamonapi:jamon:2.4") 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 9d53f59e07..5b04ad9375 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 @@ -79,7 +79,7 @@ import org.springframework.util.ObjectUtils; * @see DefaultAopProxyFactory */ @SuppressWarnings("serial") -final class CglibAopProxy implements AopProxy, Serializable { +class CglibAopProxy implements AopProxy, Serializable { // Constants for CGLIB callback array indices private static final int AOP_PROXY = 0; @@ -185,29 +185,20 @@ final class CglibAopProxy implements AopProxy, Serializable { enhancer.setSuperclass(proxySuperClass); enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class)); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); - enhancer.setInterceptDuringConstruction(false); Callback[] callbacks = getCallbacks(rootClass); - enhancer.setCallbacks(callbacks); - enhancer.setCallbackFilter(new ProxyCallbackFilter( - this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); - Class[] types = new Class[callbacks.length]; + for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } + enhancer.setCallbackTypes(types); + enhancer.setCallbackFilter(new ProxyCallbackFilter( + this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); // Generate the proxy class and create a proxy instance. - Object proxy; - if (this.constructorArgs != null) { - proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs); - } - else { - proxy = enhancer.create(); - } - - return proxy; + return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException ex) { throw new AopConfigException("Could not generate CGLIB subclass of class [" + @@ -227,6 +218,15 @@ final class CglibAopProxy implements AopProxy, Serializable { } } + protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { + + enhancer.setInterceptDuringConstruction(false); + enhancer.setCallbacks(callbacks); + + return this.constructorArgs == null ? enhancer.create() : enhancer.create( + this.constructorArgTypes, this.constructorArgs); + } + /** * Creates the CGLIB {@link Enhancer}. Subclasses may wish to override this to return a custom * {@link Enhancer} implementation. diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index b00eafce08..65a4c2e971 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -59,7 +59,7 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } - return CglibProxyFactory.createCglibProxy(config); + return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); @@ -75,17 +75,4 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { Class[] interfaces = config.getProxiedInterfaces(); return (interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.equals(interfaces[0]))); } - - - /** - * Inner factory class used to just introduce a CGLIB dependency - * when actually creating a CGLIB proxy. - */ - private static class CglibProxyFactory { - - public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) { - return new CglibAopProxy(advisedSupport); - } - } - } 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 new file mode 100644 index 0000000000..bd82707401 --- /dev/null +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ObjenesisCglibAopProxy.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2013 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.aop.framework; + + +import org.apache.commons.logging.Log; +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.ObjenesisException; +import org.springframework.objenesis.ObjenesisStd; + +/** + * Objenesis based extension of {@link CglibAopProxy} to create proxy instances without + * invoking the constructor of the class. + * + * @author Oliver Gierke + * @since 4.0 + */ +class ObjenesisCglibAopProxy extends CglibAopProxy { + + private static final Log logger = LogFactory.getLog(ObjenesisCglibAopProxy.class); + + private final ObjenesisStd objenesis; + + + /** + * Creates a new {@link ObjenesisCglibAopProxy} using the given {@link AdvisedSupport}. + * @param config must not be {@literal null}. + */ + public ObjenesisCglibAopProxy(AdvisedSupport config) { + super(config); + this.objenesis = new ObjenesisStd(true); + } + + + @Override + @SuppressWarnings("unchecked") + protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { + try { + Factory factory = (Factory) objenesis.newInstance(enhancer.createClass()); + factory.setCallbacks(callbacks); + return factory; + } + catch (ObjenesisException ex) { + // Fallback to Cglib on unsupported JVMs + if (logger.isDebugEnabled()) { + logger.debug("Unable to instantiate proxy using Objenesis, falling back " + + "to regular proxy construction", ex); + } + return super.createProxyClassAndInstance(enhancer, callbacks); + } + } + +} diff --git a/spring-aop/src/test/java/org/springframework/aop/framework/ClassWithConstructor.java b/spring-aop/src/test/java/org/springframework/aop/framework/ClassWithConstructor.java new file mode 100644 index 0000000000..fd7ee4a4b7 --- /dev/null +++ b/spring-aop/src/test/java/org/springframework/aop/framework/ClassWithConstructor.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2013 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.aop.framework; + +public class ClassWithConstructor { + + public ClassWithConstructor(Object object) { + + } + + public void method() { + + } +} \ No newline at end of file diff --git a/spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java index 194b44472c..485d7db7b5 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java @@ -16,13 +16,6 @@ package org.springframework.aop.framework; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.Serializable; import org.aopalliance.intercept.MethodInterceptor; @@ -33,7 +26,6 @@ import org.springframework.aop.MethodMatcher; import org.springframework.aop.Pointcut; import org.springframework.aop.support.AopUtils; import org.springframework.aop.support.DefaultPointcutAdvisor; -import org.springframework.cglib.core.CodeGenerationException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -43,6 +35,8 @@ import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.TestBean; import test.mixin.LockMixinAdvisor; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; /** * Additional and overridden tests for the CGLIB proxy. @@ -135,31 +129,6 @@ public final class CglibProxyTests extends AbstractAopProxyTests implements Seri assertEquals(32, tb.getAge()); } - @Test - public void testCglibProxyingGivesMeaningfulExceptionIfAskedToProxyNonvisibleClass() { - - @SuppressWarnings("unused") - class YouCantSeeThis { - void hidden() { - } - } - - YouCantSeeThis mine = new YouCantSeeThis(); - try { - ProxyFactory pf = new ProxyFactory(mine); - pf.getProxy(); - fail("Shouldn't be able to proxy non-visible class with CGLIB"); - } - catch (AopConfigException ex) { - // Check that stack trace is preserved - assertTrue(ex.getCause() instanceof CodeGenerationException || - ex.getCause() instanceof IllegalArgumentException); - // Check that error message is helpful - assertTrue(ex.getMessage().indexOf("final") != -1); - assertTrue(ex.getMessage().indexOf("visible") != -1); - } - } - @Test public void testMethodInvocationDuringConstructor() { CglibTestBean bean = new CglibTestBean(); @@ -348,6 +317,7 @@ public final class CglibProxyTests extends AbstractAopProxyTests implements Seri } @Test + @SuppressWarnings("resource") public void testWithDependencyChecking() { ApplicationContext ctx = new ClassPathXmlApplicationContext(DEPENDENCY_CHECK_CONTEXT, getClass()); diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java b/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java new file mode 100644 index 0000000000..09ad6bd07b --- /dev/null +++ b/spring-context/src/test/java/org/springframework/aop/framework/ClassWithComplexConstructor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2013 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.aop.framework; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +/** + * @author Oliver Gierke + */ +@Component +public class ClassWithComplexConstructor { + + private final Dependency dependency; + + @Autowired + public ClassWithComplexConstructor(Dependency dependency) { + Assert.notNull(dependency); + this.dependency = dependency; + } + + public Dependency getDependency() { + return dependency; + } + + public void method() { + this.dependency.method(); + } +} diff --git a/spring-context/src/test/java/org/springframework/aop/framework/Dependency.java b/spring-context/src/test/java/org/springframework/aop/framework/Dependency.java new file mode 100644 index 0000000000..1346977245 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/aop/framework/Dependency.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2013 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.aop.framework; + +import org.springframework.stereotype.Component; + +@Component class Dependency { + + private int value = 0; + + public void method() { + value++; + } + + public int getValue() { + return value; + } +} diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ObjenesisProxyTests-context.xml b/spring-context/src/test/java/org/springframework/aop/framework/ObjenesisProxyTests-context.xml new file mode 100644 index 0000000000..c3ba5ccea7 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/aop/framework/ObjenesisProxyTests-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/spring-context/src/test/java/org/springframework/aop/framework/ObjenesisProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/ObjenesisProxyTests.java new file mode 100644 index 0000000000..68400dcbd0 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/aop/framework/ObjenesisProxyTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2013 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.aop.framework; + +import org.junit.Test; +import org.springframework.aop.interceptor.DebugInterceptor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Integration test for Objenesis proxy creation. + * + * @author Oliver Gierke + */ +public class ObjenesisProxyTests { + + @Test + public void appliesAspectToClassWithComplexConstructor() { + + @SuppressWarnings("resource") + ApplicationContext context = new ClassPathXmlApplicationContext("ObjenesisProxyTests-context.xml", getClass()); + + ClassWithComplexConstructor bean = context.getBean(ClassWithComplexConstructor.class); + bean.method(); + + DebugInterceptor interceptor = context.getBean(DebugInterceptor.class); + assertThat(interceptor.getCount(), is(1L)); + assertThat(bean.getDependency().getValue(), is(1)); + } +}