Skip to content

Commit 0d0b879

Browse files
committed
CglibAopProxy logs explicit warning for interface-implementing method marked as final
Issue: SPR-15436
1 parent 5d3249f commit 0d0b879

File tree

1 file changed

+58
-50
lines changed

1 file changed

+58
-50
lines changed

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

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.HashMap;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Set;
2627
import java.util.WeakHashMap;
2728

2829
import org.aopalliance.aop.Advice;
@@ -56,9 +57,6 @@
5657
/**
5758
* CGLIB-based {@link AopProxy} implementation for the Spring AOP framework.
5859
*
59-
* <p>Formerly named {@code Cglib2AopProxy}, as of Spring 3.2, this class depends on
60-
* Spring's own internally repackaged version of CGLIB 3.</i>.
61-
*
6260
* <p>Objects of this type should be obtained through proxy factories,
6361
* configured by an {@link AdvisedSupport} object. This class is internal
6462
* to Spring's AOP framework and need not be used directly by client code.
@@ -235,10 +233,11 @@ protected Enhancer createEnhancer() {
235233
* validates it if not.
236234
*/
237235
private void validateClassIfNecessary(Class<?> proxySuperClass, ClassLoader proxyClassLoader) {
238-
if (logger.isInfoEnabled()) {
236+
if (logger.isWarnEnabled()) {
239237
synchronized (validatedClasses) {
240238
if (!validatedClasses.containsKey(proxySuperClass)) {
241-
doValidateClass(proxySuperClass, proxyClassLoader);
239+
doValidateClass(proxySuperClass, proxyClassLoader,
240+
ClassUtils.getAllInterfacesForClassAsSet(proxySuperClass));
242241
validatedClasses.put(proxySuperClass, Boolean.TRUE);
243242
}
244243
}
@@ -249,30 +248,35 @@ private void validateClassIfNecessary(Class<?> proxySuperClass, ClassLoader prox
249248
* Checks for final methods on the given {@code Class}, as well as package-visible
250249
* methods across ClassLoaders, and writes warnings to the log for each one found.
251250
*/
252-
private void doValidateClass(Class<?> proxySuperClass, ClassLoader proxyClassLoader) {
251+
private void doValidateClass(Class<?> proxySuperClass, ClassLoader proxyClassLoader, Set<Class<?>> ifcs) {
253252
if (proxySuperClass != Object.class) {
254253
Method[] methods = proxySuperClass.getDeclaredMethods();
255254
for (Method method : methods) {
256255
int mod = method.getModifiers();
257256
if (!Modifier.isStatic(mod)) {
258257
if (Modifier.isFinal(mod)) {
259-
logger.info("Unable to proxy method [" + method + "] because it is final: " +
260-
"All calls to this method via a proxy will NOT be routed to the target instance.");
258+
if (implementsInterface(method, ifcs)) {
259+
logger.warn("Unable to proxy interface-implmenting method [" + method + "] because " +
260+
"it is marked as final: Consider using interface-based proxies instead!");
261+
}
262+
logger.info("Final method [" + method + "] cannot get proxied via CGLIB: " +
263+
"Calls to this method will NOT be routed to the target instance and " +
264+
"might lead to NPEs against uninitialized fields in the proxy instance.");
261265
}
262266
else if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod) && !Modifier.isPrivate(mod) &&
263267
proxyClassLoader != null && proxySuperClass.getClassLoader() != proxyClassLoader) {
264-
logger.info("Unable to proxy method [" + method + "] because it is package-visible " +
265-
"across different ClassLoaders: All calls to this method via a proxy will " +
266-
"NOT be routed to the target instance.");
268+
logger.info("Method [" + method + "] is package-visible across different ClassLoaders " +
269+
"and cannot get proxied via CGLIB: Declare this method as public or protected " +
270+
"if you need to support invocations through the proxy.");
267271
}
268272
}
269273
}
270-
doValidateClass(proxySuperClass.getSuperclass(), proxyClassLoader);
274+
doValidateClass(proxySuperClass.getSuperclass(), proxyClassLoader, ifcs);
271275
}
272276
}
273277

274278
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
275-
// Parameters used for optimisation choices...
279+
// Parameters used for optimization choices...
276280
boolean exposeProxy = this.advised.isExposeProxy();
277281
boolean isFrozen = this.advised.isFrozen();
278282
boolean isStatic = this.advised.getTargetSource().isStatic();
@@ -311,14 +315,14 @@ private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
311315
Callback[] callbacks;
312316

313317
// If the target is a static one and the advice chain is frozen,
314-
// then we can make some optimisations by sending the AOP calls
318+
// then we can make some optimizations by sending the AOP calls
315319
// direct to the target using the fixed chain for that method.
316320
if (isStatic && isFrozen) {
317321
Method[] methods = rootClass.getMethods();
318322
Callback[] fixedCallbacks = new Callback[methods.length];
319323
this.fixedInterceptorMap = new HashMap<>(methods.length);
320324

321-
// TODO: small memory optimisation here (can skip creation for methods with no advice)
325+
// TODO: small memory optimization here (can skip creation for methods with no advice)
322326
for (int x = 0; x < methods.length; x++) {
323327
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
324328
fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
@@ -339,6 +343,31 @@ private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
339343
return callbacks;
340344
}
341345

346+
347+
@Override
348+
public boolean equals(Object other) {
349+
return (this == other || (other instanceof CglibAopProxy &&
350+
AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised)));
351+
}
352+
353+
@Override
354+
public int hashCode() {
355+
return CglibAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
356+
}
357+
358+
359+
/**
360+
* Check whether the given method is declared on any of the given interfaces.
361+
*/
362+
private static boolean implementsInterface(Method method, Set<Class<?>> ifcs) {
363+
for (Class<?> ifc : ifcs) {
364+
if (ClassUtils.hasMethod(ifc, method.getName(), method.getParameterTypes())) {
365+
return true;
366+
}
367+
}
368+
return false;
369+
}
370+
342371
/**
343372
* Process a return value. Wraps a return of {@code this} if necessary to be the
344373
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@@ -360,18 +389,6 @@ private static Object processReturnType(Object proxy, Object target, Method meth
360389
}
361390

362391

363-
@Override
364-
public boolean equals(Object other) {
365-
return (this == other || (other instanceof CglibAopProxy &&
366-
AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised)));
367-
}
368-
369-
@Override
370-
public int hashCode() {
371-
return CglibAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
372-
}
373-
374-
375392
/**
376393
* Serializable replacement for CGLIB's NoOp interface.
377394
* Public to allow use elsewhere in the framework.
@@ -817,51 +834,42 @@ public int accept(Method method) {
817834
// Else use the AOP_PROXY.
818835
if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {
819836
if (logger.isDebugEnabled()) {
820-
logger.debug("Method has advice and optimisations are enabled: " + method);
837+
logger.debug("Method has advice and optimizations are enabled: " + method);
821838
}
822-
// We know that we are optimising so we can use the FixedStaticChainInterceptors.
839+
// We know that we are optimizing so we can use the FixedStaticChainInterceptors.
823840
int index = this.fixedInterceptorMap.get(key);
824841
return (index + this.fixedInterceptorOffset);
825842
}
826843
else {
827844
if (logger.isDebugEnabled()) {
828-
logger.debug("Unable to apply any optimisations to advised method: " + method);
845+
logger.debug("Unable to apply any optimizations to advised method: " + method);
829846
}
830847
return AOP_PROXY;
831848
}
832849
}
833850
else {
834-
// See if the return type of the method is outside the class hierarchy
835-
// of the target type. If so we know it never needs to have return type
836-
// massage and can use a dispatcher.
837-
// If the proxy is being exposed, then must use the interceptor the
838-
// correct one is already configured. If the target is not static, then
839-
// cannot use a dispatcher because the target cannot be released.
851+
// See if the return type of the method is outside the class hierarchy of the target type.
852+
// If so we know it never needs to have return type massage and can use a dispatcher.
853+
// If the proxy is being exposed, then must use the interceptor the correct one is already
854+
// configured. If the target is not static, then we cannot use a dispatcher because the
855+
// target needs to be explicitly released after the invocation.
840856
if (exposeProxy || !isStatic) {
841857
return INVOKE_TARGET;
842858
}
843859
Class<?> returnType = method.getReturnType();
844-
if (targetClass == returnType) {
860+
if (returnType.isAssignableFrom(targetClass)) {
845861
if (logger.isDebugEnabled()) {
846-
logger.debug("Method " + method +
847-
"has return type same as target type (may return this) - using INVOKE_TARGET");
862+
logger.debug("Method return type is assignable from target type and " +
863+
"may therefore return 'this' - using INVOKE_TARGET: " + method);
848864
}
849865
return INVOKE_TARGET;
850866
}
851-
else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) {
852-
if (logger.isDebugEnabled()) {
853-
logger.debug("Method " + method +
854-
" has return type that ensures this cannot be returned- using DISPATCH_TARGET");
855-
}
856-
return DISPATCH_TARGET;
857-
}
858867
else {
859868
if (logger.isDebugEnabled()) {
860-
logger.debug("Method " + method +
861-
"has return type that is assignable from the target type (may return this) - " +
862-
"using INVOKE_TARGET");
869+
logger.debug("Method return type ensures 'this' cannot be returned - " +
870+
"using DISPATCH_TARGET: " + method);
863871
}
864-
return INVOKE_TARGET;
872+
return DISPATCH_TARGET;
865873
}
866874
}
867875
}

0 commit comments

Comments
 (0)