Skip to content

Commit 1f9e8f6

Browse files
odrotbohmPhillip Webb
authored and
Phillip Webb
committed
Add support for Objenesis proxy creation.
Extended DefaultAopProxyFactory to create Objenesis based proxies if the library is on the classpath. This allows classes without a default constructor being CGLib proxied. We're now falling back to original CGLib based behavior in case the proxy creation using Objenesis fails. Objenesis 2.0 is now inlined into spring-core to avoid interfering with other Objenesis versions on the classpath. Issue: SPR-10594
1 parent 41fffdc commit 1f9e8f6

File tree

10 files changed

+289
-62
lines changed

10 files changed

+289
-62
lines changed

build.gradle

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,12 @@ project("spring-core") {
166166
// further transformed by the JarJar task to depend on org.springframework.asm; this
167167
// avoids including two different copies of asm unnecessarily.
168168
def cglibVersion = "3.0"
169+
def objenesisVersion = "2.0"
169170

170171
configurations {
171172
jarjar
172173
cglib
174+
objenesis
173175
}
174176

175177
task cglibRepackJar(type: Jar) { repackJar ->
@@ -195,8 +197,28 @@ project("spring-core") {
195197
}
196198
}
197199

200+
task objenesisRepackJar(type: Jar) { repackJar ->
201+
repackJar.baseName = "spring-objenesis-repack"
202+
repackJar.version = objenesisVersion
203+
204+
doLast() {
205+
project.ant {
206+
taskdef name: "jarjar", classname: "com.tonicsystems.jarjar.JarJarTask",
207+
classpath: configurations.jarjar.asPath
208+
jarjar(destfile: repackJar.archivePath) {
209+
configurations.objenesis.each { originalJar ->
210+
zipfileset(src: originalJar)
211+
}
212+
// repackage org.objenesis => org.springframework.objenesis
213+
rule(pattern: "org.objenesis.**", result: "org.springframework.objenesis.@1")
214+
}
215+
}
216+
}
217+
}
218+
198219
dependencies {
199220
cglib("cglib:cglib:${cglibVersion}@jar")
221+
objenesis("org.objenesis:objenesis:${objenesisVersion}@jar")
200222
jarjar("com.googlecode.jarjar:jarjar:1.3")
201223

202224
compile(files(cglibRepackJar))
@@ -216,6 +238,11 @@ project("spring-core") {
216238
from(zipTree(cglibRepackJar.archivePath)) {
217239
include "org/springframework/cglib/**"
218240
}
241+
242+
dependsOn objenesisRepackJar
243+
from(zipTree(objenesisRepackJar.archivePath)) {
244+
include "org/springframework/objenesis/**"
245+
}
219246
}
220247
}
221248

@@ -237,6 +264,7 @@ project("spring-aop") {
237264
dependencies {
238265
compile(project(":spring-core"))
239266
compile(files(project(":spring-core").cglibRepackJar))
267+
compile(files(project(":spring-core").objenesisRepackJar))
240268
compile(project(":spring-beans"))
241269
compile("aopalliance:aopalliance:1.0")
242270
optional("com.jamonapi:jamon:2.4")

spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
* @see DefaultAopProxyFactory
8080
*/
8181
@SuppressWarnings("serial")
82-
final class CglibAopProxy implements AopProxy, Serializable {
82+
class CglibAopProxy implements AopProxy, Serializable {
8383

8484
// Constants for CGLIB callback array indices
8585
private static final int AOP_PROXY = 0;
@@ -185,29 +185,20 @@ public Object getProxy(ClassLoader classLoader) {
185185
enhancer.setSuperclass(proxySuperClass);
186186
enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class));
187187
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
188-
enhancer.setInterceptDuringConstruction(false);
189188

190189
Callback[] callbacks = getCallbacks(rootClass);
191-
enhancer.setCallbacks(callbacks);
192-
enhancer.setCallbackFilter(new ProxyCallbackFilter(
193-
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
194-
195190
Class<?>[] types = new Class[callbacks.length];
191+
196192
for (int x = 0; x < types.length; x++) {
197193
types[x] = callbacks[x].getClass();
198194
}
195+
199196
enhancer.setCallbackTypes(types);
197+
enhancer.setCallbackFilter(new ProxyCallbackFilter(
198+
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
200199

201200
// Generate the proxy class and create a proxy instance.
202-
Object proxy;
203-
if (this.constructorArgs != null) {
204-
proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
205-
}
206-
else {
207-
proxy = enhancer.create();
208-
}
209-
210-
return proxy;
201+
return createProxyClassAndInstance(enhancer, callbacks);
211202
}
212203
catch (CodeGenerationException ex) {
213204
throw new AopConfigException("Could not generate CGLIB subclass of class [" +
@@ -227,6 +218,15 @@ public Object getProxy(ClassLoader classLoader) {
227218
}
228219
}
229220

221+
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
222+
223+
enhancer.setInterceptDuringConstruction(false);
224+
enhancer.setCallbacks(callbacks);
225+
226+
return this.constructorArgs == null ? enhancer.create() : enhancer.create(
227+
this.constructorArgTypes, this.constructorArgs);
228+
}
229+
230230
/**
231231
* Creates the CGLIB {@link Enhancer}. Subclasses may wish to override this to return a custom
232232
* {@link Enhancer} implementation.

spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException
5959
if (targetClass.isInterface()) {
6060
return new JdkDynamicAopProxy(config);
6161
}
62-
return CglibProxyFactory.createCglibProxy(config);
62+
return new ObjenesisCglibAopProxy(config);
6363
}
6464
else {
6565
return new JdkDynamicAopProxy(config);
@@ -75,17 +75,4 @@ private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
7575
Class[] interfaces = config.getProxiedInterfaces();
7676
return (interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.equals(interfaces[0])));
7777
}
78-
79-
80-
/**
81-
* Inner factory class used to just introduce a CGLIB dependency
82-
* when actually creating a CGLIB proxy.
83-
*/
84-
private static class CglibProxyFactory {
85-
86-
public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) {
87-
return new CglibAopProxy(advisedSupport);
88-
}
89-
}
90-
9178
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aop.framework;
18+
19+
20+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
22+
import org.springframework.cglib.proxy.Callback;
23+
import org.springframework.cglib.proxy.Enhancer;
24+
import org.springframework.cglib.proxy.Factory;
25+
import org.springframework.objenesis.ObjenesisException;
26+
import org.springframework.objenesis.ObjenesisStd;
27+
28+
/**
29+
* Objenesis based extension of {@link CglibAopProxy} to create proxy instances without
30+
* invoking the constructor of the class.
31+
*
32+
* @author Oliver Gierke
33+
* @since 4.0
34+
*/
35+
class ObjenesisCglibAopProxy extends CglibAopProxy {
36+
37+
private static final Log logger = LogFactory.getLog(ObjenesisCglibAopProxy.class);
38+
39+
private final ObjenesisStd objenesis;
40+
41+
42+
/**
43+
* Creates a new {@link ObjenesisCglibAopProxy} using the given {@link AdvisedSupport}.
44+
* @param config must not be {@literal null}.
45+
*/
46+
public ObjenesisCglibAopProxy(AdvisedSupport config) {
47+
super(config);
48+
this.objenesis = new ObjenesisStd(true);
49+
}
50+
51+
52+
@Override
53+
@SuppressWarnings("unchecked")
54+
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
55+
try {
56+
Factory factory = (Factory) objenesis.newInstance(enhancer.createClass());
57+
factory.setCallbacks(callbacks);
58+
return factory;
59+
}
60+
catch (ObjenesisException ex) {
61+
// Fallback to Cglib on unsupported JVMs
62+
if (logger.isDebugEnabled()) {
63+
logger.debug("Unable to instantiate proxy using Objenesis, falling back "
64+
+ "to regular proxy construction", ex);
65+
}
66+
return super.createProxyClassAndInstance(enhancer, callbacks);
67+
}
68+
}
69+
70+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aop.framework;
18+
19+
public class ClassWithConstructor {
20+
21+
public ClassWithConstructor(Object object) {
22+
23+
}
24+
25+
public void method() {
26+
27+
}
28+
}

spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@
1616

1717
package org.springframework.aop.framework;
1818

19-
import static org.hamcrest.CoreMatchers.instanceOf;
20-
import static org.junit.Assert.assertEquals;
21-
import static org.junit.Assert.assertNotNull;
22-
import static org.junit.Assert.assertThat;
23-
import static org.junit.Assert.assertTrue;
24-
import static org.junit.Assert.fail;
25-
2619
import java.io.Serializable;
2720

2821
import org.aopalliance.intercept.MethodInterceptor;
@@ -33,7 +26,6 @@
3326
import org.springframework.aop.Pointcut;
3427
import org.springframework.aop.support.AopUtils;
3528
import org.springframework.aop.support.DefaultPointcutAdvisor;
36-
import org.springframework.cglib.core.CodeGenerationException;
3729
import org.springframework.context.ApplicationContext;
3830
import org.springframework.context.ApplicationContextException;
3931
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -43,6 +35,8 @@
4335
import org.springframework.tests.sample.beans.TestBean;
4436

4537
import test.mixin.LockMixinAdvisor;
38+
import static org.hamcrest.CoreMatchers.*;
39+
import static org.junit.Assert.*;
4640

4741
/**
4842
* Additional and overridden tests for the CGLIB proxy.
@@ -135,31 +129,6 @@ public void testProxyCanBeClassNotInterface() throws Exception {
135129
assertEquals(32, tb.getAge());
136130
}
137131

138-
@Test
139-
public void testCglibProxyingGivesMeaningfulExceptionIfAskedToProxyNonvisibleClass() {
140-
141-
@SuppressWarnings("unused")
142-
class YouCantSeeThis {
143-
void hidden() {
144-
}
145-
}
146-
147-
YouCantSeeThis mine = new YouCantSeeThis();
148-
try {
149-
ProxyFactory pf = new ProxyFactory(mine);
150-
pf.getProxy();
151-
fail("Shouldn't be able to proxy non-visible class with CGLIB");
152-
}
153-
catch (AopConfigException ex) {
154-
// Check that stack trace is preserved
155-
assertTrue(ex.getCause() instanceof CodeGenerationException ||
156-
ex.getCause() instanceof IllegalArgumentException);
157-
// Check that error message is helpful
158-
assertTrue(ex.getMessage().indexOf("final") != -1);
159-
assertTrue(ex.getMessage().indexOf("visible") != -1);
160-
}
161-
}
162-
163132
@Test
164133
public void testMethodInvocationDuringConstructor() {
165134
CglibTestBean bean = new CglibTestBean();
@@ -348,6 +317,7 @@ public void testExceptionHandling() {
348317
}
349318

350319
@Test
320+
@SuppressWarnings("resource")
351321
public void testWithDependencyChecking() {
352322
ApplicationContext ctx =
353323
new ClassPathXmlApplicationContext(DEPENDENCY_CHECK_CONTEXT, getClass());
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aop.framework;
18+
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.stereotype.Component;
21+
import org.springframework.util.Assert;
22+
23+
/**
24+
* @author Oliver Gierke
25+
*/
26+
@Component
27+
public class ClassWithComplexConstructor {
28+
29+
private final Dependency dependency;
30+
31+
@Autowired
32+
public ClassWithComplexConstructor(Dependency dependency) {
33+
Assert.notNull(dependency);
34+
this.dependency = dependency;
35+
}
36+
37+
public Dependency getDependency() {
38+
return dependency;
39+
}
40+
41+
public void method() {
42+
this.dependency.method();
43+
}
44+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aop.framework;
18+
19+
import org.springframework.stereotype.Component;
20+
21+
@Component class Dependency {
22+
23+
private int value = 0;
24+
25+
public void method() {
26+
value++;
27+
}
28+
29+
public int getValue() {
30+
return value;
31+
}
32+
}

0 commit comments

Comments
 (0)