Browse Source

AspectJExpressionPointcut consistently resolves superinterface methods

Includes efficient check for same ClassLoader in ClassUtils.isVisible, efficient MethodMatchers check for IntroductionAwareMethodMatcher, and supertype method resolution in MethodMapTransactionAttributeSource.

Issue: SPR-16723
pull/1793/head
Juergen Hoeller 8 years ago
parent
commit
b95e05db04
  1. 39
      spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java
  2. 6
      spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.java
  3. 29
      spring-beans/src/test/java/org/springframework/tests/sample/beans/AgeHolder.java
  4. 8
      spring-beans/src/test/java/org/springframework/tests/sample/beans/ITestBean.java
  5. 54
      spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.java
  6. 15
      spring-context/src/test/java/test/aspect/PerTargetAspect.java
  7. 8
      spring-context/src/test/java/test/aspect/TwoAdviceAspect.java
  8. 10
      spring-context/src/test/resources/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests-twoAdviceAspect.xml
  9. 3
      spring-context/src/test/resources/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests-twoAdviceAspectPrototype.xml
  10. 15
      spring-context/src/test/resources/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests-twoAdviceAspectSingleton.xml
  11. 43
      spring-core/src/main/java/org/springframework/util/ClassUtils.java
  12. 7
      spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java
  13. 17
      spring-tx/src/test/java/org/springframework/transaction/interceptor/BeanFactoryTransactionTests.java

39
spring-aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -49,13 +49,13 @@ import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.aop.framework.autoproxy.ProxyCreationContext; import org.springframework.aop.framework.autoproxy.ProxyCreationContext;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor; import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.support.AbstractExpressionPointcut; import org.springframework.aop.support.AbstractExpressionPointcut;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -289,10 +289,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
} }
@Override @Override
public boolean matches(Method method, @Nullable Class<?> targetClass, boolean beanHasIntroductions) { public boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression(); obtainPointcutExpression();
Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass); ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);
// Special handling for this, target, @this, @target, @annotation // Special handling for this, target, @this, @target, @annotation
// in Spring - we can optimize since we know we have exactly this class, // in Spring - we can optimize since we know we have exactly this class,
@ -305,7 +304,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
} }
else { else {
// the maybe case // the maybe case
if (beanHasIntroductions) { if (hasIntroductions) {
return true; return true;
} }
// A match test returned maybe - if there are any subtype sensitive variables // A match test returned maybe - if there are any subtype sensitive variables
@ -331,8 +330,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override @Override
public boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) { public boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) {
obtainPointcutExpression(); obtainPointcutExpression();
ShadowMatch shadowMatch = getShadowMatch(AopUtils.getMostSpecificMethod(method, targetClass), method); ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
ShadowMatch originalShadowMatch = getShadowMatch(method, method);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target, // Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
// consistent with return of MethodInvocationProceedingJoinPoint // consistent with return of MethodInvocationProceedingJoinPoint
@ -367,7 +365,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* <p>See SPR-2979 for the original bug. * <p>See SPR-2979 for the original bug.
*/ */
if (pmi != null && thisObject != null) { // there is a current invocation if (pmi != null && thisObject != null) { // there is a current invocation
RuntimeTestWalker originalMethodResidueTest = getRuntimeTestWalker(originalShadowMatch); RuntimeTestWalker originalMethodResidueTest = getRuntimeTestWalker(getShadowMatch(method, method));
if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) { if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) {
return false; return false;
} }
@ -427,6 +425,23 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
invocation.setUserAttribute(resolveExpression(), jpm); invocation.setUserAttribute(resolveExpression(), jpm);
} }
private ShadowMatch getTargetShadowMatch(Method method, @Nullable Class<?> targetClass) {
Method targetMethod = method;
if (targetClass != null) {
targetMethod = ClassUtils.getMostSpecificMethod(method, ClassUtils.getUserClass(targetClass));
if (targetMethod.getDeclaringClass().isInterface()) {
Set<Class<?>> ifcs = ClassUtils.getAllInterfacesForClassAsSet(targetClass);
if (ifcs.size() > 1) {
Class<?> compositeInterface = ClassUtils.createCompositeInterface(
ClassUtils.toClassArray(ifcs), targetClass.getClassLoader());
targetMethod = ClassUtils.getMostSpecificMethod(targetMethod, compositeInterface);
}
}
}
targetMethod = BridgeMethodResolver.findBridgedMethod(targetMethod);
return getShadowMatch(targetMethod, method);
}
private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) { private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
// Avoid lock contention for known Methods through concurrent access... // Avoid lock contention for known Methods through concurrent access...
ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod); ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
@ -434,9 +449,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
synchronized (this.shadowMatchCache) { synchronized (this.shadowMatchCache) {
// Not found - now check again with full lock... // Not found - now check again with full lock...
PointcutExpression fallbackExpression = null; PointcutExpression fallbackExpression = null;
Method methodToMatch = targetMethod;
shadowMatch = this.shadowMatchCache.get(targetMethod); shadowMatch = this.shadowMatchCache.get(targetMethod);
if (shadowMatch == null) { if (shadowMatch == null) {
Method methodToMatch = targetMethod;
try { try {
try { try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch); shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
@ -459,7 +474,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
try { try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch); shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
} }
catch (ReflectionWorldException ex3) { catch (ReflectionWorldException ex) {
// Could neither introspect the target class nor the proxy class -> // Could neither introspect the target class nor the proxy class ->
// let's try the original method's declaring class before we give up... // let's try the original method's declaring class before we give up...
try { try {
@ -468,7 +483,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch); shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
} }
} }
catch (ReflectionWorldException ex4) { catch (ReflectionWorldException ex2) {
fallbackExpression = null; fallbackExpression = null;
} }
} }

6
spring-aop/src/main/java/org/springframework/aop/support/MethodMatchers.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -90,8 +90,8 @@ public abstract class MethodMatchers {
*/ */
public static boolean matches(MethodMatcher mm, Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) { public static boolean matches(MethodMatcher mm, Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(mm, "MethodMatcher must not be null"); Assert.notNull(mm, "MethodMatcher must not be null");
return ((mm instanceof IntroductionAwareMethodMatcher && return (mm instanceof IntroductionAwareMethodMatcher ?
((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions)) || ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) :
mm.matches(method, targetClass)); mm.matches(method, targetClass));
} }

29
spring-beans/src/test/java/org/springframework/tests/sample/beans/AgeHolder.java

@ -0,0 +1,29 @@
/*
* 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.
* 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.tests.sample.beans;
public interface AgeHolder {
default int age() {
return getAge();
}
int getAge();
void setAge(int age);
}

8
spring-beans/src/test/java/org/springframework/tests/sample/beans/ITestBean.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2007 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,11 +27,7 @@ import java.io.IOException;
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
*/ */
public interface ITestBean { public interface ITestBean extends AgeHolder {
int getAge();
void setAge(int age);
String getName(); String getName();

54
spring-context/src/test/java/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -185,7 +185,7 @@ public class AspectJAutoProxyCreatorTests {
// Create a child factory with a bean that should be woven // Create a child factory with a bean that should be woven
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
bd.getPropertyValues().addPropertyValue(new PropertyValue("name", "Adrian")) bd.getPropertyValues().addPropertyValue(new PropertyValue("name", "Adrian"))
.addPropertyValue(new PropertyValue("age", new Integer(34))); .addPropertyValue(new PropertyValue("age", 34));
childAc.registerBeanDefinition("adrian2", bd); childAc.registerBeanDefinition("adrian2", bd);
// Register the advisor auto proxy creator with subclass // Register the advisor auto proxy creator with subclass
childAc.registerBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class.getName(), new RootBeanDefinition( childAc.registerBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class.getName(), new RootBeanDefinition(
@ -271,24 +271,44 @@ public class AspectJAutoProxyCreatorTests {
} }
@Test @Test
public void testTwoAdviceAspectSingleton() { public void testTwoAdviceAspect() {
doTestTwoAdviceAspectWith("twoAdviceAspect.xml"); ClassPathXmlApplicationContext bf = newContext("twoAdviceAspect.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testAgeAspect(adrian1, 0, 2);
} }
@Test @Test
public void testTwoAdviceAspectPrototype() { public void testTwoAdviceAspectSingleton() {
doTestTwoAdviceAspectWith("twoAdviceAspectPrototype.xml"); ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectSingleton.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testAgeAspect(adrian1, 0, 1);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertNotSame(adrian1, adrian2);
testAgeAspect(adrian2, 2, 1);
} }
private void doTestTwoAdviceAspectWith(String location) { @Test
ClassPathXmlApplicationContext bf = newContext(location); public void testTwoAdviceAspectPrototype() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectPrototype.xml");
boolean aspectSingleton = bf.isSingleton("aspect");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian"); ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testPrototype(adrian1, 0); testAgeAspect(adrian1, 0, 1);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian"); ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
assertNotSame(adrian1, adrian2); assertNotSame(adrian1, adrian2);
testPrototype(adrian2, aspectSingleton ? 2 : 0); testAgeAspect(adrian2, 0, 1);
}
private void testAgeAspect(ITestBean adrian, int start, int increment) {
assertTrue(AopUtils.isAopProxy(adrian));
adrian.setName("");
assertEquals(start, adrian.age());
int newAge = 32;
adrian.setAge(newAge);
assertEquals(start + increment, adrian.age());
adrian.setAge(0);
assertEquals(start + increment * 2, adrian.age());
} }
@Test @Test
@ -312,18 +332,6 @@ public class AspectJAutoProxyCreatorTests {
assertEquals(68, adrian.getAge()); assertEquals(68, adrian.getAge());
} }
private void testPrototype(ITestBean adrian1, int start) {
assertTrue(AopUtils.isAopProxy(adrian1));
//TwoAdviceAspect twoAdviceAspect = (TwoAdviceAspect) bf.getBean(TwoAdviceAspect.class.getName());
adrian1.setName("");
assertEquals(start++, adrian1.getAge());
int newAge = 32;
adrian1.setAge(newAge);
assertEquals(start++, adrian1.getAge());
adrian1.setAge(0);
assertEquals(start++, adrian1.getAge());
}
@Test @Test
public void testForceProxyTargetClass() { public void testForceProxyTargetClass() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithCGLIB.xml"); ClassPathXmlApplicationContext bf = newContext("aspectsWithCGLIB.xml");

15
spring-context/src/test/java/test/aspect/PerTargetAspect.java

@ -1,6 +1,19 @@
/** /*
* 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.
* 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 test.aspect; package test.aspect;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;

8
spring-context/src/test/java/test/aspect/TwoAdviceAspect.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2008 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,15 +23,17 @@ import org.aspectj.lang.annotation.Before;
@Aspect @Aspect
public class TwoAdviceAspect { public class TwoAdviceAspect {
private int totalCalls; private int totalCalls;
@Around("execution(* getAge())") @Around("execution(* org.springframework.tests.sample.beans.ITestBean.age())")
public int returnCallCount(ProceedingJoinPoint pjp) throws Exception { public int returnCallCount(ProceedingJoinPoint pjp) throws Exception {
return totalCalls; return totalCalls;
} }
@Before("execution(* setAge(int)) && args(newAge)") @Before("execution(* org.springframework.tests.sample.beans.ITestBean.setAge(int)) && args(newAge)")
public void countSet(int newAge) throws Exception { public void countSet(int newAge) throws Exception {
++totalCalls; ++totalCalls;
} }
} }

10
spring-context/src/test/resources/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests-twoAdviceAspect.xml

@ -7,9 +7,13 @@
<bean id="aspect" class="test.aspect.TwoAdviceAspect"/> <bean id="aspect" class="test.aspect.TwoAdviceAspect"/>
<bean id="adrian" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"> <bean id="adrian" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="name" value="adrian"/> <property name="target">
<property name="age" value="34"/> <bean class="org.springframework.tests.sample.beans.TestBean">
<property name="name" value="adrian"/>
<property name="age" value="34"/>
</bean>
</property>
</bean> </bean>
</beans> </beans>

3
spring-context/src/test/resources/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests-twoAdviceAspectPrototype.xml

@ -5,8 +5,7 @@
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/> <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
<bean id="aspect" class="test.aspect.TwoAdviceAspect" <bean id="aspect" class="test.aspect.TwoAdviceAspect" scope="prototype"/>
scope="prototype"/>
<bean id="adrian" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"> <bean id="adrian" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
<property name="name" value="adrian"/> <property name="name" value="adrian"/>

15
spring-context/src/test/resources/org/springframework/aop/aspectj/autoproxy/AspectJAutoProxyCreatorTests-twoAdviceAspectSingleton.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
<bean id="aspect" class="test.aspect.TwoAdviceAspect"/>
<bean id="adrian" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
<property name="name" value="adrian"/>
<property name="age" value="34"/>
</bean>
</beans>

43
spring-core/src/main/java/org/springframework/util/ClassUtils.java

@ -348,13 +348,16 @@ public abstract class ClassUtils {
return true; return true;
} }
try { try {
return (clazz == classLoader.loadClass(clazz.getName())); if (clazz.getClassLoader() == classLoader) {
// Else: different class with same name found return true;
}
} }
catch (ClassNotFoundException ex) { catch (SecurityException ex) {
// No corresponding class found at all // Fall through to loadable check below
return false;
} }
// Visible if same Class can be loaded from given ClassLoader
return isLoadable(clazz, classLoader);
} }
/** /**
@ -392,12 +395,29 @@ public abstract class ClassUtils {
} }
} }
catch (SecurityException ex) { catch (SecurityException ex) {
// Fall through to Class reference comparison below // Fall through to loadable check below
} }
// Fallback for ClassLoaders without parent/child relationship: // Fallback for ClassLoaders without parent/child relationship:
// safe if same Class can be loaded from given ClassLoader // safe if same Class can be loaded from given ClassLoader
return (classLoader != null && isVisible(clazz, classLoader)); return (classLoader != null && isLoadable(clazz, classLoader));
}
/**
* Check whether the given class is loadable in the given ClassLoader.
* @param clazz the class to check (typically an interface)
* @param classLoader the ClassLoader to check against
* @since 5.0.6
*/
private static boolean isLoadable(Class<?> clazz, ClassLoader classLoader) {
try {
return (clazz == classLoader.loadClass(clazz.getName()));
// Else: different class with same name found
}
catch (ClassNotFoundException ex) {
// No corresponding class found at all
return false;
}
} }
/** /**
@ -711,14 +731,16 @@ public abstract class ClassUtils {
public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz, @Nullable ClassLoader classLoader) { public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz, @Nullable ClassLoader classLoader) {
Assert.notNull(clazz, "Class must not be null"); Assert.notNull(clazz, "Class must not be null");
if (clazz.isInterface() && isVisible(clazz, classLoader)) { if (clazz.isInterface() && isVisible(clazz, classLoader)) {
return Collections.<Class<?>>singleton(clazz); return Collections.singleton(clazz);
} }
Set<Class<?>> interfaces = new LinkedHashSet<>(); Set<Class<?>> interfaces = new LinkedHashSet<>();
Class<?> current = clazz; Class<?> current = clazz;
while (current != null) { while (current != null) {
Class<?>[] ifcs = current.getInterfaces(); Class<?>[] ifcs = current.getInterfaces();
for (Class<?> ifc : ifcs) { for (Class<?> ifc : ifcs) {
interfaces.addAll(getAllInterfacesForClassAsSet(ifc, classLoader)); if (isVisible(ifc, classLoader)) {
interfaces.add(ifc);
}
} }
current = current.getSuperclass(); current = current.getSuperclass();
} }
@ -1211,8 +1233,7 @@ public abstract class ClassUtils {
* {@code targetClass} doesn't implement it or is {@code null} * {@code targetClass} doesn't implement it or is {@code null}
*/ */
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) { public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
if (isOverridable(method, targetClass) && if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) {
targetClass != null && targetClass != method.getDeclaringClass()) {
try { try {
if (Modifier.isPublic(method.getModifiers())) { if (Modifier.isPublic(method.getModifiers())) {
try { try {

7
spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,6 +32,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils; import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* Simple {@link TransactionAttributeSource} implementation that * Simple {@link TransactionAttributeSource} implementation that
@ -144,7 +145,7 @@ public class MethodMapTransactionAttributeSource
Assert.notNull(mappedName, "Mapped name must not be null"); Assert.notNull(mappedName, "Mapped name must not be null");
String name = clazz.getName() + '.' + mappedName; String name = clazz.getName() + '.' + mappedName;
Method[] methods = clazz.getDeclaredMethods(); Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
List<Method> matchingMethods = new ArrayList<>(); List<Method> matchingMethods = new ArrayList<>();
for (Method method : methods) { for (Method method : methods) {
if (isMatch(method.getName(), mappedName)) { if (isMatch(method.getName(), mappedName)) {
@ -156,7 +157,7 @@ public class MethodMapTransactionAttributeSource
"Couldn't find method '" + mappedName + "' on class [" + clazz.getName() + "]"); "Couldn't find method '" + mappedName + "' on class [" + clazz.getName() + "]");
} }
// register all matching methods // Register all matching methods
for (Method method : matchingMethods) { for (Method method : matchingMethods) {
String regMethodName = this.methodNameMap.get(method); String regMethodName = this.methodNameMap.get(method);
if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) { if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) {

17
spring-tx/src/test/java/org/springframework/transaction/interceptor/BeanFactoryTransactionTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -45,7 +45,6 @@ import org.springframework.transaction.TransactionStatus;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*; import static org.mockito.BDDMockito.*;
/** /**
* Test cases for AOP transaction management. * Test cases for AOP transaction management.
* *
@ -67,7 +66,7 @@ public class BeanFactoryTransactionTests {
@Test @Test
public void testGetsAreNotTransactionalWithProxyFactory1() throws NoSuchMethodException { public void testGetsAreNotTransactionalWithProxyFactory1() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory1"); ITestBean testBean = (ITestBean) factory.getBean("proxyFactory1");
assertTrue("testBean is a dynamic proxy", Proxy.isProxyClass(testBean.getClass())); assertTrue("testBean is a dynamic proxy", Proxy.isProxyClass(testBean.getClass()));
assertFalse(testBean instanceof TransactionalProxy); assertFalse(testBean instanceof TransactionalProxy);
@ -75,7 +74,7 @@ public class BeanFactoryTransactionTests {
} }
@Test @Test
public void testGetsAreNotTransactionalWithProxyFactory2DynamicProxy() throws NoSuchMethodException { public void testGetsAreNotTransactionalWithProxyFactory2DynamicProxy() {
this.factory.preInstantiateSingletons(); this.factory.preInstantiateSingletons();
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2DynamicProxy"); ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2DynamicProxy");
assertTrue("testBean is a dynamic proxy", Proxy.isProxyClass(testBean.getClass())); assertTrue("testBean is a dynamic proxy", Proxy.isProxyClass(testBean.getClass()));
@ -84,7 +83,7 @@ public class BeanFactoryTransactionTests {
} }
@Test @Test
public void testGetsAreNotTransactionalWithProxyFactory2Cglib() throws NoSuchMethodException { public void testGetsAreNotTransactionalWithProxyFactory2Cglib() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2Cglib"); ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2Cglib");
assertTrue("testBean is CGLIB advised", AopUtils.isCglibProxy(testBean)); assertTrue("testBean is CGLIB advised", AopUtils.isCglibProxy(testBean));
assertTrue(testBean instanceof TransactionalProxy); assertTrue(testBean instanceof TransactionalProxy);
@ -92,7 +91,7 @@ public class BeanFactoryTransactionTests {
} }
@Test @Test
public void testProxyFactory2Lazy() throws NoSuchMethodException { public void testProxyFactory2Lazy() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2Lazy"); ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2Lazy");
assertFalse(factory.containsSingleton("target")); assertFalse(factory.containsSingleton("target"));
assertEquals(666, testBean.getAge()); assertEquals(666, testBean.getAge());
@ -100,7 +99,7 @@ public class BeanFactoryTransactionTests {
} }
@Test @Test
public void testCglibTransactionProxyImplementsNoInterfaces() throws NoSuchMethodException { public void testCglibTransactionProxyImplementsNoInterfaces() {
ImplementsNoInterfaces ini = (ImplementsNoInterfaces) factory.getBean("cglibNoInterfaces"); ImplementsNoInterfaces ini = (ImplementsNoInterfaces) factory.getBean("cglibNoInterfaces");
assertTrue("testBean is CGLIB advised", AopUtils.isCglibProxy(ini)); assertTrue("testBean is CGLIB advised", AopUtils.isCglibProxy(ini));
assertTrue(ini instanceof TransactionalProxy); assertTrue(ini instanceof TransactionalProxy);
@ -116,7 +115,7 @@ public class BeanFactoryTransactionTests {
} }
@Test @Test
public void testGetsAreNotTransactionalWithProxyFactory3() throws NoSuchMethodException { public void testGetsAreNotTransactionalWithProxyFactory3() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory3"); ITestBean testBean = (ITestBean) factory.getBean("proxyFactory3");
assertTrue("testBean is a full proxy", testBean instanceof DerivedTestBean); assertTrue("testBean is a full proxy", testBean instanceof DerivedTestBean);
assertTrue(testBean instanceof TransactionalProxy); assertTrue(testBean instanceof TransactionalProxy);
@ -202,7 +201,7 @@ public class BeanFactoryTransactionTests {
* Test that we can set the target to a dynamic TargetSource. * Test that we can set the target to a dynamic TargetSource.
*/ */
@Test @Test
public void testDynamicTargetSource() throws NoSuchMethodException { public void testDynamicTargetSource() {
// Install facade // Install facade
CallCountingTransactionManager txMan = new CallCountingTransactionManager(); CallCountingTransactionManager txMan = new CallCountingTransactionManager();
PlatformTransactionManagerFacade.delegate = txMan; PlatformTransactionManagerFacade.delegate = txMan;

Loading…
Cancel
Save