Skip to content

Commit

Permalink
Add support for Objenesis proxy creation.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
odrotbohm authored and Phillip Webb committed Sep 12, 2013
1 parent 41fffdc commit 1f9e8f6
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 62 deletions.
28 changes: 28 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand All @@ -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))
Expand All @@ -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/**"
}
}
}

Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
* @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;
Expand Down Expand Up @@ -185,29 +185,20 @@ public Object getProxy(ClassLoader classLoader) {
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 [" +
Expand All @@ -227,6 +218,15 @@ public Object getProxy(ClassLoader classLoader) {
}
}

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return CglibProxyFactory.createCglibProxy(config);
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
Expand All @@ -75,17 +75,4 @@ private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,7 +26,6 @@
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;
Expand All @@ -43,6 +35,8 @@
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.
Expand Down Expand Up @@ -135,31 +129,6 @@ public void testProxyCanBeClassNotInterface() throws Exception {
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();
Expand Down Expand Up @@ -348,6 +317,7 @@ public void testExceptionHandling() {
}

@Test
@SuppressWarnings("resource")
public void testWithDependencyChecking() {
ApplicationContext ctx =
new ClassPathXmlApplicationContext(DEPENDENCY_CHECK_CONTEXT, getClass());
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 1f9e8f6

Please sign in to comment.