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<>();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -491,6 +506,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
|||
*/
|
||||
protected void adviceChanged() {
|
||||
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
|
||||
public String toProxyConfigString() {
|
||||
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
|
||||
* caching methods, for efficient equals and hashCode comparisons.
|
||||
|
@ -633,7 +650,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
|||
* @see #getConfigurationOnlyCopy()
|
||||
* @see #getAdvisorKey()
|
||||
*/
|
||||
private static class AdvisorKeyEntry implements Advisor {
|
||||
private static final class AdvisorKeyEntry implements Advisor {
|
||||
|
||||
private final Class<?> adviceType;
|
||||
|
||||
|
@ -643,7 +660,6 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
|||
@Nullable
|
||||
private final String methodMatcherKey;
|
||||
|
||||
|
||||
public AdvisorKeyEntry(Advisor advisor) {
|
||||
this.adviceType = advisor.getAdvice().getClass();
|
||||
if (advisor instanceof PointcutAdvisor pointcutAdvisor) {
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.aop.framework;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
|
@ -71,34 +73,16 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
|||
private static final long serialVersionUID = 5531744639992436476L;
|
||||
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow";
|
||||
|
||||
/** We use a static Log to avoid serialization issues. */
|
||||
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. */
|
||||
private final AdvisedSupport advised;
|
||||
|
||||
private final Class<?>[] proxiedInterfaces;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/** Cached in {@link AdvisedSupport#proxyMetadataCache}. */
|
||||
private transient ProxiedInterfacesCache cache;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -110,8 +94,17 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
|||
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
|
||||
Assert.notNull(config, "AdvisedSupport must not be null");
|
||||
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()) {
|
||||
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")
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
|
@ -198,11 +169,11 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
|||
Object target = null;
|
||||
|
||||
try {
|
||||
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
|
||||
if (!this.cache.equalsDefined && AopUtils.isEqualsMethod(method)) {
|
||||
// The target does not implement the equals(Object) method itself.
|
||||
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.
|
||||
return hashCode();
|
||||
}
|
||||
|
@ -324,4 +295,53 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
|||
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
|
||||
void testNullConfig() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new JdkDynamicAopProxy(null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new JdkDynamicAopProxy(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -69,10 +68,8 @@ class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable
|
|||
JdkDynamicAopProxy aop = new JdkDynamicAopProxy(pc);
|
||||
|
||||
Object proxy = aop.getProxy();
|
||||
boolean condition = proxy instanceof ITestBean;
|
||||
assertThat(condition).isTrue();
|
||||
boolean condition1 = proxy instanceof TestBean;
|
||||
assertThat(condition1).isFalse();
|
||||
assertThat(proxy instanceof ITestBean).isTrue();
|
||||
assertThat(proxy instanceof TestBean).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -131,11 +128,15 @@ class JdkDynamicProxyTests extends AbstractAopProxyTests implements Serializable
|
|||
|
||||
@Test
|
||||
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();
|
||||
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(named.hashCode()).isEqualTo(proxy.hashCode());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue