Introduce ProxiedInterfacesCache for JdkDynamicAopProxy
Closes gh-30499
This commit is contained in:
parent
cd8bc2f82a
commit
44c652ec98
|
@ -102,8 +102,23 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||||
*/
|
*/
|
||||||
private List<Advisor> advisors = new ArrayList<>();
|
private List<Advisor> advisors = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of minimal {@link AdvisorKeyEntry} instances,
|
||||||
|
* to be assigned to the {@link #advisors} field on reduction.
|
||||||
|
* @since 6.0.10
|
||||||
|
* @see #reduceToAdvisorKey
|
||||||
|
*/
|
||||||
private List<Advisor> advisorKey = this.advisors;
|
private List<Advisor> advisorKey = this.advisors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional field for {@link AopProxy} implementations to store metadata in.
|
||||||
|
* Used for {@link JdkDynamicAopProxy.ProxiedInterfacesCache}.
|
||||||
|
* @since 6.1.3
|
||||||
|
* @see JdkDynamicAopProxy#JdkDynamicAopProxy(AdvisedSupport)
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
transient Object proxyMetadataCache;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No-arg constructor for use as a JavaBean.
|
* No-arg constructor for use as a JavaBean.
|
||||||
|
@ -491,6 +506,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||||
*/
|
*/
|
||||||
protected void adviceChanged() {
|
protected void adviceChanged() {
|
||||||
this.methodCache.clear();
|
this.methodCache.clear();
|
||||||
|
this.proxyMetadataCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -551,18 +567,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
// Serialization support
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
|
|
||||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
|
||||||
// Rely on default serialization; just initialize state after deserialization.
|
|
||||||
ois.defaultReadObject();
|
|
||||||
|
|
||||||
// Initialize transient fields.
|
|
||||||
this.methodCache = new ConcurrentHashMap<>(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toProxyConfigString() {
|
public String toProxyConfigString() {
|
||||||
return toString();
|
return toString();
|
||||||
|
@ -584,6 +588,19 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// Serialization support
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||||
|
// Rely on default serialization; just initialize state after deserialization.
|
||||||
|
ois.defaultReadObject();
|
||||||
|
|
||||||
|
// Initialize transient fields.
|
||||||
|
this.methodCache = new ConcurrentHashMap<>(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple wrapper class around a Method. Used as the key when
|
* Simple wrapper class around a Method. Used as the key when
|
||||||
* caching methods, for efficient equals and hashCode comparisons.
|
* caching methods, for efficient equals and hashCode comparisons.
|
||||||
|
@ -633,7 +650,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||||
* @see #getConfigurationOnlyCopy()
|
* @see #getConfigurationOnlyCopy()
|
||||||
* @see #getAdvisorKey()
|
* @see #getAdvisorKey()
|
||||||
*/
|
*/
|
||||||
private static class AdvisorKeyEntry implements Advisor {
|
private static final class AdvisorKeyEntry implements Advisor {
|
||||||
|
|
||||||
private final Class<?> adviceType;
|
private final Class<?> adviceType;
|
||||||
|
|
||||||
|
@ -643,7 +660,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String methodMatcherKey;
|
private final String methodMatcherKey;
|
||||||
|
|
||||||
|
|
||||||
public AdvisorKeyEntry(Advisor advisor) {
|
public AdvisorKeyEntry(Advisor advisor) {
|
||||||
this.adviceType = advisor.getAdvice().getClass();
|
this.adviceType = advisor.getAdvice().getClass();
|
||||||
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
|
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.aop.framework;
|
package org.springframework.aop.framework;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.InvocationHandler;
|
import java.lang.reflect.InvocationHandler;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -71,34 +73,16 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||||
private static final long serialVersionUID = 5531744639992436476L;
|
private static final long serialVersionUID = 5531744639992436476L;
|
||||||
|
|
||||||
|
|
||||||
/*
|
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||||
* NOTE: We could avoid the code duplication between this class and the CGLIB
|
|
||||||
* proxies by refactoring "invoke" into a template method. However, this approach
|
|
||||||
* adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice
|
|
||||||
* elegance for performance (we have a good test suite to ensure that the different
|
|
||||||
* proxies behave the same :-)).
|
|
||||||
* This way, we can also more easily take advantage of minor optimizations in each class.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** We use a static Log to avoid serialization issues. */
|
/** We use a static Log to avoid serialization issues. */
|
||||||
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
|
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);
|
||||||
|
|
||||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
|
||||||
|
|
||||||
/** Config used to configure this proxy. */
|
/** Config used to configure this proxy. */
|
||||||
private final AdvisedSupport advised;
|
private final AdvisedSupport advised;
|
||||||
|
|
||||||
private final Class<?>[] proxiedInterfaces;
|
/** Cached in {@link AdvisedSupport#proxyMetadataCache}. */
|
||||||
|
private transient ProxiedInterfacesCache cache;
|
||||||
/**
|
|
||||||
* Is the {@link #equals} method defined on the proxied interfaces?
|
|
||||||
*/
|
|
||||||
private boolean equalsDefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the {@link #hashCode} method defined on the proxied interfaces?
|
|
||||||
*/
|
|
||||||
private boolean hashCodeDefined;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -110,8 +94,17 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||||
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
|
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
|
||||||
Assert.notNull(config, "AdvisedSupport must not be null");
|
Assert.notNull(config, "AdvisedSupport must not be null");
|
||||||
this.advised = config;
|
this.advised = config;
|
||||||
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
|
|
||||||
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
|
// Initialize ProxiedInterfacesCache if not cached already
|
||||||
|
ProxiedInterfacesCache cache;
|
||||||
|
if (config.proxyMetadataCache instanceof ProxiedInterfacesCache proxiedInterfacesCache) {
|
||||||
|
cache = proxiedInterfacesCache;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cache = new ProxiedInterfacesCache(config);
|
||||||
|
config.proxyMetadataCache = cache;
|
||||||
|
}
|
||||||
|
this.cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,13 +118,13 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
|
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
|
||||||
}
|
}
|
||||||
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
|
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.cache.proxiedInterfaces, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
|
public Class<?> getProxyClass(@Nullable ClassLoader classLoader) {
|
||||||
return Proxy.getProxyClass(determineClassLoader(classLoader), this.proxiedInterfaces);
|
return Proxy.getProxyClass(determineClassLoader(classLoader), this.cache.proxiedInterfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,28 +153,6 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||||
return classLoader;
|
return classLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds any {@link #equals} or {@link #hashCode} method that may be defined
|
|
||||||
* on the supplied set of interfaces.
|
|
||||||
* @param proxiedInterfaces the interfaces to introspect
|
|
||||||
*/
|
|
||||||
private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
|
|
||||||
for (Class<?> proxiedInterface : proxiedInterfaces) {
|
|
||||||
Method[] methods = proxiedInterface.getDeclaredMethods();
|
|
||||||
for (Method method : methods) {
|
|
||||||
if (AopUtils.isEqualsMethod(method)) {
|
|
||||||
this.equalsDefined = true;
|
|
||||||
}
|
|
||||||
if (AopUtils.isHashCodeMethod(method)) {
|
|
||||||
this.hashCodeDefined = true;
|
|
||||||
}
|
|
||||||
if (this.equalsDefined && this.hashCodeDefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@code InvocationHandler.invoke}.
|
* Implementation of {@code InvocationHandler.invoke}.
|
||||||
|
@ -198,11 +169,11 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||||
Object target = null;
|
Object target = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
|
if (!this.cache.equalsDefined && AopUtils.isEqualsMethod(method)) {
|
||||||
// The target does not implement the equals(Object) method itself.
|
// The target does not implement the equals(Object) method itself.
|
||||||
return equals(args[0]);
|
return equals(args[0]);
|
||||||
}
|
}
|
||||||
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
|
else if (!this.cache.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
|
||||||
// The target does not implement the hashCode() method itself.
|
// The target does not implement the hashCode() method itself.
|
||||||
return hashCode();
|
return hashCode();
|
||||||
}
|
}
|
||||||
|
@ -324,4 +295,53 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||||
return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
|
return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// Serialization support
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||||
|
// Rely on default serialization; just initialize state after deserialization.
|
||||||
|
ois.defaultReadObject();
|
||||||
|
|
||||||
|
// Initialize transient fields.
|
||||||
|
this.cache = new ProxiedInterfacesCache(this.advised);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder for the complete proxied interfaces and derived metadata,
|
||||||
|
* to be cached in {@link AdvisedSupport#proxyMetadataCache}.
|
||||||
|
* @since 6.1.3
|
||||||
|
*/
|
||||||
|
static final class ProxiedInterfacesCache {
|
||||||
|
|
||||||
|
Class<?>[] proxiedInterfaces;
|
||||||
|
|
||||||
|
boolean equalsDefined;
|
||||||
|
|
||||||
|
boolean hashCodeDefined;
|
||||||
|
|
||||||
|
ProxiedInterfacesCache(AdvisedSupport config) {
|
||||||
|
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(config, true);
|
||||||
|
|
||||||
|
// Find any {@link #equals} or {@link #hashCode} method that may be defined
|
||||||
|
//on the supplied set of interfaces.
|
||||||
|
for (Class<?> proxiedInterface : this.proxiedInterfaces) {
|
||||||
|
Method[] methods = proxiedInterface.getDeclaredMethods();
|
||||||
|
for (Method method : methods) {
|
||||||
|
if (AopUtils.isEqualsMethod(method)) {
|
||||||
|
this.equalsDefined = true;
|
||||||
|
}
|
||||||
|
if (AopUtils.isHashCodeMethod(method)) {
|
||||||
|
this.hashCodeDefined = true;
|
||||||
|
}
|
||||||
|
if (this.equalsDefined && this.hashCodeDefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,7 @@ class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNullConfig() {
|
void testNullConfig() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
assertThatIllegalArgumentException().isThrownBy(() -> new JdkDynamicAopProxy(null));
|
||||||
new JdkDynamicAopProxy(null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -69,10 +68,8 @@ class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable
|
||||||
JdkDynamicAopProxy aop = new JdkDynamicAopProxy(pc);
|
JdkDynamicAopProxy aop = new JdkDynamicAopProxy(pc);
|
||||||
|
|
||||||
Object proxy = aop.getProxy();
|
Object proxy = aop.getProxy();
|
||||||
boolean condition = proxy instanceof ITestBean;
|
assertThat(proxy instanceof ITestBean).isTrue();
|
||||||
assertThat(condition).isTrue();
|
assertThat(proxy instanceof TestBean).isFalse();
|
||||||
boolean condition1 = proxy instanceof TestBean;
|
|
||||||
assertThat(condition1).isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -131,11 +128,15 @@ class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEqualsAndHashCodeDefined() {
|
void testEqualsAndHashCodeDefined() {
|
||||||
AdvisedSupport as = new AdvisedSupport(Named.class);
|
|
||||||
as.setTarget(new Person());
|
|
||||||
JdkDynamicAopProxy aopProxy = new JdkDynamicAopProxy(as);
|
|
||||||
Named proxy = (Named) aopProxy.getProxy();
|
|
||||||
Named named = new Person();
|
Named named = new Person();
|
||||||
|
AdvisedSupport as = new AdvisedSupport(Named.class);
|
||||||
|
as.setTarget(named);
|
||||||
|
|
||||||
|
Named proxy = (Named) new JdkDynamicAopProxy(as).getProxy();
|
||||||
|
assertThat(proxy).isEqualTo(named);
|
||||||
|
assertThat(named.hashCode()).isEqualTo(proxy.hashCode());
|
||||||
|
|
||||||
|
proxy = (Named) new JdkDynamicAopProxy(as).getProxy();
|
||||||
assertThat(proxy).isEqualTo(named);
|
assertThat(proxy).isEqualTo(named);
|
||||||
assertThat(named.hashCode()).isEqualTo(proxy.hashCode());
|
assertThat(named.hashCode()).isEqualTo(proxy.hashCode());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue