提交 b95e05db 编写于 作者: J Juergen Hoeller

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
上级 c6ed41ec
/*
* 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.
......@@ -49,13 +49,13 @@ import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.aop.framework.autoproxy.ProxyCreationContext;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.support.AbstractExpressionPointcut;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
......@@ -289,10 +289,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
@Override
public boolean matches(Method method, @Nullable Class<?> targetClass, boolean beanHasIntroductions) {
public boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression();
Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Special handling for this, target, @this, @target, @annotation
// in Spring - we can optimize since we know we have exactly this class,
......@@ -305,7 +304,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
else {
// the maybe case
if (beanHasIntroductions) {
if (hasIntroductions) {
return true;
}
// A match test returned maybe - if there are any subtype sensitive variables
......@@ -331,8 +330,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getShadowMatch(AopUtils.getMostSpecificMethod(method, targetClass), method);
ShadowMatch originalShadowMatch = getShadowMatch(method, method);
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
// consistent with return of MethodInvocationProceedingJoinPoint
......@@ -367,7 +365,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* <p>See SPR-2979 for the original bug.
*/
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())) {
return false;
}
......@@ -427,6 +425,23 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
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) {
// Avoid lock contention for known Methods through concurrent access...
ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
......@@ -434,9 +449,9 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
synchronized (this.shadowMatchCache) {
// Not found - now check again with full lock...
PointcutExpression fallbackExpression = null;
Method methodToMatch = targetMethod;
shadowMatch = this.shadowMatchCache.get(targetMethod);
if (shadowMatch == null) {
Method methodToMatch = targetMethod;
try {
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
......@@ -459,7 +474,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
try {
shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
}
catch (ReflectionWorldException ex3) {
catch (ReflectionWorldException ex) {
// Could neither introspect the target class nor the proxy class ->
// let's try the original method's declaring class before we give up...
try {
......@@ -468,7 +483,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
}
}
catch (ReflectionWorldException ex4) {
catch (ReflectionWorldException ex2) {
fallbackExpression = null;
}
}
......
/*
* 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.
......@@ -90,8 +90,8 @@ public abstract class MethodMatchers {
*/
public static boolean matches(MethodMatcher mm, Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(mm, "MethodMatcher must not be null");
return ((mm instanceof IntroductionAwareMethodMatcher &&
((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions)) ||
return (mm instanceof IntroductionAwareMethodMatcher ?
((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions) :
mm.matches(method, targetClass));
}
......
/*
* 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);
}
/*
* 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");
* you may not use this file except in compliance with the License.
......@@ -27,11 +27,7 @@ import java.io.IOException;
* @author Rod Johnson
* @author Juergen Hoeller
*/
public interface ITestBean {
int getAge();
void setAge(int age);
public interface ITestBean extends AgeHolder {
String getName();
......
/*
* 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.
......@@ -185,7 +185,7 @@ public class AspectJAutoProxyCreatorTests {
// Create a child factory with a bean that should be woven
RootBeanDefinition bd = new RootBeanDefinition(TestBean.class);
bd.getPropertyValues().addPropertyValue(new PropertyValue("name", "Adrian"))
.addPropertyValue(new PropertyValue("age", new Integer(34)));
.addPropertyValue(new PropertyValue("age", 34));
childAc.registerBeanDefinition("adrian2", bd);
// Register the advisor auto proxy creator with subclass
childAc.registerBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class.getName(), new RootBeanDefinition(
......@@ -271,24 +271,44 @@ public class AspectJAutoProxyCreatorTests {
}
@Test
public void testTwoAdviceAspectSingleton() {
doTestTwoAdviceAspectWith("twoAdviceAspect.xml");
public void testTwoAdviceAspect() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspect.xml");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testAgeAspect(adrian1, 0, 2);
}
@Test
public void testTwoAdviceAspectPrototype() {
doTestTwoAdviceAspectWith("twoAdviceAspectPrototype.xml");
public void testTwoAdviceAspectSingleton() {
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) {
ClassPathXmlApplicationContext bf = newContext(location);
@Test
public void testTwoAdviceAspectPrototype() {
ClassPathXmlApplicationContext bf = newContext("twoAdviceAspectPrototype.xml");
boolean aspectSingleton = bf.isSingleton("aspect");
ITestBean adrian1 = (ITestBean) bf.getBean("adrian");
testPrototype(adrian1, 0);
testAgeAspect(adrian1, 0, 1);
ITestBean adrian2 = (ITestBean) bf.getBean("adrian");
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
......@@ -312,18 +332,6 @@ public class AspectJAutoProxyCreatorTests {
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
public void testForceProxyTargetClass() {
ClassPathXmlApplicationContext bf = newContext("aspectsWithCGLIB.xml");
......
/**
/*
* 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;
import org.aspectj.lang.annotation.Around;
......
/*
* 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");
* you may not use this file except in compliance with the License.
......@@ -23,15 +23,17 @@ import org.aspectj.lang.annotation.Before;
@Aspect
public class TwoAdviceAspect {
private int totalCalls;
@Around("execution(* getAge())")
@Around("execution(* org.springframework.tests.sample.beans.ITestBean.age())")
public int returnCallCount(ProceedingJoinPoint pjp) throws Exception {
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 {
++totalCalls;
}
}
\ No newline at end of file
}
......@@ -7,9 +7,13 @@
<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 id="adrian" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="org.springframework.tests.sample.beans.TestBean">
<property name="name" value="adrian"/>
<property name="age" value="34"/>
</bean>
</property>
</bean>
</beans>
......@@ -5,8 +5,7 @@
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
<bean id="aspect" class="test.aspect.TwoAdviceAspect"
scope="prototype"/>
<bean id="aspect" class="test.aspect.TwoAdviceAspect" scope="prototype"/>
<bean id="adrian" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
<property name="name" value="adrian"/>
......
<?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>
......@@ -348,13 +348,16 @@ public abstract class ClassUtils {
return true;
}
try {
return (clazz == classLoader.loadClass(clazz.getName()));
// Else: different class with same name found
if (clazz.getClassLoader() == classLoader) {
return true;
}
}
catch (ClassNotFoundException ex) {
// No corresponding class found at all
return false;
catch (SecurityException ex) {
// Fall through to loadable check below
}
// 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) {
// Fall through to Class reference comparison below
// Fall through to loadable check below
}
// Fallback for ClassLoaders without parent/child relationship:
// 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) {
Assert.notNull(clazz, "Class must not be null");
if (clazz.isInterface() && isVisible(clazz, classLoader)) {
return Collections.<Class<?>>singleton(clazz);
return Collections.singleton(clazz);
}
Set<Class<?>> interfaces = new LinkedHashSet<>();
Class<?> current = clazz;
while (current != null) {
Class<?>[] ifcs = current.getInterfaces();
for (Class<?> ifc : ifcs) {
interfaces.addAll(getAllInterfacesForClassAsSet(ifc, classLoader));
if (isVisible(ifc, classLoader)) {
interfaces.add(ifc);
}
}
current = current.getSuperclass();
}
......@@ -1211,8 +1233,7 @@ public abstract class ClassUtils {
* {@code targetClass} doesn't implement it or is {@code null}
*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {
if (isOverridable(method, targetClass) &&
targetClass != null && targetClass != method.getDeclaringClass()) {
if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) {
try {
if (Modifier.isPublic(method.getModifiers())) {
try {
......
/*
* 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.
......@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
/**
* Simple {@link TransactionAttributeSource} implementation that
......@@ -144,7 +145,7 @@ public class MethodMapTransactionAttributeSource
Assert.notNull(mappedName, "Mapped name must not be null");
String name = clazz.getName() + '.' + mappedName;
Method[] methods = clazz.getDeclaredMethods();
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
List<Method> matchingMethods = new ArrayList<>();
for (Method method : methods) {
if (isMatch(method.getName(), mappedName)) {
......@@ -156,7 +157,7 @@ public class MethodMapTransactionAttributeSource
"Couldn't find method '" + mappedName + "' on class [" + clazz.getName() + "]");
}
// register all matching methods
// Register all matching methods
for (Method method : matchingMethods) {
String regMethodName = this.methodNameMap.get(method);
if (regMethodName == null || (!regMethodName.equals(name) && regMethodName.length() <= name.length())) {
......
/*
* 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");
* 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.mockito.BDDMockito.*;
/**
* Test cases for AOP transaction management.
*
......@@ -67,7 +66,7 @@ public class BeanFactoryTransactionTests {
@Test
public void testGetsAreNotTransactionalWithProxyFactory1() throws NoSuchMethodException {
public void testGetsAreNotTransactionalWithProxyFactory1() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory1");
assertTrue("testBean is a dynamic proxy", Proxy.isProxyClass(testBean.getClass()));
assertFalse(testBean instanceof TransactionalProxy);
......@@ -75,7 +74,7 @@ public class BeanFactoryTransactionTests {
}
@Test
public void testGetsAreNotTransactionalWithProxyFactory2DynamicProxy() throws NoSuchMethodException {
public void testGetsAreNotTransactionalWithProxyFactory2DynamicProxy() {
this.factory.preInstantiateSingletons();
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2DynamicProxy");
assertTrue("testBean is a dynamic proxy", Proxy.isProxyClass(testBean.getClass()));
......@@ -84,7 +83,7 @@ public class BeanFactoryTransactionTests {
}
@Test
public void testGetsAreNotTransactionalWithProxyFactory2Cglib() throws NoSuchMethodException {
public void testGetsAreNotTransactionalWithProxyFactory2Cglib() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2Cglib");
assertTrue("testBean is CGLIB advised", AopUtils.isCglibProxy(testBean));
assertTrue(testBean instanceof TransactionalProxy);
......@@ -92,7 +91,7 @@ public class BeanFactoryTransactionTests {
}
@Test
public void testProxyFactory2Lazy() throws NoSuchMethodException {
public void testProxyFactory2Lazy() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory2Lazy");
assertFalse(factory.containsSingleton("target"));
assertEquals(666, testBean.getAge());
......@@ -100,7 +99,7 @@ public class BeanFactoryTransactionTests {
}
@Test
public void testCglibTransactionProxyImplementsNoInterfaces() throws NoSuchMethodException {
public void testCglibTransactionProxyImplementsNoInterfaces() {
ImplementsNoInterfaces ini = (ImplementsNoInterfaces) factory.getBean("cglibNoInterfaces");
assertTrue("testBean is CGLIB advised", AopUtils.isCglibProxy(ini));
assertTrue(ini instanceof TransactionalProxy);
......@@ -116,7 +115,7 @@ public class BeanFactoryTransactionTests {
}
@Test
public void testGetsAreNotTransactionalWithProxyFactory3() throws NoSuchMethodException {
public void testGetsAreNotTransactionalWithProxyFactory3() {
ITestBean testBean = (ITestBean) factory.getBean("proxyFactory3");
assertTrue("testBean is a full proxy", testBean instanceof DerivedTestBean);
assertTrue(testBean instanceof TransactionalProxy);
......@@ -202,7 +201,7 @@ public class BeanFactoryTransactionTests {
* Test that we can set the target to a dynamic TargetSource.
*/
@Test
public void testDynamicTargetSource() throws NoSuchMethodException {
public void testDynamicTargetSource() {
// Install facade
CallCountingTransactionManager txMan = new CallCountingTransactionManager();
PlatformTransactionManagerFacade.delegate = txMan;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册