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 0d04031fb3..71b2bf1dda 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-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -205,9 +205,8 @@ class CglibAopProxy implements AopProxy, Serializable { return createProxyClassAndInstance(enhancer, callbacks); } catch (CodeGenerationException | IllegalArgumentException ex) { - throw new AopConfigException("Could not generate CGLIB subclass of class [" + - this.advised.getTargetClass() + "]: " + - "Common causes of this problem include using a final class or a non-visible class", + throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { @@ -743,7 +742,7 @@ class CglibAopProxy implements AopProxy, Serializable { */ @Override protected Object invokeJoinpoint() throws Throwable { - if (this.publicMethod) { + if (this.publicMethod && getMethod().getDeclaringClass() != Object.class) { return this.methodProxy.invoke(this.target, this.arguments); } else { diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java index 43cc76aa8c..09ef9c06e2 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -82,22 +82,24 @@ public abstract class AbstractAspectJAdvisorFactoryTests { @Test public void testRejectsPerCflowAspect() { try { - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new PerCflowAspect(),"someBean")); + getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new PerCflowAspect(), "someBean")); fail("Cannot accept cflow"); } catch (AopConfigException ex) { - assertTrue(ex.getMessage().indexOf("PERCFLOW") != -1); + assertTrue(ex.getMessage().contains("PERCFLOW")); } } @Test public void testRejectsPerCflowBelowAspect() { try { - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new PerCflowBelowAspect(),"someBean")); + getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new PerCflowBelowAspect(), "someBean")); fail("Cannot accept cflowbelow"); } catch (AopConfigException ex) { - assertTrue(ex.getMessage().indexOf("PERCFLOWBELOW") != -1); + assertTrue(ex.getMessage().contains("PERCFLOWBELOW")); } } @@ -227,9 +229,7 @@ public abstract class AbstractAspectJAdvisorFactoryTests { int realAge = 65; target.setAge(realAge); PerTypeWithinAspectInstanceFactory aif = new PerTypeWithinAspectInstanceFactory(); - TestBean itb = (TestBean) createProxy(target, - getFixture().getAdvisors(aif), - TestBean.class); + TestBean itb = (TestBean) createProxy(target, getFixture().getAdvisors(aif), TestBean.class); assertEquals("No method calls", 0, aif.getInstantiationCount()); assertEquals("Around advice must now apply", 0, itb.getAge()); @@ -257,9 +257,7 @@ public abstract class AbstractAspectJAdvisorFactoryTests { assertEquals("Around advice must still apply", 1, itb.getAge()); assertEquals("Around advice must still apply", 2, itb.getAge()); - TestBean itb2 = (TestBean) createProxy(target, - getFixture().getAdvisors(aif), - TestBean.class); + TestBean itb2 = (TestBean) createProxy(target, getFixture().getAdvisors(aif), TestBean.class); assertEquals(1, aif.getInstantiationCount()); assertEquals("Around advice be independent for second instance", 0, itb2.getAge()); assertEquals(2, aif.getInstantiationCount()); @@ -284,7 +282,8 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testNamedPointcutFromAspectLibraryWithBinding() { TestBean target = new TestBean(); ITestBean itb = (ITestBean) createProxy(target, - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new NamedPointcutAspectFromLibraryWithBinding(),"someBean")), + getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory( + new NamedPointcutAspectFromLibraryWithBinding(), "someBean")), ITestBean.class); itb.setAge(10); assertEquals("Around advice must apply", 20, itb.getAge()); @@ -296,7 +295,7 @@ public abstract class AbstractAspectJAdvisorFactoryTests { int realAge = 65; target.setAge(realAge); ITestBean itb = (ITestBean) createProxy(target, - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(aspectInstance,"someBean")), + getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(aspectInstance, "someBean")), ITestBean.class); assertEquals("Around advice must apply", -1, itb.getAge()); assertEquals(realAge, target.getAge()); @@ -306,7 +305,8 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testBindingWithSingleArg() { TestBean target = new TestBean(); ITestBean itb = (ITestBean) createProxy(target, - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new BindingAspectWithSingleArg(),"someBean")), + getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new BindingAspectWithSingleArg(), "someBean")), ITestBean.class); itb.setAge(10); assertEquals("Around advice must apply", 20, itb.getAge()); @@ -317,7 +317,8 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testBindingWithMultipleArgsDifferentlyOrdered() { ManyValuedArgs target = new ManyValuedArgs(); ManyValuedArgs mva = (ManyValuedArgs) createProxy(target, - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new ManyValuedArgs(),"someBean")), + getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new ManyValuedArgs(), "someBean")), ManyValuedArgs.class); String a = "a"; @@ -338,7 +339,7 @@ public abstract class AbstractAspectJAdvisorFactoryTests { assertFalse(notLockableTarget instanceof Lockable); NotLockable notLockable1 = (NotLockable) createProxy(notLockableTarget, getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), + new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean")), NotLockable.class); assertTrue(notLockable1 instanceof Lockable); Lockable lockable = (Lockable) notLockable1; @@ -349,7 +350,7 @@ public abstract class AbstractAspectJAdvisorFactoryTests { NotLockable notLockable2Target = new NotLockable(); NotLockable notLockable2 = (NotLockable) createProxy(notLockable2Target, getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), + new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean")), NotLockable.class); assertTrue(notLockable2 instanceof Lockable); Lockable lockable2 = (Lockable) notLockable2; @@ -369,10 +370,10 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testIntroductionAdvisorExcludedFromTargetImplementingInterface() { assertTrue(AopUtils.findAdvisorsThatCanApply( getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory( - new MakeLockable(),"someBean")), + new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean")), CannotBeUnlocked.class).isEmpty()); - assertEquals(2, AopUtils.findAdvisorsThatCanApply(getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class).size()); + assertEquals(2, AopUtils.findAdvisorsThatCanApply(getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean")), NotLockable.class).size()); } @Test @@ -408,42 +409,34 @@ public abstract class AbstractAspectJAdvisorFactoryTests { getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean")), List.class ), - CannotBeUnlocked.class); + List.class); assertFalse("Type pattern must have excluded mixin", proxy instanceof Lockable); } - /* prereq AspectJ 1.6.7 @Test - public void testIntroductionBasedOnAnnotationMatch_Spr5307() { + public void testIntroductionBasedOnAnnotationMatch_SPR5307() { AnnotatedTarget target = new AnnotatedTargetImpl(); - List advisors = getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory(new MakeAnnotatedTypeModifiable(),"someBean")); - Object proxy = createProxy(target, - advisors, - AnnotatedTarget.class); + new SingletonMetadataAwareAspectInstanceFactory(new MakeAnnotatedTypeModifiable(), "someBean")); + Object proxy = createProxy(target, advisors, AnnotatedTarget.class); System.out.println(advisors.get(1)); assertTrue(proxy instanceof Lockable); Lockable lockable = (Lockable)proxy; lockable.locked(); } - */ // TODO: Why does this test fail? It hasn't been run before, so it maybe never actually passed... - @Test @Ignore public void testIntroductionWithArgumentBinding() { TestBean target = new TestBean(); List advisors = getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory(new MakeITestBeanModifiable(),"someBean")); + new SingletonMetadataAwareAspectInstanceFactory(new MakeITestBeanModifiable(), "someBean")); advisors.addAll(getFixture().getAdvisors( - new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(),"someBean"))); + new SingletonMetadataAwareAspectInstanceFactory(new MakeLockable(), "someBean"))); - Modifiable modifiable = (Modifiable) createProxy(target, - advisors, - ITestBean.class); + Modifiable modifiable = (Modifiable) createProxy(target, advisors, ITestBean.class); assertThat(modifiable, instanceOf(Modifiable.class)); Lockable lockable = (Lockable) modifiable; assertFalse(lockable.locked()); @@ -477,11 +470,11 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testAspectMethodThrowsExceptionLegalOnSignature() { TestBean target = new TestBean(); UnsupportedOperationException expectedException = new UnsupportedOperationException(); - List advisors = getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new ExceptionAspect(expectedException),"someBean")); + List advisors = getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new ExceptionAspect(expectedException), "someBean")); assertEquals("One advice method was found", 1, advisors.size()); - ITestBean itb = (ITestBean) createProxy(target, - advisors, - ITestBean.class); + ITestBean itb = (ITestBean) createProxy(target, advisors, ITestBean.class); + try { itb.getAge(); fail(); @@ -497,11 +490,11 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testAspectMethodThrowsExceptionIllegalOnSignature() { TestBean target = new TestBean(); RemoteException expectedException = new RemoteException(); - List advisors = getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new ExceptionAspect(expectedException),"someBean")); + List advisors = getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(new ExceptionAspect(expectedException), "someBean")); assertEquals("One advice method was found", 1, advisors.size()); - ITestBean itb = (ITestBean) createProxy(target, - advisors, - ITestBean.class); + ITestBean itb = (ITestBean) createProxy(target, advisors, ITestBean.class); + try { itb.getAge(); fail(); @@ -522,10 +515,7 @@ public abstract class AbstractAspectJAdvisorFactoryTests { // Required everywhere we use AspectJ proxies pf.addAdvice(ExposeInvocationInterceptor.INSTANCE); - - for (Object a : advisors) { - pf.addAdvisor((Advisor) a); - } + pf.addAdvisors(advisors); pf.setExposeProxy(true); return pf.getProxy(); @@ -534,13 +524,11 @@ public abstract class AbstractAspectJAdvisorFactoryTests { @Test public void testTwoAdvicesOnOneAspect() { TestBean target = new TestBean(); - TwoAdviceAspect twoAdviceAspect = new TwoAdviceAspect(); - List advisors = getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(twoAdviceAspect,"someBean")); + List advisors = getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(twoAdviceAspect, "someBean")); assertEquals("Two advice methods found", 2, advisors.size()); - ITestBean itb = (ITestBean) createProxy(target, - advisors, - ITestBean.class); + ITestBean itb = (ITestBean) createProxy(target, advisors, ITestBean.class); itb.setName(""); assertEquals(0, itb.getAge()); int newAge = 32; @@ -551,16 +539,15 @@ public abstract class AbstractAspectJAdvisorFactoryTests { @Test public void testAfterAdviceTypes() throws Exception { Echo target = new Echo(); - ExceptionHandling afterReturningAspect = new ExceptionHandling(); - List advisors = getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(afterReturningAspect,"someBean")); - Echo echo = (Echo) createProxy(target, - advisors, - Echo.class); + List advisors = getFixture().getAdvisors( + new SingletonMetadataAwareAspectInstanceFactory(afterReturningAspect, "someBean")); + Echo echo = (Echo) createProxy(target, advisors, Echo.class); assertEquals(0, afterReturningAspect.successCount); assertEquals("", echo.echo("")); assertEquals(1, afterReturningAspect.successCount); assertEquals(0, afterReturningAspect.failureCount); + try { echo.echo(new FileNotFoundException()); fail(); @@ -580,9 +567,9 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testFailureWithoutExplicitDeclarePrecedence() { TestBean target = new TestBean(); MetadataAwareAspectInstanceFactory aspectInstanceFactory = new SingletonMetadataAwareAspectInstanceFactory( - new NoDeclarePrecedenceShouldFail(), "someBean"); + new NoDeclarePrecedenceShouldFail(), "someBean"); ITestBean itb = (ITestBean) createProxy(target, - getFixture().getAdvisors(aspectInstanceFactory), ITestBean.class); + getFixture().getAdvisors(aspectInstanceFactory), ITestBean.class); itb.getAge(); } @@ -590,21 +577,10 @@ public abstract class AbstractAspectJAdvisorFactoryTests { public void testDeclarePrecedenceNotSupported() { TestBean target = new TestBean(); MetadataAwareAspectInstanceFactory aspectInstanceFactory = new SingletonMetadataAwareAspectInstanceFactory( - new DeclarePrecedenceShouldSucceed(), "someBean"); - createProxy(target, getFixture().getAdvisors(aspectInstanceFactory), - ITestBean.class); + new DeclarePrecedenceShouldSucceed(), "someBean"); + createProxy(target, getFixture().getAdvisors(aspectInstanceFactory), ITestBean.class); } - /** Not supported in 2.0! - public void testExplicitDeclarePrecedencePreventsFailure() { - TestBean target = new TestBean(); - ITestBean itb = (ITestBean) createProxy(target, - getFixture().getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(new DeclarePrecedenceShouldSucceed(), "someBean")), - ITestBean.class); - assertEquals(666, itb.getAge()); - } - */ - @Aspect("percflow(execution(* *(..)))") public static class PerCflowAspect { @@ -1019,9 +995,7 @@ class MakeLockable { public static Lockable mixin; @Before(value="execution(void set*(*)) && this(mixin)", argNames="mixin") - public void checkNotLocked( - Lockable mixin) // Bind to arg - { + public void checkNotLocked( Lockable mixin) { // Can also obtain the mixin (this) this way //Lockable mixin = (Lockable) jp.getThis(); if (mixin.locked()) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java index 903b34f115..e5cc670775 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -16,9 +16,6 @@ package org.springframework.beans.factory.config; -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.Map; import java.util.Properties; import org.junit.After; @@ -56,7 +53,7 @@ public class PropertyPlaceholderConfigurerTests { @Before - public void setUp() { + public void setup() { p1BeanDef = rootBeanDefinition(TestBean.class) .addPropertyValue("name", "${" + P1 + "}") .getBeanDefinition(); @@ -66,16 +63,14 @@ public class PropertyPlaceholderConfigurerTests { ppcProperties = new Properties(); ppcProperties.setProperty(P1, P1_LOCAL_PROPS_VAL); System.setProperty(P1, P1_SYSTEM_PROPS_VAL); - getModifiableSystemEnvironment().put(P1, P1_SYSTEM_ENV_VAL); ppc = new PropertyPlaceholderConfigurer(); ppc.setProperties(ppcProperties); } @After - public void tearDown() { + public void cleanup() { System.clearProperty(P1); - getModifiableSystemEnvironment().remove(P1); } @@ -95,7 +90,7 @@ public class PropertyPlaceholderConfigurerTests { @Test public void resolveFromSystemProperties() { - getModifiableSystemEnvironment().put("otherKey", "systemValue"); + System.setProperty("otherKey", "systemValue"); p1BeanDef = rootBeanDefinition(TestBean.class) .addPropertyValue("name", "${" + P1 + "}") .addPropertyValue("sex", "${otherKey}") @@ -105,12 +100,12 @@ public class PropertyPlaceholderConfigurerTests { TestBean bean = bf.getBean(TestBean.class); assertThat(bean.getName(), equalTo(P1_LOCAL_PROPS_VAL)); assertThat(bean.getSex(), equalTo("systemValue")); - getModifiableSystemEnvironment().remove("otherKey"); + System.clearProperty("otherKey"); } @Test public void resolveFromLocalProperties() { - tearDown(); // eliminate entries from system props/environment + System.clearProperty(P1); registerWithGeneratedName(p1BeanDef, bf); ppc.postProcessBeanFactory(bf); TestBean bean = bf.getBean(TestBean.class); @@ -134,16 +129,6 @@ public class PropertyPlaceholderConfigurerTests { assertThat(bean.getName(), equalTo(P1_SYSTEM_PROPS_VAL)); } - @Test - public void setSystemSystemPropertiesMode_toOverride_andResolveFromSystemEnvironment() { - registerWithGeneratedName(p1BeanDef, bf); - System.clearProperty(P1); // will now fall all the way back to system environment - ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE); - ppc.postProcessBeanFactory(bf); - TestBean bean = bf.getBean(TestBean.class); - assertThat(bean.getName(), equalTo(P1_SYSTEM_ENV_VAL)); - } - @Test public void setSystemSystemPropertiesMode_toOverride_andSetSearchSystemEnvironment_toFalse() { registerWithGeneratedName(p1BeanDef, bf); @@ -160,7 +145,7 @@ public class PropertyPlaceholderConfigurerTests { * settings regarding resolving properties from the environment. */ @Test - public void twoPlacholderConfigurers_withConflictingSettings() { + public void twoPlaceholderConfigurers_withConflictingSettings() { String P2 = "p2"; String P2_LOCAL_PROPS_VAL = "p2LocalPropsVal"; String P2_SYSTEM_PROPS_VAL = "p2SystemPropsVal"; @@ -178,7 +163,6 @@ public class PropertyPlaceholderConfigurerTests { ppc.postProcessBeanFactory(bf); System.setProperty(P2, P2_SYSTEM_PROPS_VAL); - getModifiableSystemEnvironment().put(P2, P2_SYSTEM_ENV_VAL); Properties ppc2Properties = new Properties(); ppc2Properties.put(P2, P2_LOCAL_PROPS_VAL); @@ -198,7 +182,6 @@ public class PropertyPlaceholderConfigurerTests { assertThat(p2Bean.getCountry(), equalTo(P2_SYSTEM_PROPS_VAL)); System.clearProperty(P2); - getModifiableSystemEnvironment().remove(P2); } @Test @@ -228,100 +211,41 @@ public class PropertyPlaceholderConfigurerTests { public void nullValueIsPreserved() { PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setNullValue("customNull"); - getModifiableSystemEnvironment().put("my.name", "customNull"); + System.setProperty("my.name", "customNull"); DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class) .addPropertyValue("name", "${my.name}") .getBeanDefinition()); ppc.postProcessBeanFactory(bf); assertThat(bf.getBean(TestBean.class).getName(), nullValue()); - getModifiableSystemEnvironment().remove("my.name"); + System.clearProperty("my.name"); } @Test public void trimValuesIsOffByDefault() { PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); - getModifiableSystemEnvironment().put("my.name", " myValue "); + System.setProperty("my.name", " myValue "); DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class) .addPropertyValue("name", "${my.name}") .getBeanDefinition()); ppc.postProcessBeanFactory(bf); assertThat(bf.getBean(TestBean.class).getName(), equalTo(" myValue ")); - getModifiableSystemEnvironment().remove("my.name"); + System.clearProperty("my.name"); } @Test public void trimValuesIsApplied() { PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setTrimValues(true); - getModifiableSystemEnvironment().put("my.name", " myValue "); + System.setProperty("my.name", " myValue "); DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class) .addPropertyValue("name", "${my.name}") .getBeanDefinition()); ppc.postProcessBeanFactory(bf); assertThat(bf.getBean(TestBean.class).getName(), equalTo("myValue")); - getModifiableSystemEnvironment().remove("my.name"); + System.clearProperty("my.name"); } - - @SuppressWarnings("unchecked") - private static Map getModifiableSystemEnvironment() { - // for os x / linux - Class[] classes = Collections.class.getDeclaredClasses(); - Map env = System.getenv(); - for (Class cl : classes) { - if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { - try { - Field field = cl.getDeclaredField("m"); - field.setAccessible(true); - Object obj = field.get(env); - if (obj != null && obj.getClass().getName().equals("java.lang.ProcessEnvironment$StringEnvironment")) { - return (Map) obj; - } - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - } - - // for windows - Class processEnvironmentClass; - try { - processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - - try { - Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); - theCaseInsensitiveEnvironmentField.setAccessible(true); - Object obj = theCaseInsensitiveEnvironmentField.get(null); - return (Map) obj; - } - catch (NoSuchFieldException ex) { - // do nothing - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - - try { - Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); - theEnvironmentField.setAccessible(true); - Object obj = theEnvironmentField.get(null); - return (Map) obj; - } - catch (NoSuchFieldException ex) { - // do nothing - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - - throw new IllegalStateException(); - } } 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 b7a6d67e14..7ffaafd8ea 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 @@ -118,9 +118,9 @@ class ConfigurationClassEnhancer { /** * Creates a new CGLIB {@link Enhancer} instance. */ - private Enhancer newEnhancer(Class superclass, @Nullable ClassLoader classLoader) { + private Enhancer newEnhancer(Class configSuperClass, @Nullable ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(superclass); + enhancer.setSuperclass(configSuperClass); enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); diff --git a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java index 7d8049df2d..66d0d2dc8b 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/AbstractAopProxyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -212,7 +212,7 @@ public abstract class AbstractAopProxyTests { } @Test - public void testSerializationSerializableTargetAndAdvice() throws Throwable { + public void testSerializableTargetAndAdvice() throws Throwable { SerializablePerson personTarget = new SerializablePerson(); personTarget.setName("jim"); personTarget.setAge(26); @@ -435,7 +435,7 @@ public abstract class AbstractAopProxyTests { TestBean raw = new OwnSpouse(); ProxyCreatorSupport pc = new ProxyCreatorSupport(); - pc.setInterfaces(new Class[] {ITestBean.class}); + pc.setInterfaces(ITestBean.class); pc.setTarget(raw); ITestBean tb = (ITestBean) createProxy(pc); @@ -457,7 +457,7 @@ public abstract class AbstractAopProxyTests { pc.addAdvice(mi); // We don't care about the object - mockTargetSource.setTarget(new Object()); + mockTargetSource.setTarget(new TestBean()); pc.setTargetSource(mockTargetSource); AopProxy aop = createAopProxy(pc); 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 f887ee70bb..4c3a41dbb7 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -122,27 +122,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab } @Test - public void testPackageMethodInvocationWithDifferentClassLoader() { - ClassLoader child = new ClassLoader(getClass().getClassLoader()) { - }; - - PackageMethodTestBean bean = new PackageMethodTestBean(); - bean.value = "foo"; - mockTargetSource.setTarget(bean); - - AdvisedSupport as = new AdvisedSupport(); - as.setTargetSource(mockTargetSource); - as.addAdvice(new NopInterceptor()); - AopProxy aop = new CglibAopProxy(as); - - PackageMethodTestBean proxy = (PackageMethodTestBean) aop.getProxy(child); - assertTrue(AopUtils.isCglibProxy(proxy)); - assertNotEquals(proxy.getClass().getClassLoader(), bean.getClass().getClassLoader()); - assertNull(proxy.getString()); // we're stuck in the proxy instance - } - - @Test - public void testProxyCanBeClassNotInterface() throws Exception { + public void testProxyCanBeClassNotInterface() { TestBean raw = new TestBean(); raw.setAge(32); mockTargetSource.setTarget(raw); @@ -174,7 +154,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab } @Test - public void testUnadvisedProxyCreationWithCallDuringConstructor() throws Exception { + public void testUnadvisedProxyCreationWithCallDuringConstructor() { CglibTestBean target = new CglibTestBean(); target.setName("Rob Harrop"); @@ -370,7 +350,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab } @Test - public void testProxyProtectedMethod() throws Exception { + public void testProxyProtectedMethod() { CountingBeforeAdvice advice = new CountingBeforeAdvice(); ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); proxyFactory.addAdvice(advice); @@ -382,14 +362,14 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab } @Test - public void testProxyTargetClassInCaseOfNoInterfaces() throws Exception { + public void testProxyTargetClassInCaseOfNoInterfaces() { ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); MyBean proxy = (MyBean) proxyFactory.getProxy(); assertEquals(4, proxy.add(1, 3)); } @Test // SPR-13328 - public void testVarargsWithEnumArray() throws Exception { + public void testVarargsWithEnumArray() { ProxyFactory proxyFactory = new ProxyFactory(new MyBean()); MyBean proxy = (MyBean) proxyFactory.getProxy(); assertTrue(proxy.doWithVarargs(MyEnum.A, MyOtherEnum.C)); diff --git a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreatorInitTests.java b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreatorInitTests.java index c223fe7aed..ffc5e4f5de 100644 --- a/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreatorInitTests.java +++ b/spring-context/src/test/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreatorInitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2018 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. @@ -35,13 +35,13 @@ import static org.junit.Assert.*; public class BeanNameAutoProxyCreatorInitTests { @Test(expected = IllegalArgumentException.class) - public void testIgnoreAdvisorThatIsCurrentlyCreation() { + public void testIgnoreAdvisorThatIsCurrentlyInCreation() { ClassPathXmlApplicationContext ctx = - new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass()); + new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass()); TestBean bean = (TestBean) ctx.getBean("bean"); bean.setName("foo"); assertEquals("foo", bean.getName()); - bean.setName(null); // should throw + bean.setName(null); // should throw } } diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 7a5281d7bc..dd190d50c2 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -40,7 +40,7 @@ task cglibRepackJar(type: Jar) { repackJar -> } // Repackage net.sf.cglib => org.springframework.cglib rule(pattern: "net.sf.cglib.**", result: "org.springframework.cglib.@1") - // As mentioned above, transform cglib"s internal asm dependencies from + // As mentioned above, transform cglib's internal asm dependencies from // org.objectweb.asm => org.springframework.asm. Doing this counts on the // the fact that Spring and cglib depend on the same version of asm! rule(pattern: "org.objectweb.asm.**", result: "org.springframework.asm.@1") @@ -98,6 +98,11 @@ jar { dependsOn cglibRepackJar from(zipTree(cglibRepackJar.archivePath)) { include "org/springframework/cglib/**" + exclude "org/springframework/cglib/core/AbstractClassGenerator*.class" + exclude "org/springframework/cglib/core/KeyFactory*.class" + exclude "org/springframework/cglib/core/ReflectUtils*.class" + exclude "org/springframework/cglib/proxy/Enhancer*.class" + exclude "org/springframework/cglib/proxy/MethodProxy*.class" } dependsOn objenesisRepackJar diff --git a/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java b/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java new file mode 100644 index 0000000000..85a37b60f9 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/AbstractClassGenerator.java @@ -0,0 +1,379 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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.cglib.core; + +import java.lang.ref.WeakReference; +import java.security.ProtectionDomain; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.springframework.asm.ClassReader; +import org.springframework.cglib.core.internal.Function; +import org.springframework.cglib.core.internal.LoadingCache; + +/** + * Abstract class for all code-generating CGLIB utilities. + * In addition to caching generated classes for performance, it provides hooks for + * customizing the ClassLoader, name of the generated class, and transformations + * applied before generation. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class AbstractClassGenerator implements ClassGenerator { + + private static final ThreadLocal CURRENT = new ThreadLocal(); + + private static volatile Map CACHE = new WeakHashMap(); + + private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE; + + private NamingPolicy namingPolicy = DefaultNamingPolicy.INSTANCE; + + private Source source; + + private ClassLoader classLoader; + + private Class contextClass; + + private String namePrefix; + + private Object key; + + private boolean useCache = true; + + private String className; + + private boolean attemptLoad; + + + protected static class ClassLoaderData { + + private final Set reservedClassNames = new HashSet(); + + /** + * {@link AbstractClassGenerator} here holds "cache key" (e.g. {@link org.springframework.cglib.proxy.Enhancer} + * configuration), and the value is the generated class plus some additional values + * (see {@link #unwrapCachedValue(Object)}. + *

The generated classes can be reused as long as their classloader is reachable.

+ *

Note: the only way to access a class is to find it through generatedClasses cache, thus + * the key should not expire as long as the class itself is alive (its classloader is alive).

+ */ + private final LoadingCache generatedClasses; + + /** + * Note: ClassLoaderData object is stored as a value of {@code WeakHashMap} thus + * this classLoader reference should be weak otherwise it would make classLoader strongly reachable + * and alive forever. + * Reference queue is not required since the cleanup is handled by {@link WeakHashMap}. + */ + private final WeakReference classLoader; + + private final Predicate uniqueNamePredicate = new Predicate() { + public boolean evaluate(Object name) { + return reservedClassNames.contains(name); + } + }; + + private static final Function GET_KEY = new Function() { + public Object apply(AbstractClassGenerator gen) { + return gen.key; + } + }; + + public ClassLoaderData(ClassLoader classLoader) { + if (classLoader == null) { + throw new IllegalArgumentException("classLoader == null is not yet supported"); + } + this.classLoader = new WeakReference(classLoader); + Function load = + new Function() { + public Object apply(AbstractClassGenerator gen) { + Class klass = gen.generate(ClassLoaderData.this); + return gen.wrapCachedClass(klass); + } + }; + generatedClasses = new LoadingCache(GET_KEY, load); + } + + public ClassLoader getClassLoader() { + return classLoader.get(); + } + + public void reserveName(String name) { + reservedClassNames.add(name); + } + + public Predicate getUniqueNamePredicate() { + return uniqueNamePredicate; + } + + public Object get(AbstractClassGenerator gen, boolean useCache) { + if (!useCache) { + return gen.generate(ClassLoaderData.this); + } + else { + Object cachedValue = generatedClasses.get(gen); + return gen.unwrapCachedValue(cachedValue); + } + } + } + + + protected T wrapCachedClass(Class klass) { + return (T) new WeakReference(klass); + } + + protected Object unwrapCachedValue(T cached) { + return ((WeakReference) cached).get(); + } + + + protected static class Source { + + String name; + + public Source(String name) { + this.name = name; + } + } + + + protected AbstractClassGenerator(Source source) { + this.source = source; + } + + protected void setNamePrefix(String namePrefix) { + this.namePrefix = namePrefix; + } + + final protected String getClassName() { + return className; + } + + private void setClassName(String className) { + this.className = className; + } + + private String generateClassName(Predicate nameTestPredicate) { + return namingPolicy.getClassName(namePrefix, source.name, key, nameTestPredicate); + } + + /** + * Set the ClassLoader in which the class will be generated. + * Concrete subclasses of AbstractClassGenerator (such as Enhancer) + * will try to choose an appropriate default if this is unset. + *

+ * Classes are cached per-ClassLoader using a WeakHashMap, to allow + * the generated classes to be removed when the associated loader is garbage collected. + * @param classLoader the loader to generate the new class with, or null to use the default + */ + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + // SPRING PATCH BEGIN + public void setContextClass(Class contextClass) { + this.contextClass = contextClass; + } + // SPRING PATCH END + + /** + * Override the default naming policy. + * @param namingPolicy the custom policy, or null to use the default + * @see DefaultNamingPolicy + */ + public void setNamingPolicy(NamingPolicy namingPolicy) { + if (namingPolicy == null) + namingPolicy = DefaultNamingPolicy.INSTANCE; + this.namingPolicy = namingPolicy; + } + + /** + * @see #setNamingPolicy + */ + public NamingPolicy getNamingPolicy() { + return namingPolicy; + } + + /** + * Whether use and update the static cache of generated classes + * for a class with the same properties. Default is true. + */ + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + /** + * @see #setUseCache + */ + public boolean getUseCache() { + return useCache; + } + + /** + * If set, CGLIB will attempt to load classes from the specified + * ClassLoader before generating them. Because generated + * class names are not guaranteed to be unique, the default is false. + */ + public void setAttemptLoad(boolean attemptLoad) { + this.attemptLoad = attemptLoad; + } + + public boolean getAttemptLoad() { + return attemptLoad; + } + + /** + * Set the strategy to use to create the bytecode from this generator. + * By default an instance of {@see DefaultGeneratorStrategy} is used. + */ + public void setStrategy(GeneratorStrategy strategy) { + if (strategy == null) + strategy = DefaultGeneratorStrategy.INSTANCE; + this.strategy = strategy; + } + + /** + * @see #setStrategy + */ + public GeneratorStrategy getStrategy() { + return strategy; + } + + /** + * Used internally by CGLIB. Returns the AbstractClassGenerator + * that is being used to generate a class in the current thread. + */ + public static AbstractClassGenerator getCurrent() { + return (AbstractClassGenerator) CURRENT.get(); + } + + public ClassLoader getClassLoader() { + ClassLoader t = classLoader; + if (t == null) { + t = getDefaultClassLoader(); + } + if (t == null) { + t = getClass().getClassLoader(); + } + if (t == null) { + t = Thread.currentThread().getContextClassLoader(); + } + if (t == null) { + throw new IllegalStateException("Cannot determine classloader"); + } + return t; + } + + abstract protected ClassLoader getDefaultClassLoader(); + + /** + * Returns the protection domain to use when defining the class. + *

+ * Default implementation returns null for using a default protection domain. Sub-classes may + * override to use a more specific protection domain. + *

+ * @return the protection domain (null for using a default) + */ + protected ProtectionDomain getProtectionDomain() { + return null; + } + + protected Object create(Object key) { + try { + ClassLoader loader = getClassLoader(); + Map cache = CACHE; + ClassLoaderData data = cache.get(loader); + if (data == null) { + synchronized (AbstractClassGenerator.class) { + cache = CACHE; + data = cache.get(loader); + if (data == null) { + Map newCache = new WeakHashMap(cache); + data = new ClassLoaderData(loader); + newCache.put(loader, data); + CACHE = newCache; + } + } + } + this.key = key; + Object obj = data.get(this, getUseCache()); + if (obj instanceof Class) { + return firstInstance((Class) obj); + } + return nextInstance(obj); + } + catch (RuntimeException | Error ex) { + throw ex; + } + catch (Exception ex) { + throw new CodeGenerationException(ex); + } + } + + protected Class generate(ClassLoaderData data) { + Class gen; + Object save = CURRENT.get(); + CURRENT.set(this); + try { + ClassLoader classLoader = data.getClassLoader(); + if (classLoader == null) { + throw new IllegalStateException("ClassLoader is null while trying to define class " + + getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " + + "Please file an issue at cglib's issue tracker."); + } + synchronized (classLoader) { + String name = generateClassName(data.getUniqueNamePredicate()); + data.reserveName(name); + this.setClassName(name); + } + if (attemptLoad) { + try { + gen = classLoader.loadClass(getClassName()); + return gen; + } + catch (ClassNotFoundException e) { + // ignore + } + } + byte[] b = strategy.generate(this); + String className = ClassNameReader.getClassName(new ClassReader(b)); + ProtectionDomain protectionDomain = getProtectionDomain(); + synchronized (classLoader) { // just in case + // SPRING PATCH BEGIN + gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain, contextClass); + // SPRING PATCH END + } + return gen; + } + catch (RuntimeException | Error ex) { + throw ex; + } + catch (Exception ex) { + throw new CodeGenerationException(ex); + } + finally { + CURRENT.set(save); + } + } + + abstract protected Object firstInstance(Class type) throws Exception; + + abstract protected Object nextInstance(Object instance) throws Exception; + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java b/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java new file mode 100644 index 0000000000..3adee79367 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/KeyFactory.java @@ -0,0 +1,363 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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.cglib.core; + +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.Collections; +import java.util.List; + +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Label; +import org.springframework.asm.Type; +import org.springframework.cglib.core.internal.CustomizerRegistry; + +/** + * Generates classes to handle multi-valued keys, for use in things such as Maps and Sets. + * Code for equals and hashCode methods follow the + * the rules laid out in Effective Java by Joshua Bloch. + *

+ * To generate a KeyFactory, you need to supply an interface which + * describes the structure of the key. The interface should have a + * single method named newInstance, which returns an + * Object. The arguments array can be + * anything--Objects, primitive values, or single or + * multi-dimension arrays of either. For example: + *

+ *     private interface IntStringKey {
+ *         public Object newInstance(int i, String s);
+ *     }
+ * 

+ * Once you have made a KeyFactory, you generate a new key by calling + * the newInstance method defined by your interface. + *

+ *     IntStringKey factory = (IntStringKey)KeyFactory.create(IntStringKey.class);
+ *     Object key1 = factory.newInstance(4, "Hello");
+ *     Object key2 = factory.newInstance(4, "World");
+ * 

+ * Note: + * hashCode equality between two keys key1 and key2 is only guaranteed if + * key1.equals(key2) and the keys were produced by the same factory. + * @version $Id: KeyFactory.java,v 1.26 2006/03/05 02:43:19 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +abstract public class KeyFactory { + + private static final Signature GET_NAME = + TypeUtils.parseSignature("String getName()"); + + private static final Signature GET_CLASS = + TypeUtils.parseSignature("Class getClass()"); + + private static final Signature HASH_CODE = + TypeUtils.parseSignature("int hashCode()"); + + private static final Signature EQUALS = + TypeUtils.parseSignature("boolean equals(Object)"); + + private static final Signature TO_STRING = + TypeUtils.parseSignature("String toString()"); + + private static final Signature APPEND_STRING = + TypeUtils.parseSignature("StringBuffer append(String)"); + + private static final Type KEY_FACTORY = + TypeUtils.parseType("org.springframework.cglib.core.KeyFactory"); + + private static final Signature GET_SORT = + TypeUtils.parseSignature("int getSort()"); + + //generated numbers: + private final static int PRIMES[] = { + 11, 73, 179, 331, + 521, 787, 1213, 1823, + 2609, 3691, 5189, 7247, + 10037, 13931, 19289, 26627, + 36683, 50441, 69403, 95401, + 131129, 180179, 247501, 340057, + 467063, 641371, 880603, 1209107, + 1660097, 2279161, 3129011, 4295723, + 5897291, 8095873, 11114263, 15257791, + 20946017, 28754629, 39474179, 54189869, + 74391461, 102123817, 140194277, 192456917, + 264202273, 362693231, 497900099, 683510293, + 938313161, 1288102441, 1768288259}; + + + public static final Customizer CLASS_BY_NAME = new Customizer() { + public void customize(CodeEmitter e, Type type) { + if (type.equals(Constants.TYPE_CLASS)) { + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + } + } + }; + + public static final FieldTypeCustomizer STORE_CLASS_AS_STRING = new FieldTypeCustomizer() { + public void customize(CodeEmitter e, int index, Type type) { + if (type.equals(Constants.TYPE_CLASS)) { + e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME); + } + } + public Type getOutType(int index, Type type) { + if (type.equals(Constants.TYPE_CLASS)) { + return Constants.TYPE_STRING; + } + return type; + } + }; + + /** + * {@link Type#hashCode()} is very expensive as it traverses full descriptor to calculate hash code. + * This customizer uses {@link Type#getSort()} as a hash code. + */ + public static final HashCodeCustomizer HASH_ASM_TYPE = new HashCodeCustomizer() { + public boolean customize(CodeEmitter e, Type type) { + if (Constants.TYPE_TYPE.equals(type)) { + e.invoke_virtual(type, GET_SORT); + return true; + } + return false; + } + }; + + /** + * @deprecated this customizer might result in unexpected class leak since key object still holds a strong reference to the Object and class. + * It is recommended to have pre-processing method that would strip Objects and represent Classes as Strings + */ + @Deprecated + public static final Customizer OBJECT_BY_CLASS = new Customizer() { + public void customize(CodeEmitter e, Type type) { + e.invoke_virtual(Constants.TYPE_OBJECT, GET_CLASS); + } + }; + + protected KeyFactory() { + } + + public static KeyFactory create(Class keyInterface) { + return create(keyInterface, null); + } + + public static KeyFactory create(Class keyInterface, Customizer customizer) { + return create(keyInterface.getClassLoader(), keyInterface, customizer); + } + + public static KeyFactory create(Class keyInterface, KeyFactoryCustomizer first, List next) { + return create(keyInterface.getClassLoader(), keyInterface, first, next); + } + + public static KeyFactory create(ClassLoader loader, Class keyInterface, Customizer customizer) { + return create(loader, keyInterface, customizer, Collections.emptyList()); + } + + public static KeyFactory create(ClassLoader loader, Class keyInterface, KeyFactoryCustomizer customizer, + List next) { + Generator gen = new Generator(); + gen.setInterface(keyInterface); + // SPRING PATCH BEGIN + gen.setContextClass(keyInterface); + // SPRING PATCH END + + if (customizer != null) { + gen.addCustomizer(customizer); + } + if (next != null && !next.isEmpty()) { + for (KeyFactoryCustomizer keyFactoryCustomizer : next) { + gen.addCustomizer(keyFactoryCustomizer); + } + } + gen.setClassLoader(loader); + return gen.create(); + } + + + public static class Generator extends AbstractClassGenerator { + + private static final Source SOURCE = new Source(KeyFactory.class.getName()); + + private static final Class[] KNOWN_CUSTOMIZER_TYPES = new Class[]{Customizer.class, FieldTypeCustomizer.class}; + + private Class keyInterface; + + // TODO: Make me final when deprecated methods are removed + private CustomizerRegistry customizers = new CustomizerRegistry(KNOWN_CUSTOMIZER_TYPES); + + private int constant; + + private int multiplier; + + public Generator() { + super(SOURCE); + } + + protected ClassLoader getDefaultClassLoader() { + return keyInterface.getClassLoader(); + } + + protected ProtectionDomain getProtectionDomain() { + return ReflectUtils.getProtectionDomain(keyInterface); + } + + /** + * @deprecated Use {@link #addCustomizer(KeyFactoryCustomizer)} instead. + */ + @Deprecated + public void setCustomizer(Customizer customizer) { + customizers = CustomizerRegistry.singleton(customizer); + } + + public void addCustomizer(KeyFactoryCustomizer customizer) { + customizers.add(customizer); + } + + public List getCustomizers(Class klass) { + return customizers.get(klass); + } + + public void setInterface(Class keyInterface) { + this.keyInterface = keyInterface; + } + + public KeyFactory create() { + setNamePrefix(keyInterface.getName()); + return (KeyFactory) super.create(keyInterface.getName()); + } + + public void setHashConstant(int constant) { + this.constant = constant; + } + + public void setHashMultiplier(int multiplier) { + this.multiplier = multiplier; + } + + protected Object firstInstance(Class type) { + return ReflectUtils.newInstance(type); + } + + protected Object nextInstance(Object instance) { + return instance; + } + + public void generateClass(ClassVisitor v) { + ClassEmitter ce = new ClassEmitter(v); + + Method newInstance = ReflectUtils.findNewInstance(keyInterface); + if (!newInstance.getReturnType().equals(Object.class)) { + throw new IllegalArgumentException("newInstance method must return Object"); + } + + Type[] parameterTypes = TypeUtils.getTypes(newInstance.getParameterTypes()); + ce.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + KEY_FACTORY, + new Type[]{Type.getType(keyInterface)}, + Constants.SOURCE_FILE); + EmitUtils.null_constructor(ce); + EmitUtils.factory_method(ce, ReflectUtils.getSignature(newInstance)); + + int seed = 0; + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, + TypeUtils.parseConstructor(parameterTypes), + null); + e.load_this(); + e.super_invoke_constructor(); + e.load_this(); + List fieldTypeCustomizers = getCustomizers(FieldTypeCustomizer.class); + for (int i = 0; i < parameterTypes.length; i++) { + Type parameterType = parameterTypes[i]; + Type fieldType = parameterType; + for (FieldTypeCustomizer customizer : fieldTypeCustomizers) { + fieldType = customizer.getOutType(i, fieldType); + } + seed += fieldType.hashCode(); + ce.declare_field(Constants.ACC_PRIVATE | Constants.ACC_FINAL, + getFieldName(i), + fieldType, + null); + e.dup(); + e.load_arg(i); + for (FieldTypeCustomizer customizer : fieldTypeCustomizers) { + customizer.customize(e, i, parameterType); + } + e.putfield(getFieldName(i)); + } + e.return_value(); + e.end_method(); + + // hash code + e = ce.begin_method(Constants.ACC_PUBLIC, HASH_CODE, null); + int hc = (constant != 0) ? constant : PRIMES[(Math.abs(seed) % PRIMES.length)]; + int hm = (multiplier != 0) ? multiplier : PRIMES[(Math.abs(seed * 13) % PRIMES.length)]; + e.push(hc); + for (int i = 0; i < parameterTypes.length; i++) { + e.load_this(); + e.getfield(getFieldName(i)); + EmitUtils.hash_code(e, parameterTypes[i], hm, customizers); + } + e.return_value(); + e.end_method(); + + // equals + e = ce.begin_method(Constants.ACC_PUBLIC, EQUALS, null); + Label fail = e.make_label(); + e.load_arg(0); + e.instance_of_this(); + e.if_jump(CodeEmitter.EQ, fail); + for (int i = 0; i < parameterTypes.length; i++) { + e.load_this(); + e.getfield(getFieldName(i)); + e.load_arg(0); + e.checkcast_this(); + e.getfield(getFieldName(i)); + EmitUtils.not_equals(e, parameterTypes[i], fail, customizers); + } + e.push(1); + e.return_value(); + e.mark(fail); + e.push(0); + e.return_value(); + e.end_method(); + + // toString + e = ce.begin_method(Constants.ACC_PUBLIC, TO_STRING, null); + e.new_instance(Constants.TYPE_STRING_BUFFER); + e.dup(); + e.invoke_constructor(Constants.TYPE_STRING_BUFFER); + for (int i = 0; i < parameterTypes.length; i++) { + if (i > 0) { + e.push(", "); + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING); + } + e.load_this(); + e.getfield(getFieldName(i)); + EmitUtils.append_string(e, parameterTypes[i], EmitUtils.DEFAULT_DELIMITERS, customizers); + } + e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING); + e.return_value(); + e.end_method(); + + ce.end_class(); + } + + private String getFieldName(int arg) { + return "FIELD_" + arg; + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java new file mode 100644 index 0000000000..89256606e8 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java @@ -0,0 +1,617 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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.cglib.core; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.Attribute; +import org.springframework.asm.Type; + +/** + * @version $Id: ReflectUtils.java,v 1.30 2009/01/11 19:47:49 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ReflectUtils { + + private ReflectUtils() { + } + + private static final Map primitives = new HashMap(8); + + private static final Map transforms = new HashMap(8); + + private static final ClassLoader defaultLoader = ReflectUtils.class.getClassLoader(); + + // SPRING PATCH BEGIN + private static final Method privateLookupInMethod; + + private static final Method lookupDefineClassMethod; + + private static final Method classLoaderDefineClassMethod; + + private static final ProtectionDomain PROTECTION_DOMAIN; + + private static final Throwable THROWABLE; + + private static final List OBJECT_METHODS = new ArrayList(); + + static { + Method privateLookupIn; + Method lookupDefineClass; + Method classLoaderDefineClass; + ProtectionDomain protectionDomain; + Throwable throwable = null; + try { + privateLookupIn = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + try { + return MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class); + } + catch (NoSuchMethodException ex) { + return null; + } + } + }); + lookupDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + try { + return MethodHandles.Lookup.class.getMethod("defineClass", byte[].class); + } + catch (NoSuchMethodException ex) { + return null; + } + } + }); + classLoaderDefineClass = (Method) AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return ClassLoader.class.getDeclaredMethod("defineClass", + String.class, byte[].class, Integer.TYPE, Integer.TYPE, ProtectionDomain.class); + } + }); + protectionDomain = getProtectionDomain(ReflectUtils.class); + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + Method[] methods = Object.class.getDeclaredMethods(); + for (Method method : methods) { + if ("finalize".equals(method.getName()) + || (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) { + continue; + } + OBJECT_METHODS.add(method); + } + return null; + } + }); + } + catch (Throwable t) { + privateLookupIn = null; + lookupDefineClass = null; + classLoaderDefineClass = null; + protectionDomain = null; + throwable = t; + } + privateLookupInMethod = privateLookupIn; + lookupDefineClassMethod = lookupDefineClass; + classLoaderDefineClassMethod = classLoaderDefineClass; + PROTECTION_DOMAIN = protectionDomain; + THROWABLE = throwable; + } + // SPRING PATCH END + + private static final String[] CGLIB_PACKAGES = { + "java.lang", + }; + + static { + primitives.put("byte", Byte.TYPE); + primitives.put("char", Character.TYPE); + primitives.put("double", Double.TYPE); + primitives.put("float", Float.TYPE); + primitives.put("int", Integer.TYPE); + primitives.put("long", Long.TYPE); + primitives.put("short", Short.TYPE); + primitives.put("boolean", Boolean.TYPE); + + transforms.put("byte", "B"); + transforms.put("char", "C"); + transforms.put("double", "D"); + transforms.put("float", "F"); + transforms.put("int", "I"); + transforms.put("long", "J"); + transforms.put("short", "S"); + transforms.put("boolean", "Z"); + } + + public static ProtectionDomain getProtectionDomain(final Class source) { + if (source == null) { + return null; + } + return (ProtectionDomain) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return source.getProtectionDomain(); + } + }); + } + + public static Type[] getExceptionTypes(Member member) { + if (member instanceof Method) { + return TypeUtils.getTypes(((Method) member).getExceptionTypes()); + } + else if (member instanceof Constructor) { + return TypeUtils.getTypes(((Constructor) member).getExceptionTypes()); + } + else { + throw new IllegalArgumentException("Cannot get exception types of a field"); + } + } + + public static Signature getSignature(Member member) { + if (member instanceof Method) { + return new Signature(member.getName(), Type.getMethodDescriptor((Method) member)); + } + else if (member instanceof Constructor) { + Type[] types = TypeUtils.getTypes(((Constructor) member).getParameterTypes()); + return new Signature(Constants.CONSTRUCTOR_NAME, + Type.getMethodDescriptor(Type.VOID_TYPE, types)); + + } + else { + throw new IllegalArgumentException("Cannot get signature of a field"); + } + } + + public static Constructor findConstructor(String desc) { + return findConstructor(desc, defaultLoader); + } + + public static Constructor findConstructor(String desc, ClassLoader loader) { + try { + int lparen = desc.indexOf('('); + String className = desc.substring(0, lparen).trim(); + return getClass(className, loader).getConstructor(parseTypes(desc, loader)); + } + catch (ClassNotFoundException | NoSuchMethodException ex) { + throw new CodeGenerationException(ex); + } + } + + public static Method findMethod(String desc) { + return findMethod(desc, defaultLoader); + } + + public static Method findMethod(String desc, ClassLoader loader) { + try { + int lparen = desc.indexOf('('); + int dot = desc.lastIndexOf('.', lparen); + String className = desc.substring(0, dot).trim(); + String methodName = desc.substring(dot + 1, lparen).trim(); + return getClass(className, loader).getDeclaredMethod(methodName, parseTypes(desc, loader)); + } + catch (ClassNotFoundException | NoSuchMethodException ex) { + throw new CodeGenerationException(ex); + } + } + + private static Class[] parseTypes(String desc, ClassLoader loader) throws ClassNotFoundException { + int lparen = desc.indexOf('('); + int rparen = desc.indexOf(')', lparen); + List params = new ArrayList(); + int start = lparen + 1; + for (; ; ) { + int comma = desc.indexOf(',', start); + if (comma < 0) { + break; + } + params.add(desc.substring(start, comma).trim()); + start = comma + 1; + } + if (start < rparen) { + params.add(desc.substring(start, rparen).trim()); + } + Class[] types = new Class[params.size()]; + for (int i = 0; i < types.length; i++) { + types[i] = getClass((String) params.get(i), loader); + } + return types; + } + + private static Class getClass(String className, ClassLoader loader) throws ClassNotFoundException { + return getClass(className, loader, CGLIB_PACKAGES); + } + + private static Class getClass(String className, ClassLoader loader, String[] packages) throws ClassNotFoundException { + String save = className; + int dimensions = 0; + int index = 0; + while ((index = className.indexOf("[]", index) + 1) > 0) { + dimensions++; + } + StringBuffer brackets = new StringBuffer(className.length() - dimensions); + for (int i = 0; i < dimensions; i++) { + brackets.append('['); + } + className = className.substring(0, className.length() - 2 * dimensions); + + String prefix = (dimensions > 0) ? brackets + "L" : ""; + String suffix = (dimensions > 0) ? ";" : ""; + try { + return Class.forName(prefix + className + suffix, false, loader); + } + catch (ClassNotFoundException ignore) { + } + for (int i = 0; i < packages.length; i++) { + try { + return Class.forName(prefix + packages[i] + '.' + className + suffix, false, loader); + } + catch (ClassNotFoundException ignore) { + } + } + if (dimensions == 0) { + Class c = (Class) primitives.get(className); + if (c != null) { + return c; + } + } + else { + String transform = (String) transforms.get(className); + if (transform != null) { + try { + return Class.forName(brackets + transform, false, loader); + } + catch (ClassNotFoundException ignore) { + } + } + } + throw new ClassNotFoundException(save); + } + + public static Object newInstance(Class type) { + return newInstance(type, Constants.EMPTY_CLASS_ARRAY, null); + } + + public static Object newInstance(Class type, Class[] parameterTypes, Object[] args) { + return newInstance(getConstructor(type, parameterTypes), args); + } + + @SuppressWarnings("deprecation") // on JDK 9 + public static Object newInstance(final Constructor cstruct, final Object[] args) { + boolean flag = cstruct.isAccessible(); + try { + if (!flag) { + cstruct.setAccessible(true); + } + Object result = cstruct.newInstance(args); + return result; + } + catch (InstantiationException e) { + throw new CodeGenerationException(e); + } + catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } + catch (InvocationTargetException e) { + throw new CodeGenerationException(e.getTargetException()); + } + finally { + if (!flag) { + cstruct.setAccessible(flag); + } + } + } + + public static Constructor getConstructor(Class type, Class[] parameterTypes) { + try { + Constructor constructor = type.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } + catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + public static String[] getNames(Class[] classes) { + if (classes == null) + return null; + String[] names = new String[classes.length]; + for (int i = 0; i < names.length; i++) { + names[i] = classes[i].getName(); + } + return names; + } + + public static Class[] getClasses(Object[] objects) { + Class[] classes = new Class[objects.length]; + for (int i = 0; i < objects.length; i++) { + classes[i] = objects[i].getClass(); + } + return classes; + } + + public static Method findNewInstance(Class iface) { + Method m = findInterfaceMethod(iface); + if (!m.getName().equals("newInstance")) { + throw new IllegalArgumentException(iface + " missing newInstance method"); + } + return m; + } + + public static Method[] getPropertyMethods(PropertyDescriptor[] properties, boolean read, boolean write) { + Set methods = new HashSet(); + for (int i = 0; i < properties.length; i++) { + PropertyDescriptor pd = properties[i]; + if (read) { + methods.add(pd.getReadMethod()); + } + if (write) { + methods.add(pd.getWriteMethod()); + } + } + methods.remove(null); + return (Method[]) methods.toArray(new Method[methods.size()]); + } + + public static PropertyDescriptor[] getBeanProperties(Class type) { + return getPropertiesHelper(type, true, true); + } + + public static PropertyDescriptor[] getBeanGetters(Class type) { + return getPropertiesHelper(type, true, false); + } + + public static PropertyDescriptor[] getBeanSetters(Class type) { + return getPropertiesHelper(type, false, true); + } + + private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) { + try { + BeanInfo info = Introspector.getBeanInfo(type, Object.class); + PropertyDescriptor[] all = info.getPropertyDescriptors(); + if (read && write) { + return all; + } + List properties = new ArrayList(all.length); + for (int i = 0; i < all.length; i++) { + PropertyDescriptor pd = all[i]; + if ((read && pd.getReadMethod() != null) || + (write && pd.getWriteMethod() != null)) { + properties.add(pd); + } + } + return (PropertyDescriptor[]) properties.toArray(new PropertyDescriptor[properties.size()]); + } + catch (IntrospectionException e) { + throw new CodeGenerationException(e); + } + } + + public static Method findDeclaredMethod(final Class type, + final String methodName, final Class[] parameterTypes) + throws NoSuchMethodException { + + Class cl = type; + while (cl != null) { + try { + return cl.getDeclaredMethod(methodName, parameterTypes); + } + catch (NoSuchMethodException e) { + cl = cl.getSuperclass(); + } + } + throw new NoSuchMethodException(methodName); + } + + public static List addAllMethods(final Class type, final List list) { + if (type == Object.class) { + list.addAll(OBJECT_METHODS); + } + else + list.addAll(java.util.Arrays.asList(type.getDeclaredMethods())); + + Class superclass = type.getSuperclass(); + if (superclass != null) { + addAllMethods(superclass, list); + } + Class[] interfaces = type.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + addAllMethods(interfaces[i], list); + } + + return list; + } + + public static List addAllInterfaces(Class type, List list) { + Class superclass = type.getSuperclass(); + if (superclass != null) { + list.addAll(Arrays.asList(type.getInterfaces())); + addAllInterfaces(superclass, list); + } + return list; + } + + + public static Method findInterfaceMethod(Class iface) { + if (!iface.isInterface()) { + throw new IllegalArgumentException(iface + " is not an interface"); + } + Method[] methods = iface.getDeclaredMethods(); + if (methods.length != 1) { + throw new IllegalArgumentException("expecting exactly 1 method in " + iface); + } + return methods[0]; + } + + // SPRING PATCH BEGIN + public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception { + return defineClass(className, b, loader, null, null); + } + + public static Class defineClass(String className, byte[] b, ClassLoader loader, + ProtectionDomain protectionDomain) throws Exception { + + return defineClass(className, b, loader, protectionDomain, null); + } + + @SuppressWarnings("deprecation") // on JDK 9 + public static Class defineClass(String className, byte[] b, ClassLoader loader, + ProtectionDomain protectionDomain, Class contextClass) throws Exception { + + Class c = null; + if (contextClass != null && privateLookupInMethod != null && lookupDefineClassMethod != null) { + try { + MethodHandles.Lookup lookup = + (MethodHandles.Lookup) privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup()); + c = (Class) lookupDefineClassMethod.invoke(lookup, b); + } + catch (InvocationTargetException ex) { + if (!(ex.getTargetException() instanceof IllegalArgumentException)) { + throw new CodeGenerationException(ex.getTargetException()); + } + // in case of IllegalArgumentException: fall through to defineClass + } + catch (Throwable ex) { + throw new CodeGenerationException(ex); + } + } + if (protectionDomain == null) { + protectionDomain = PROTECTION_DOMAIN; + } + if (c == null) { + if (classLoaderDefineClassMethod != null) { + Object[] args = new Object[]{className, b, 0, b.length, protectionDomain}; + try { + if (!classLoaderDefineClassMethod.isAccessible()) { + classLoaderDefineClassMethod.setAccessible(true); + } + c = (Class) classLoaderDefineClassMethod.invoke(loader, args); + } + catch (InvocationTargetException ex) { + throw new CodeGenerationException(ex.getTargetException()); + } + catch (Throwable ex) { + throw new CodeGenerationException(ex); + } + } + else { + throw new CodeGenerationException(THROWABLE); + } + } + // Force static initializers to run. + Class.forName(className, true, loader); + return c; + } + // SPRING PATCH END + + public static int findPackageProtected(Class[] classes) { + for (int i = 0; i < classes.length; i++) { + if (!Modifier.isPublic(classes[i].getModifiers())) { + return i; + } + } + return 0; + } + + public static MethodInfo getMethodInfo(final Member member, final int modifiers) { + final Signature sig = getSignature(member); + return new MethodInfo() { + private ClassInfo ci; + + public ClassInfo getClassInfo() { + if (ci == null) + ci = ReflectUtils.getClassInfo(member.getDeclaringClass()); + return ci; + } + + public int getModifiers() { + return modifiers; + } + + public Signature getSignature() { + return sig; + } + + public Type[] getExceptionTypes() { + return ReflectUtils.getExceptionTypes(member); + } + + public Attribute getAttribute() { + return null; + } + }; + } + + public static MethodInfo getMethodInfo(Member member) { + return getMethodInfo(member, member.getModifiers()); + } + + public static ClassInfo getClassInfo(final Class clazz) { + final Type type = Type.getType(clazz); + final Type sc = (clazz.getSuperclass() == null) ? null : Type.getType(clazz.getSuperclass()); + return new ClassInfo() { + public Type getType() { + return type; + } + public Type getSuperType() { + return sc; + } + public Type[] getInterfaces() { + return TypeUtils.getTypes(clazz.getInterfaces()); + } + public int getModifiers() { + return clazz.getModifiers(); + } + }; + } + + // used by MethodInterceptorGenerated generated code + public static Method[] findMethods(String[] namesAndDescriptors, Method[] methods) { + Map map = new HashMap(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + map.put(method.getName() + Type.getMethodDescriptor(method), method); + } + Method[] result = new Method[namesAndDescriptors.length / 2]; + for (int i = 0; i < result.length; i++) { + result[i] = (Method) map.get(namesAndDescriptors[i * 2] + namesAndDescriptors[i * 2 + 1]); + if (result[i] == null) { + // TODO: error? + } + } + return result; + } + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/core/package-info.java b/spring-core/src/main/java/org/springframework/cglib/core/package-info.java new file mode 100644 index 0000000000..a2ed94ff2e --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/core/package-info.java @@ -0,0 +1,10 @@ +/** + * Spring's repackaging of the + * CGLIB core package + * (for internal use only). + * + *

As this repackaging happens at the class file level, sources + * and javadocs are not available here... except for a few files + * that have been patched for Spring's purposes on JDK 9/10/11. + */ +package org.springframework.cglib.core; diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java new file mode 100644 index 0000000000..98fe3a99db --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java @@ -0,0 +1,1435 @@ +/* + * Copyright 2002,2003,2004 The Apache Software Foundation + * + * 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.cglib.proxy; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Label; +import org.springframework.asm.Type; +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.ClassEmitter; +import org.springframework.cglib.core.CodeEmitter; +import org.springframework.cglib.core.CodeGenerationException; +import org.springframework.cglib.core.CollectionUtils; +import org.springframework.cglib.core.Constants; +import org.springframework.cglib.core.DuplicatesPredicate; +import org.springframework.cglib.core.EmitUtils; +import org.springframework.cglib.core.KeyFactory; +import org.springframework.cglib.core.Local; +import org.springframework.cglib.core.MethodInfo; +import org.springframework.cglib.core.MethodInfoTransformer; +import org.springframework.cglib.core.MethodWrapper; +import org.springframework.cglib.core.ObjectSwitchCallback; +import org.springframework.cglib.core.ProcessSwitchCallback; +import org.springframework.cglib.core.ReflectUtils; +import org.springframework.cglib.core.RejectModifierPredicate; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.core.Transformer; +import org.springframework.cglib.core.TypeUtils; +import org.springframework.cglib.core.VisibilityPredicate; +import org.springframework.cglib.core.WeakCacheKey; + +/** + * Generates dynamic subclasses to enable method interception. This + * class started as a substitute for the standard Dynamic Proxy support + * included with JDK 1.3, but one that allowed the proxies to extend a + * concrete base class, in addition to implementing interfaces. The dynamically + * generated subclasses override the non-final methods of the superclass and + * have hooks which callback to user-defined interceptor + * implementations. + *

+ * The original and most general callback type is the {@link MethodInterceptor}, which + * in AOP terms enables "around advice"--that is, you can invoke custom code both before + * and after the invocation of the "super" method. In addition you can modify the + * arguments before calling the super method, or not call it at all. + *

+ * Although MethodInterceptor is generic enough to meet any + * interception need, it is often overkill. For simplicity and performance, additional + * specialized callback types, such as {@link LazyLoader} are also available. + * Often a single callback will be used per enhanced class, but you can control + * which callback is used on a per-method basis with a {@link CallbackFilter}. + *

+ * The most common uses of this class are embodied in the static helper methods. For + * advanced needs, such as customizing the ClassLoader to use, you should create + * a new instance of Enhancer. Other classes within CGLIB follow a similar pattern. + *

+ * All enhanced objects implement the {@link Factory} interface, unless {@link #setUseFactory} is + * used to explicitly disable this feature. The Factory interface provides an API + * to change the callbacks of an existing object, as well as a faster and easier way to create + * new instances of the same type. + *

+ * For an almost drop-in replacement for + * java.lang.reflect.Proxy, see the {@link Proxy} class. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class Enhancer extends AbstractClassGenerator { + + private static final CallbackFilter ALL_ZERO = new CallbackFilter() { + public int accept(Method method) { + return 0; + } + }; + + private static final Source SOURCE = new Source(Enhancer.class.getName()); + + private static final EnhancerKey KEY_FACTORY = + (EnhancerKey) KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null); + + private static final String BOUND_FIELD = "CGLIB$BOUND"; + + private static final String FACTORY_DATA_FIELD = "CGLIB$FACTORY_DATA"; + + private static final String THREAD_CALLBACKS_FIELD = "CGLIB$THREAD_CALLBACKS"; + + private static final String STATIC_CALLBACKS_FIELD = "CGLIB$STATIC_CALLBACKS"; + + private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS"; + + private static final String SET_STATIC_CALLBACKS_NAME = "CGLIB$SET_STATIC_CALLBACKS"; + + private static final String CONSTRUCTED_FIELD = "CGLIB$CONSTRUCTED"; + + /** + * {@link org.springframework.cglib.core.AbstractClassGenerator.ClassLoaderData#generatedClasses} requires to keep cache key + * in a good shape (the keys should be up and running if the proxy class is alive), and one of the cache keys is + * {@link CallbackFilter}. That is why the generated class contains static field that keeps strong reference to + * the {@link #filter}. + *

This dance achieves two goals: ensures generated class is reusable and available through generatedClasses + * cache, and it enables to unload classloader and the related {@link CallbackFilter} in case user does not need + * that

+ */ + private static final String CALLBACK_FILTER_FIELD = "CGLIB$CALLBACK_FILTER"; + + private static final Type OBJECT_TYPE = + TypeUtils.parseType("Object"); + + private static final Type FACTORY = + TypeUtils.parseType("org.springframework.cglib.proxy.Factory"); + + private static final Type ILLEGAL_STATE_EXCEPTION = + TypeUtils.parseType("IllegalStateException"); + + private static final Type ILLEGAL_ARGUMENT_EXCEPTION = + TypeUtils.parseType("IllegalArgumentException"); + + private static final Type THREAD_LOCAL = + TypeUtils.parseType("ThreadLocal"); + + private static final Type CALLBACK = + TypeUtils.parseType("org.springframework.cglib.proxy.Callback"); + + private static final Type CALLBACK_ARRAY = + Type.getType(Callback[].class); + + private static final Signature CSTRUCT_NULL = + TypeUtils.parseConstructor(""); + + private static final Signature SET_THREAD_CALLBACKS = + new Signature(SET_THREAD_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{CALLBACK_ARRAY}); + + private static final Signature SET_STATIC_CALLBACKS = + new Signature(SET_STATIC_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{CALLBACK_ARRAY}); + + private static final Signature NEW_INSTANCE = + new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{CALLBACK_ARRAY}); + + private static final Signature MULTIARG_NEW_INSTANCE = + new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ + Constants.TYPE_CLASS_ARRAY, + Constants.TYPE_OBJECT_ARRAY, + CALLBACK_ARRAY, + }); + + private static final Signature SINGLE_NEW_INSTANCE = + new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{CALLBACK}); + + private static final Signature SET_CALLBACK = + new Signature("setCallback", Type.VOID_TYPE, new Type[]{Type.INT_TYPE, CALLBACK}); + + private static final Signature GET_CALLBACK = + new Signature("getCallback", CALLBACK, new Type[]{Type.INT_TYPE}); + + private static final Signature SET_CALLBACKS = + new Signature("setCallbacks", Type.VOID_TYPE, new Type[]{CALLBACK_ARRAY}); + + private static final Signature GET_CALLBACKS = + new Signature("getCallbacks", CALLBACK_ARRAY, new Type[0]); + + private static final Signature THREAD_LOCAL_GET = + TypeUtils.parseSignature("Object get()"); + + private static final Signature THREAD_LOCAL_SET = + TypeUtils.parseSignature("void set(Object)"); + + private static final Signature BIND_CALLBACKS = + TypeUtils.parseSignature("void CGLIB$BIND_CALLBACKS(Object)"); + + private EnhancerFactoryData currentData; + + private Object currentKey; + + + /** + * Internal interface, only public due to ClassLoader issues. + */ + public interface EnhancerKey { + + public Object newInstance(String type, + String[] interfaces, + WeakCacheKey filter, + Type[] callbackTypes, + boolean useFactory, + boolean interceptDuringConstruction, + Long serialVersionUID); + } + + + private Class[] interfaces; + + private CallbackFilter filter; + + private Callback[] callbacks; + + private Type[] callbackTypes; + + private boolean validateCallbackTypes; + + private boolean classOnly; + + private Class superclass; + + private Class[] argumentTypes; + + private Object[] arguments; + + private boolean useFactory = true; + + private Long serialVersionUID; + + private boolean interceptDuringConstruction = true; + + /** + * Create a new Enhancer. A new Enhancer + * object should be used for each generated object, and should not + * be shared across threads. To create additional instances of a + * generated class, use the Factory interface. + * @see Factory + */ + public Enhancer() { + super(SOURCE); + } + + /** + * Set the class which the generated class will extend. As a convenience, + * if the supplied superclass is actually an interface, setInterfaces + * will be called with the appropriate argument instead. + * A non-interface argument must not be declared as final, and must have an + * accessible constructor. + * @param superclass class to extend or interface to implement + * @see #setInterfaces(Class[]) + */ + public void setSuperclass(Class superclass) { + if (superclass != null && superclass.isInterface()) { + setInterfaces(new Class[]{superclass}); + } + else if (superclass != null && superclass.equals(Object.class)) { + // affects choice of ClassLoader + this.superclass = null; + } + else { + this.superclass = superclass; + // SPRING PATCH BEGIN + setContextClass(superclass); + // SPRING PATCH END + } + } + + /** + * Set the interfaces to implement. The Factory interface will + * always be implemented regardless of what is specified here. + * @param interfaces array of interfaces to implement, or null + * @see Factory + */ + public void setInterfaces(Class[] interfaces) { + this.interfaces = interfaces; + } + + /** + * Set the {@link CallbackFilter} used to map the generated class' methods + * to a particular callback index. + * New object instances will always use the same mapping, but may use different + * actual callback objects. + * @param filter the callback filter to use when generating a new class + * @see #setCallbacks + */ + public void setCallbackFilter(CallbackFilter filter) { + this.filter = filter; + } + + + /** + * Set the single {@link Callback} to use. + * Ignored if you use {@link #createClass}. + * @param callback the callback to use for all methods + * @see #setCallbacks + */ + public void setCallback(final Callback callback) { + setCallbacks(new Callback[]{callback}); + } + + /** + * Set the array of callbacks to use. + * Ignored if you use {@link #createClass}. + * You must use a {@link CallbackFilter} to specify the index into this + * array for each method in the proxied class. + * @param callbacks the callback array + * @see #setCallbackFilter + * @see #setCallback + */ + public void setCallbacks(Callback[] callbacks) { + if (callbacks != null && callbacks.length == 0) { + throw new IllegalArgumentException("Array cannot be empty"); + } + this.callbacks = callbacks; + } + + /** + * Set whether the enhanced object instances should implement + * the {@link Factory} interface. + * This was added for tools that need for proxies to be more + * indistinguishable from their targets. Also, in some cases it may + * be necessary to disable the Factory interface to + * prevent code from changing the underlying callbacks. + * @param useFactory whether to implement Factory; default is true + */ + public void setUseFactory(boolean useFactory) { + this.useFactory = useFactory; + } + + /** + * Set whether methods called from within the proxy's constructer + * will be intercepted. The default value is true. Unintercepted methods + * will call the method of the proxy's base class, if it exists. + * @param interceptDuringConstruction whether to intercept methods called from the constructor + */ + public void setInterceptDuringConstruction(boolean interceptDuringConstruction) { + this.interceptDuringConstruction = interceptDuringConstruction; + } + + /** + * Set the single type of {@link Callback} to use. + * This may be used instead of {@link #setCallback} when calling + * {@link #createClass}, since it may not be possible to have + * an array of actual callback instances. + * @param callbackType the type of callback to use for all methods + * @see #setCallbackTypes + */ + public void setCallbackType(Class callbackType) { + setCallbackTypes(new Class[]{callbackType}); + } + + /** + * Set the array of callback types to use. + * This may be used instead of {@link #setCallbacks} when calling + * {@link #createClass}, since it may not be possible to have + * an array of actual callback instances. + * You must use a {@link CallbackFilter} to specify the index into this + * array for each method in the proxied class. + * @param callbackTypes the array of callback types + */ + public void setCallbackTypes(Class[] callbackTypes) { + if (callbackTypes != null && callbackTypes.length == 0) { + throw new IllegalArgumentException("Array cannot be empty"); + } + this.callbackTypes = CallbackInfo.determineTypes(callbackTypes); + } + + /** + * Generate a new class if necessary and uses the specified + * callbacks (if any) to create a new object instance. + * Uses the no-arg constructor of the superclass. + * @return a new instance + */ + public Object create() { + classOnly = false; + argumentTypes = null; + return createHelper(); + } + + /** + * Generate a new class if necessary and uses the specified + * callbacks (if any) to create a new object instance. + * Uses the constructor of the superclass matching the argumentTypes + * parameter, with the given arguments. + * @param argumentTypes constructor signature + * @param arguments compatible wrapped arguments to pass to constructor + * @return a new instance + */ + public Object create(Class[] argumentTypes, Object[] arguments) { + classOnly = false; + if (argumentTypes == null || arguments == null || argumentTypes.length != arguments.length) { + throw new IllegalArgumentException("Arguments must be non-null and of equal length"); + } + this.argumentTypes = argumentTypes; + this.arguments = arguments; + return createHelper(); + } + + /** + * Generate a new class if necessary and return it without creating a new instance. + * This ignores any callbacks that have been set. + * To create a new instance you will have to use reflection, and methods + * called during the constructor will not be intercepted. To avoid this problem, + * use the multi-arg create method. + * @see #create(Class[], Object[]) + */ + public Class createClass() { + classOnly = true; + return (Class) createHelper(); + } + + /** + * Insert a static serialVersionUID field into the generated class. + * @param sUID the field value, or null to avoid generating field. + */ + public void setSerialVersionUID(Long sUID) { + serialVersionUID = sUID; + } + + private void preValidate() { + if (callbackTypes == null) { + callbackTypes = CallbackInfo.determineTypes(callbacks, false); + validateCallbackTypes = true; + } + if (filter == null) { + if (callbackTypes.length > 1) { + throw new IllegalStateException("Multiple callback types possible but no filter specified"); + } + filter = ALL_ZERO; + } + } + + private void validate() { + if (classOnly ^ (callbacks == null)) { + if (classOnly) { + throw new IllegalStateException("createClass does not accept callbacks"); + } + else { + throw new IllegalStateException("Callbacks are required"); + } + } + if (classOnly && (callbackTypes == null)) { + throw new IllegalStateException("Callback types are required"); + } + if (validateCallbackTypes) { + callbackTypes = null; + } + if (callbacks != null && callbackTypes != null) { + if (callbacks.length != callbackTypes.length) { + throw new IllegalStateException("Lengths of callback and callback types array must be the same"); + } + Type[] check = CallbackInfo.determineTypes(callbacks); + for (int i = 0; i < check.length; i++) { + if (!check[i].equals(callbackTypes[i])) { + throw new IllegalStateException("Callback " + check[i] + " is not assignable to " + callbackTypes[i]); + } + } + } + else if (callbacks != null) { + callbackTypes = CallbackInfo.determineTypes(callbacks); + } + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + if (interfaces[i] == null) { + throw new IllegalStateException("Interfaces cannot be null"); + } + if (!interfaces[i].isInterface()) { + throw new IllegalStateException(interfaces[i] + " is not an interface"); + } + } + } + } + + /** + * The idea of the class is to cache relevant java.lang.reflect instances so + * proxy-class can be instantiated faster that when using {@link ReflectUtils#newInstance(Class, Class[], Object[])} + * and {@link Enhancer#setThreadCallbacks(Class, Callback[])} + */ + static class EnhancerFactoryData { + + public final Class generatedClass; + + private final Method setThreadCallbacks; + + private final Class[] primaryConstructorArgTypes; + + private final Constructor primaryConstructor; + + public EnhancerFactoryData(Class generatedClass, Class[] primaryConstructorArgTypes, boolean classOnly) { + this.generatedClass = generatedClass; + try { + setThreadCallbacks = getCallbacksSetter(generatedClass, SET_THREAD_CALLBACKS_NAME); + if (classOnly) { + this.primaryConstructorArgTypes = null; + this.primaryConstructor = null; + } + else { + this.primaryConstructorArgTypes = primaryConstructorArgTypes; + this.primaryConstructor = ReflectUtils.getConstructor(generatedClass, primaryConstructorArgTypes); + } + } + catch (NoSuchMethodException e) { + throw new CodeGenerationException(e); + } + } + + /** + * Creates proxy instance for given argument types, and assigns the callbacks. + * Ideally, for each proxy class, just one set of argument types should be used, + * otherwise it would have to spend time on constructor lookup. + * Technically, it is a re-implementation of {@link Enhancer#createUsingReflection(Class)}, + * with "cache {@link #setThreadCallbacks} and {@link #primaryConstructor}" + * @param argumentTypes constructor argument types + * @param arguments constructor arguments + * @param callbacks callbacks to set for the new instance + * @return newly created proxy + * @see #createUsingReflection(Class) + */ + public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) { + setThreadCallbacks(callbacks); + try { + // Explicit reference equality is added here just in case Arrays.equals does not have one + if (primaryConstructorArgTypes == argumentTypes || + Arrays.equals(primaryConstructorArgTypes, argumentTypes)) { + // If we have relevant Constructor instance at hand, just call it + // This skips "get constructors" machinery + return ReflectUtils.newInstance(primaryConstructor, arguments); + } + // Take a slow path if observing unexpected argument types + return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments); + } + finally { + // clear thread callbacks to allow them to be gc'd + setThreadCallbacks(null); + } + + } + + private void setThreadCallbacks(Callback[] callbacks) { + try { + setThreadCallbacks.invoke(generatedClass, (Object) callbacks); + } + catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } + catch (InvocationTargetException e) { + throw new CodeGenerationException(e.getTargetException()); + } + } + } + + private Object createHelper() { + preValidate(); + Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null, + ReflectUtils.getNames(interfaces), + filter == ALL_ZERO ? null : new WeakCacheKey(filter), + callbackTypes, + useFactory, + interceptDuringConstruction, + serialVersionUID); + this.currentKey = key; + Object result = super.create(key); + return result; + } + + @Override + protected Class generate(ClassLoaderData data) { + validate(); + if (superclass != null) { + setNamePrefix(superclass.getName()); + } + else if (interfaces != null) { + setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName()); + } + return super.generate(data); + } + + protected ClassLoader getDefaultClassLoader() { + if (superclass != null) { + return superclass.getClassLoader(); + } + else if (interfaces != null) { + return interfaces[0].getClassLoader(); + } + else { + return null; + } + } + + protected ProtectionDomain getProtectionDomain() { + if (superclass != null) { + return ReflectUtils.getProtectionDomain(superclass); + } + else if (interfaces != null) { + return ReflectUtils.getProtectionDomain(interfaces[0]); + } + else { + return null; + } + } + + private Signature rename(Signature sig, int index) { + return new Signature("CGLIB$" + sig.getName() + "$" + index, + sig.getDescriptor()); + } + + /** + * Finds all of the methods that will be extended by an + * Enhancer-generated class using the specified superclass and + * interfaces. This can be useful in building a list of Callback + * objects. The methods are added to the end of the given list. Due + * to the subclassing nature of the classes generated by Enhancer, + * the methods are guaranteed to be non-static, non-final, and + * non-private. Each method signature will only occur once, even if + * it occurs in multiple classes. + * @param superclass the class that will be extended, or null + * @param interfaces the list of interfaces that will be implemented, or null + * @param methods the list into which to copy the applicable methods + */ + public static void getMethods(Class superclass, Class[] interfaces, List methods) { + getMethods(superclass, interfaces, methods, null, null); + } + + private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic) { + ReflectUtils.addAllMethods(superclass, methods); + List target = (interfaceMethods != null) ? interfaceMethods : methods; + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + if (interfaces[i] != Factory.class) { + ReflectUtils.addAllMethods(interfaces[i], target); + } + } + } + if (interfaceMethods != null) { + if (forcePublic != null) { + forcePublic.addAll(MethodWrapper.createSet(interfaceMethods)); + } + methods.addAll(interfaceMethods); + } + CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC)); + CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true)); + CollectionUtils.filter(methods, new DuplicatesPredicate()); + CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL)); + } + + public void generateClass(ClassVisitor v) throws Exception { + Class sc = (superclass == null) ? Object.class : superclass; + + if (TypeUtils.isFinal(sc.getModifiers())) + throw new IllegalArgumentException("Cannot subclass final class " + sc.getName()); + List constructors = new ArrayList(Arrays.asList(sc.getDeclaredConstructors())); + filterConstructors(sc, constructors); + + // Order is very important: must add superclass, then + // its superclass chain, then each interface and + // its superinterfaces. + List actualMethods = new ArrayList(); + List interfaceMethods = new ArrayList(); + final Set forcePublic = new HashSet(); + getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic); + + List methods = CollectionUtils.transform(actualMethods, new Transformer() { + public Object transform(Object value) { + Method method = (Method) value; + int modifiers = Constants.ACC_FINAL + | (method.getModifiers() + & ~Constants.ACC_ABSTRACT + & ~Constants.ACC_NATIVE + & ~Constants.ACC_SYNCHRONIZED); + if (forcePublic.contains(MethodWrapper.create(method))) { + modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC; + } + return ReflectUtils.getMethodInfo(method, modifiers); + } + }); + + ClassEmitter e = new ClassEmitter(v); + if (currentData == null) { + e.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + Type.getType(sc), + (useFactory ? + TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) : + TypeUtils.getTypes(interfaces)), + Constants.SOURCE_FILE); + } + else { + e.begin_class(Constants.V1_2, + Constants.ACC_PUBLIC, + getClassName(), + null, + new Type[]{FACTORY}, + Constants.SOURCE_FILE); + } + List constructorInfo = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance()); + + e.declare_field(Constants.ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null); + e.declare_field(Constants.ACC_PUBLIC | Constants.ACC_STATIC, FACTORY_DATA_FIELD, OBJECT_TYPE, null); + if (!interceptDuringConstruction) { + e.declare_field(Constants.ACC_PRIVATE, CONSTRUCTED_FIELD, Type.BOOLEAN_TYPE, null); + } + e.declare_field(Constants.PRIVATE_FINAL_STATIC, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null); + e.declare_field(Constants.PRIVATE_FINAL_STATIC, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null); + if (serialVersionUID != null) { + e.declare_field(Constants.PRIVATE_FINAL_STATIC, Constants.SUID_FIELD_NAME, Type.LONG_TYPE, serialVersionUID); + } + + for (int i = 0; i < callbackTypes.length; i++) { + e.declare_field(Constants.ACC_PRIVATE, getCallbackField(i), callbackTypes[i], null); + } + // This is declared private to avoid "public field" pollution + e.declare_field(Constants.ACC_PRIVATE | Constants.ACC_STATIC, CALLBACK_FILTER_FIELD, OBJECT_TYPE, null); + + if (currentData == null) { + emitMethods(e, methods, actualMethods); + emitConstructors(e, constructorInfo); + } + else { + emitDefaultConstructor(e); + } + emitSetThreadCallbacks(e); + emitSetStaticCallbacks(e); + emitBindCallbacks(e); + + if (useFactory || currentData != null) { + int[] keys = getCallbackKeys(); + emitNewInstanceCallbacks(e); + emitNewInstanceCallback(e); + emitNewInstanceMultiarg(e, constructorInfo); + emitGetCallback(e, keys); + emitSetCallback(e, keys); + emitGetCallbacks(e); + emitSetCallbacks(e); + } + + e.end_class(); + } + + /** + * Filter the list of constructors from the superclass. The + * constructors which remain will be included in the generated + * class. The default implementation is to filter out all private + * constructors, but subclasses may extend Enhancer to override this + * behavior. + * @param sc the superclass + * @param constructors the list of all declared constructors from the superclass + * @throws IllegalArgumentException if there are no non-private constructors + */ + protected void filterConstructors(Class sc, List constructors) { + CollectionUtils.filter(constructors, new VisibilityPredicate(sc, true)); + if (constructors.size() == 0) + throw new IllegalArgumentException("No visible constructors in " + sc); + } + + /** + * This method should not be called in regular flow. + * Technically speaking {@link #wrapCachedClass(Class)} uses {@link EnhancerFactoryData} as a cache value, + * and the latter enables faster instantiation than plain old reflection lookup and invoke. + * This method is left intact for backward compatibility reasons: just in case it was ever used. + * @param type class to instantiate + * @return newly created proxy instance + * @throws Exception if something goes wrong + */ + protected Object firstInstance(Class type) throws Exception { + if (classOnly) { + return type; + } + else { + return createUsingReflection(type); + } + } + + protected Object nextInstance(Object instance) { + EnhancerFactoryData data = (EnhancerFactoryData) instance; + + if (classOnly) { + return data.generatedClass; + } + + Class[] argumentTypes = this.argumentTypes; + Object[] arguments = this.arguments; + if (argumentTypes == null) { + argumentTypes = Constants.EMPTY_CLASS_ARRAY; + arguments = null; + } + return data.newInstance(argumentTypes, arguments, callbacks); + } + + @Override + protected Object wrapCachedClass(Class klass) { + Class[] argumentTypes = this.argumentTypes; + if (argumentTypes == null) { + argumentTypes = Constants.EMPTY_CLASS_ARRAY; + } + EnhancerFactoryData factoryData = new EnhancerFactoryData(klass, argumentTypes, classOnly); + Field factoryDataField = null; + try { + // The subsequent dance is performed just once for each class, + // so it does not matter much how fast it goes + factoryDataField = klass.getField(FACTORY_DATA_FIELD); + factoryDataField.set(null, factoryData); + Field callbackFilterField = klass.getDeclaredField(CALLBACK_FILTER_FIELD); + callbackFilterField.setAccessible(true); + callbackFilterField.set(null, this.filter); + } + catch (NoSuchFieldException e) { + throw new CodeGenerationException(e); + } + catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } + return new WeakReference(factoryData); + } + + @Override + protected Object unwrapCachedValue(Object cached) { + if (currentKey instanceof EnhancerKey) { + EnhancerFactoryData data = ((WeakReference) cached).get(); + return data; + } + return super.unwrapCachedValue(cached); + } + + /** + * Call this method to register the {@link Callback} array to use before + * creating a new instance of the generated class via reflection. If you are using + * an instance of Enhancer or the {@link Factory} interface to create + * new instances, this method is unnecessary. Its primary use is for when you want to + * cache and reuse a generated class yourself, and the generated class does + * not implement the {@link Factory} interface. + *

+ * Note that this method only registers the callbacks on the current thread. + * If you want to register callbacks for instances created by multiple threads, + * use {@link #registerStaticCallbacks}. + *

+ * The registered callbacks are overwritten and subsequently cleared + * when calling any of the create methods (such as + * {@link #create}), or any {@link Factory} newInstance method. + * Otherwise they are not cleared, and you should be careful to set them + * back to null after creating new instances via reflection if + * memory leakage is a concern. + * @param generatedClass a class previously created by {@link Enhancer} + * @param callbacks the array of callbacks to use when instances of the generated + * class are created + * @see #setUseFactory + */ + public static void registerCallbacks(Class generatedClass, Callback[] callbacks) { + setThreadCallbacks(generatedClass, callbacks); + } + + /** + * Similar to {@link #registerCallbacks}, but suitable for use + * when multiple threads will be creating instances of the generated class. + * The thread-level callbacks will always override the static callbacks. + * Static callbacks are never cleared. + * @param generatedClass a class previously created by {@link Enhancer} + * @param callbacks the array of callbacks to use when instances of the generated + * class are created + */ + public static void registerStaticCallbacks(Class generatedClass, Callback[] callbacks) { + setCallbacksHelper(generatedClass, callbacks, SET_STATIC_CALLBACKS_NAME); + } + + /** + * Determine if a class was generated using Enhancer. + * @param type any class + * @return whether the class was generated using Enhancer + */ + public static boolean isEnhanced(Class type) { + try { + getCallbacksSetter(type, SET_THREAD_CALLBACKS_NAME); + return true; + } + catch (NoSuchMethodException e) { + return false; + } + } + + private static void setThreadCallbacks(Class type, Callback[] callbacks) { + setCallbacksHelper(type, callbacks, SET_THREAD_CALLBACKS_NAME); + } + + private static void setCallbacksHelper(Class type, Callback[] callbacks, String methodName) { + // TODO: optimize + try { + Method setter = getCallbacksSetter(type, methodName); + setter.invoke(null, new Object[]{callbacks}); + } + catch (NoSuchMethodException e) { + throw new IllegalArgumentException(type + " is not an enhanced class"); + } + catch (IllegalAccessException e) { + throw new CodeGenerationException(e); + } + catch (InvocationTargetException e) { + throw new CodeGenerationException(e); + } + } + + private static Method getCallbacksSetter(Class type, String methodName) throws NoSuchMethodException { + return type.getDeclaredMethod(methodName, new Class[]{Callback[].class}); + } + + /** + * Instantiates a proxy instance and assigns callback values. + * Implementation detail: java.lang.reflect instances are not cached, so this method should not + * be used on a hot path. + * This method is used when {@link #setUseCache(boolean)} is set to {@code false}. + * @param type class to instantiate + * @return newly created instance + */ + private Object createUsingReflection(Class type) { + setThreadCallbacks(type, callbacks); + try { + + if (argumentTypes != null) { + + return ReflectUtils.newInstance(type, argumentTypes, arguments); + + } + else { + + return ReflectUtils.newInstance(type); + + } + } + finally { + // clear thread callbacks to allow them to be gc'd + setThreadCallbacks(type, null); + } + } + + /** + * Helper method to create an intercepted object. + * For finer control over the generated instance, use a new instance of Enhancer + * instead of this static method. + * @param type class to extend or interface to implement + * @param callback the callback to use for all methods + */ + public static Object create(Class type, Callback callback) { + Enhancer e = new Enhancer(); + e.setSuperclass(type); + e.setCallback(callback); + return e.create(); + } + + /** + * Helper method to create an intercepted object. + * For finer control over the generated instance, use a new instance of Enhancer + * instead of this static method. + * @param superclass class to extend or interface to implement + * @param interfaces array of interfaces to implement, or null + * @param callback the callback to use for all methods + */ + public static Object create(Class superclass, Class interfaces[], Callback callback) { + Enhancer e = new Enhancer(); + e.setSuperclass(superclass); + e.setInterfaces(interfaces); + e.setCallback(callback); + return e.create(); + } + + /** + * Helper method to create an intercepted object. + * For finer control over the generated instance, use a new instance of Enhancer + * instead of this static method. + * @param superclass class to extend or interface to implement + * @param interfaces array of interfaces to implement, or null + * @param filter the callback filter to use when generating a new class + * @param callbacks callback implementations to use for the enhanced object + */ + public static Object create(Class superclass, Class[] interfaces, CallbackFilter filter, Callback[] callbacks) { + Enhancer e = new Enhancer(); + e.setSuperclass(superclass); + e.setInterfaces(interfaces); + e.setCallbackFilter(filter); + e.setCallbacks(callbacks); + return e.create(); + } + + private void emitDefaultConstructor(ClassEmitter ce) { + Constructor declaredConstructor; + try { + declaredConstructor = Object.class.getDeclaredConstructor(); + } + catch (NoSuchMethodException e) { + throw new IllegalStateException("Object should have default constructor ", e); + } + MethodInfo constructor = (MethodInfo) MethodInfoTransformer.getInstance().transform(declaredConstructor); + CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC); + e.load_this(); + e.dup(); + Signature sig = constructor.getSignature(); + e.super_invoke_constructor(sig); + e.return_value(); + e.end_method(); + } + + private void emitConstructors(ClassEmitter ce, List constructors) { + boolean seenNull = false; + for (Iterator it = constructors.iterator(); it.hasNext(); ) { + MethodInfo constructor = (MethodInfo) it.next(); + if (currentData != null && !"()V".equals(constructor.getSignature().getDescriptor())) { + continue; + } + CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC); + e.load_this(); + e.dup(); + e.load_args(); + Signature sig = constructor.getSignature(); + seenNull = seenNull || sig.getDescriptor().equals("()V"); + e.super_invoke_constructor(sig); + if (currentData == null) { + e.invoke_static_this(BIND_CALLBACKS); + if (!interceptDuringConstruction) { + e.load_this(); + e.push(1); + e.putfield(CONSTRUCTED_FIELD); + } + } + e.return_value(); + e.end_method(); + } + if (!classOnly && !seenNull && arguments == null) + throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given"); + } + + private int[] getCallbackKeys() { + int[] keys = new int[callbackTypes.length]; + for (int i = 0; i < callbackTypes.length; i++) { + keys[i] = i; + } + return keys; + } + + private void emitGetCallback(ClassEmitter ce, int[] keys) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACK, null); + e.load_this(); + e.invoke_static_this(BIND_CALLBACKS); + e.load_this(); + e.load_arg(0); + e.process_switch(keys, new ProcessSwitchCallback() { + public void processCase(int key, Label end) { + e.getfield(getCallbackField(key)); + e.goTo(end); + } + + public void processDefault() { + e.pop(); // stack height + e.aconst_null(); + } + }); + e.return_value(); + e.end_method(); + } + + private void emitSetCallback(ClassEmitter ce, int[] keys) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACK, null); + e.load_arg(0); + e.process_switch(keys, new ProcessSwitchCallback() { + public void processCase(int key, Label end) { + e.load_this(); + e.load_arg(1); + e.checkcast(callbackTypes[key]); + e.putfield(getCallbackField(key)); + e.goTo(end); + } + + public void processDefault() { + // TODO: error? + } + }); + e.return_value(); + e.end_method(); + } + + private void emitSetCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACKS, null); + e.load_this(); + e.load_arg(0); + for (int i = 0; i < callbackTypes.length; i++) { + e.dup2(); + e.aaload(i); + e.checkcast(callbackTypes[i]); + e.putfield(getCallbackField(i)); + } + e.return_value(); + e.end_method(); + } + + private void emitGetCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACKS, null); + e.load_this(); + e.invoke_static_this(BIND_CALLBACKS); + e.load_this(); + e.push(callbackTypes.length); + e.newarray(CALLBACK); + for (int i = 0; i < callbackTypes.length; i++) { + e.dup(); + e.push(i); + e.load_this(); + e.getfield(getCallbackField(i)); + e.aastore(); + } + e.return_value(); + e.end_method(); + } + + private void emitNewInstanceCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null); + Type thisType = getThisType(e); + e.load_arg(0); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + emitCommonNewInstance(e); + } + + private Type getThisType(CodeEmitter e) { + if (currentData == null) { + return e.getClassEmitter().getClassType(); + } + else { + return Type.getType(currentData.generatedClass); + } + } + + private void emitCommonNewInstance(CodeEmitter e) { + Type thisType = getThisType(e); + e.new_instance(thisType); + e.dup(); + e.invoke_constructor(thisType); + e.aconst_null(); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + e.return_value(); + e.end_method(); + } + + private void emitNewInstanceCallback(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SINGLE_NEW_INSTANCE, null); + switch (callbackTypes.length) { + case 0: + // TODO: make sure Callback is null + break; + case 1: + // for now just make a new array; TODO: optimize + e.push(1); + e.newarray(CALLBACK); + e.dup(); + e.push(0); + e.load_arg(0); + e.aastore(); + e.invoke_static(getThisType(e), SET_THREAD_CALLBACKS); + break; + default: + e.throw_exception(ILLEGAL_STATE_EXCEPTION, "More than one callback object required"); + } + emitCommonNewInstance(e); + } + + private void emitNewInstanceMultiarg(ClassEmitter ce, List constructors) { + final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, MULTIARG_NEW_INSTANCE, null); + final Type thisType = getThisType(e); + e.load_arg(2); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + e.new_instance(thisType); + e.dup(); + e.load_arg(0); + EmitUtils.constructor_switch(e, constructors, new ObjectSwitchCallback() { + public void processCase(Object key, Label end) { + MethodInfo constructor = (MethodInfo) key; + Type types[] = constructor.getSignature().getArgumentTypes(); + for (int i = 0; i < types.length; i++) { + e.load_arg(1); + e.push(i); + e.aaload(); + e.unbox(types[i]); + } + e.invoke_constructor(thisType, constructor.getSignature()); + e.goTo(end); + } + + public void processDefault() { + e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Constructor not found"); + } + }); + e.aconst_null(); + e.invoke_static(thisType, SET_THREAD_CALLBACKS); + e.return_value(); + e.end_method(); + } + + private void emitMethods(final ClassEmitter ce, List methods, List actualMethods) { + CallbackGenerator[] generators = CallbackInfo.getGenerators(callbackTypes); + + Map groups = new HashMap(); + final Map indexes = new HashMap(); + final Map originalModifiers = new HashMap(); + final Map positions = CollectionUtils.getIndexMap(methods); + final Map declToBridge = new HashMap(); + + Iterator it1 = methods.iterator(); + Iterator it2 = (actualMethods != null) ? actualMethods.iterator() : null; + + while (it1.hasNext()) { + MethodInfo method = (MethodInfo) it1.next(); + Method actualMethod = (it2 != null) ? (Method) it2.next() : null; + int index = filter.accept(actualMethod); + if (index >= callbackTypes.length) { + throw new IllegalArgumentException("Callback filter returned an index that is too large: " + index); + } + originalModifiers.put(method, (actualMethod != null ? actualMethod.getModifiers() : method.getModifiers())); + indexes.put(method, index); + List group = (List) groups.get(generators[index]); + if (group == null) { + groups.put(generators[index], group = new ArrayList(methods.size())); + } + group.add(method); + + // Optimization: build up a map of Class -> bridge methods in class + // so that we can look up all the bridge methods in one pass for a class. + if (TypeUtils.isBridge(actualMethod.getModifiers())) { + Set bridges = (Set) declToBridge.get(actualMethod.getDeclaringClass()); + if (bridges == null) { + bridges = new HashSet(); + declToBridge.put(actualMethod.getDeclaringClass(), bridges); + } + bridges.add(method.getSignature()); + } + } + + final Map bridgeToTarget = new BridgeMethodResolver(declToBridge, getClassLoader()).resolveAll(); + + Set seenGen = new HashSet(); + CodeEmitter se = ce.getStaticHook(); + se.new_instance(THREAD_LOCAL); + se.dup(); + se.invoke_constructor(THREAD_LOCAL, CSTRUCT_NULL); + se.putfield(THREAD_CALLBACKS_FIELD); + + final Object[] state = new Object[1]; + CallbackGenerator.Context context = new CallbackGenerator.Context() { + public ClassLoader getClassLoader() { + return Enhancer.this.getClassLoader(); + } + + public int getOriginalModifiers(MethodInfo method) { + return ((Integer) originalModifiers.get(method)).intValue(); + } + + public int getIndex(MethodInfo method) { + return ((Integer) indexes.get(method)).intValue(); + } + + public void emitCallback(CodeEmitter e, int index) { + emitCurrentCallback(e, index); + } + + public Signature getImplSignature(MethodInfo method) { + return rename(method.getSignature(), ((Integer) positions.get(method)).intValue()); + } + + public void emitLoadArgsAndInvoke(CodeEmitter e, MethodInfo method) { + // If this is a bridge and we know the target was called from invokespecial, + // then we need to invoke_virtual w/ the bridge target instead of doing + // a super, because super may itself be using super, which would bypass + // any proxies on the target. + Signature bridgeTarget = (Signature) bridgeToTarget.get(method.getSignature()); + if (bridgeTarget != null) { + // checkcast each argument against the target's argument types + for (int i = 0; i < bridgeTarget.getArgumentTypes().length; i++) { + e.load_arg(i); + Type target = bridgeTarget.getArgumentTypes()[i]; + if (!target.equals(method.getSignature().getArgumentTypes()[i])) { + e.checkcast(target); + } + } + + e.invoke_virtual_this(bridgeTarget); + + Type retType = method.getSignature().getReturnType(); + // Not necessary to cast if the target & bridge have + // the same return type. + // (This conveniently includes void and primitive types, + // which would fail if casted. It's not possible to + // covariant from boxed to unbox (or vice versa), so no having + // to box/unbox for bridges). + // TODO: It also isn't necessary to checkcast if the return is + // assignable from the target. (This would happen if a subclass + // used covariant returns to narrow the return type within a bridge + // method.) + if (!retType.equals(bridgeTarget.getReturnType())) { + e.checkcast(retType); + } + } + else { + e.load_args(); + e.super_invoke(method.getSignature()); + } + } + + public CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method) { + CodeEmitter e = EmitUtils.begin_method(ce, method); + if (!interceptDuringConstruction && + !TypeUtils.isAbstract(method.getModifiers())) { + Label constructed = e.make_label(); + e.load_this(); + e.getfield(CONSTRUCTED_FIELD); + e.if_jump(CodeEmitter.NE, constructed); + e.load_this(); + e.load_args(); + e.super_invoke(); + e.return_value(); + e.mark(constructed); + } + return e; + } + }; + for (int i = 0; i < callbackTypes.length; i++) { + CallbackGenerator gen = generators[i]; + if (!seenGen.contains(gen)) { + seenGen.add(gen); + final List fmethods = (List) groups.get(gen); + if (fmethods != null) { + try { + gen.generate(ce, context, fmethods); + gen.generateStatic(se, context, fmethods); + } + catch (RuntimeException x) { + throw x; + } + catch (Exception x) { + throw new CodeGenerationException(x); + } + } + } + } + se.return_value(); + se.end_method(); + } + + private void emitSetThreadCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC, + SET_THREAD_CALLBACKS, + null); + e.getfield(THREAD_CALLBACKS_FIELD); + e.load_arg(0); + e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_SET); + e.return_value(); + e.end_method(); + } + + private void emitSetStaticCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC, + SET_STATIC_CALLBACKS, + null); + e.load_arg(0); + e.putfield(STATIC_CALLBACKS_FIELD); + e.return_value(); + e.end_method(); + } + + private void emitCurrentCallback(CodeEmitter e, int index) { + e.load_this(); + e.getfield(getCallbackField(index)); + e.dup(); + Label end = e.make_label(); + e.ifnonnull(end); + e.pop(); // stack height + e.load_this(); + e.invoke_static_this(BIND_CALLBACKS); + e.load_this(); + e.getfield(getCallbackField(index)); + e.mark(end); + } + + private void emitBindCallbacks(ClassEmitter ce) { + CodeEmitter e = ce.begin_method(Constants.PRIVATE_FINAL_STATIC, + BIND_CALLBACKS, + null); + Local me = e.make_local(); + e.load_arg(0); + e.checkcast_this(); + e.store_local(me); + + Label end = e.make_label(); + e.load_local(me); + e.getfield(BOUND_FIELD); + e.if_jump(CodeEmitter.NE, end); + e.load_local(me); + e.push(1); + e.putfield(BOUND_FIELD); + + e.getfield(THREAD_CALLBACKS_FIELD); + e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_GET); + e.dup(); + Label found_callback = e.make_label(); + e.ifnonnull(found_callback); + e.pop(); + + e.getfield(STATIC_CALLBACKS_FIELD); + e.dup(); + e.ifnonnull(found_callback); + e.pop(); + e.goTo(end); + + e.mark(found_callback); + e.checkcast(CALLBACK_ARRAY); + e.load_local(me); + e.swap(); + for (int i = callbackTypes.length - 1; i >= 0; i--) { + if (i != 0) { + e.dup2(); + } + e.aaload(i); + e.checkcast(callbackTypes[i]); + e.putfield(getCallbackField(i)); + } + + e.mark(end); + e.return_value(); + e.end_method(); + } + + private static String getCallbackField(int index) { + return "CGLIB$CALLBACK_" + index; + } + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java new file mode 100644 index 0000000000..0e15a823ca --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/MethodProxy.java @@ -0,0 +1,251 @@ +/* + * Copyright 2003,2004 The Apache Software Foundation + * + * 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.cglib.proxy; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.springframework.cglib.core.AbstractClassGenerator; +import org.springframework.cglib.core.CodeGenerationException; +import org.springframework.cglib.core.GeneratorStrategy; +import org.springframework.cglib.core.NamingPolicy; +import org.springframework.cglib.core.Signature; +import org.springframework.cglib.reflect.FastClass; + +/** + * Classes generated by {@link Enhancer} pass this object to the + * registered {@link MethodInterceptor} objects when an intercepted method is invoked. It can + * be used to either invoke the original method, or call the same method on a different + * object of the same type. + * @version $Id: MethodProxy.java,v 1.16 2009/01/11 20:09:48 herbyderby Exp $ + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class MethodProxy { + + private Signature sig1; + + private Signature sig2; + + private CreateInfo createInfo; + + private final Object initLock = new Object(); + + private volatile FastClassInfo fastClassInfo; + + /** + * For internal use by {@link Enhancer} only; see the {@link org.springframework.cglib.reflect.FastMethod} class + * for similar functionality. + */ + public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { + MethodProxy proxy = new MethodProxy(); + proxy.sig1 = new Signature(name1, desc); + proxy.sig2 = new Signature(name2, desc); + proxy.createInfo = new CreateInfo(c1, c2); + return proxy; + } + + private void init() { + /* + * Using a volatile invariant allows us to initialize the FastClass and + * method index pairs atomically. + * + * Double-checked locking is safe with volatile in Java 5. Before 1.5 this + * code could allow fastClassInfo to be instantiated more than once, which + * appears to be benign. + */ + if (fastClassInfo == null) { + synchronized (initLock) { + if (fastClassInfo == null) { + CreateInfo ci = createInfo; + + FastClassInfo fci = new FastClassInfo(); + fci.f1 = helper(ci, ci.c1); + fci.f2 = helper(ci, ci.c2); + fci.i1 = fci.f1.getIndex(sig1); + fci.i2 = fci.f2.getIndex(sig2); + fastClassInfo = fci; + createInfo = null; + } + } + } + } + + + private static class FastClassInfo { + + FastClass f1; + + FastClass f2; + + int i1; + + int i2; + } + + + private static class CreateInfo { + + Class c1; + + Class c2; + + NamingPolicy namingPolicy; + + GeneratorStrategy strategy; + + boolean attemptLoad; + + public CreateInfo(Class c1, Class c2) { + this.c1 = c1; + this.c2 = c2; + AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent(); + if (fromEnhancer != null) { + namingPolicy = fromEnhancer.getNamingPolicy(); + strategy = fromEnhancer.getStrategy(); + attemptLoad = fromEnhancer.getAttemptLoad(); + } + } + } + + + private static FastClass helper(CreateInfo ci, Class type) { + FastClass.Generator g = new FastClass.Generator(); + g.setType(type); + // SPRING PATCH BEGIN + g.setContextClass(type); + // SPRING PATCH END + g.setClassLoader(ci.c2.getClassLoader()); + g.setNamingPolicy(ci.namingPolicy); + g.setStrategy(ci.strategy); + g.setAttemptLoad(ci.attemptLoad); + return g.create(); + } + + private MethodProxy() { + } + + /** + * Return the signature of the proxied method. + */ + public Signature getSignature() { + return sig1; + } + + /** + * Return the name of the synthetic method created by CGLIB which is + * used by {@link #invokeSuper} to invoke the superclass + * (non-intercepted) method implementation. The parameter types are + * the same as the proxied method. + */ + public String getSuperName() { + return sig2.getName(); + } + + /** + * Return the {@link org.springframework.cglib.reflect.FastClass} method index + * for the method used by {@link #invokeSuper}. This index uniquely + * identifies the method within the generated proxy, and therefore + * can be useful to reference external metadata. + * @see #getSuperName + */ + public int getSuperIndex() { + init(); + return fastClassInfo.i2; + } + + // For testing + FastClass getFastClass() { + init(); + return fastClassInfo.f1; + } + + // For testing + FastClass getSuperFastClass() { + init(); + return fastClassInfo.f2; + } + + /** + * Return the MethodProxy used when intercepting the method + * matching the given signature. + * @param type the class generated by Enhancer + * @param sig the signature to match + * @return the MethodProxy instance, or null if no applicable matching method is found + * @throws IllegalArgumentException if the Class was not created by Enhancer or does not use a MethodInterceptor + */ + public static MethodProxy find(Class type, Signature sig) { + try { + Method m = type.getDeclaredMethod(MethodInterceptorGenerator.FIND_PROXY_NAME, + MethodInterceptorGenerator.FIND_PROXY_TYPES); + return (MethodProxy) m.invoke(null, new Object[]{sig}); + } + catch (NoSuchMethodException ex) { + throw new IllegalArgumentException("Class " + type + " does not use a MethodInterceptor"); + } + catch (IllegalAccessException | InvocationTargetException ex) { + throw new CodeGenerationException(ex); + } + } + + /** + * Invoke the original method, on a different object of the same type. + * @param obj the compatible object; recursion will result if you use the object passed as the first + * argument to the MethodInterceptor (usually not what you want) + * @param args the arguments passed to the intercepted method; you may substitute a different + * argument array as long as the types are compatible + * @throws Throwable the bare exceptions thrown by the called method are passed through + * without wrapping in an InvocationTargetException + * @see MethodInterceptor#intercept + */ + public Object invoke(Object obj, Object[] args) throws Throwable { + try { + init(); + FastClassInfo fci = fastClassInfo; + return fci.f1.invoke(fci.i1, obj, args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + catch (IllegalArgumentException ex) { + if (fastClassInfo.i1 < 0) + throw new IllegalArgumentException("Protected method: " + sig1); + throw ex; + } + } + + /** + * Invoke the original (super) method on the specified object. + * @param obj the enhanced object, must be the object passed as the first + * argument to the MethodInterceptor + * @param args the arguments passed to the intercepted method; you may substitute a different + * argument array as long as the types are compatible + * @throws Throwable the bare exceptions thrown by the called method are passed through + * without wrapping in an InvocationTargetException + * @see MethodInterceptor#intercept + */ + public Object invokeSuper(Object obj, Object[] args) throws Throwable { + try { + init(); + FastClassInfo fci = fastClassInfo; + return fci.f2.invoke(fci.i2, obj, args); + } + catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java b/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java new file mode 100644 index 0000000000..0d651c8f04 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/package-info.java @@ -0,0 +1,10 @@ +/** + * Spring's repackaging of the + * CGLIB proxy package + * (for internal use only). + * + *

As this repackaging happens at the class file level, sources + * and javadocs are not available here... except for a few files + * that have been patched for Spring's purposes on JDK 9/10/11. + */ +package org.springframework.cglib.proxy;