diff --git a/config/src/main/java/org/springframework/security/config/method/ProtectPointcutPostProcessor.java b/config/src/main/java/org/springframework/security/config/method/ProtectPointcutPostProcessor.java index f498e44c4f..018454e57e 100644 --- a/config/src/main/java/org/springframework/security/config/method/ProtectPointcutPostProcessor.java +++ b/config/src/main/java/org/springframework/security/config/method/ProtectPointcutPostProcessor.java @@ -1,12 +1,7 @@ package org.springframework.security.config.method; import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -50,10 +45,11 @@ final class ProtectPointcutPostProcessor implements BeanPostProcessor { private static final Log logger = LogFactory.getLog(ProtectPointcutPostProcessor.class); - private Map> pointcutMap = new LinkedHashMap>(); - private MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; - private Set pointCutExpressions = new LinkedHashSet(); - private PointcutParser parser; + private final Map> pointcutMap = new LinkedHashMap>(); + private final MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource; + private final Set pointCutExpressions = new LinkedHashSet(); + private final PointcutParser parser; + private final Set processedBeans = new HashSet(); public ProtectPointcutPostProcessor(MapBasedMethodSecurityMetadataSource mapBasedMethodSecurityMetadataSource) { Assert.notNull(mapBasedMethodSecurityMetadataSource, "MapBasedMethodSecurityMetadataSource to populate is required"); @@ -79,6 +75,11 @@ final class ProtectPointcutPostProcessor implements BeanPostProcessor { } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (processedBeans.contains(beanName)) { + // We already have the metadata for this bean + return bean; + } + // Obtain methods for the present bean Method[] methods; try { @@ -98,6 +99,8 @@ final class ProtectPointcutPostProcessor implements BeanPostProcessor { } } + processedBeans.add(beanName); + return bean; } diff --git a/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java b/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java index ea980a76ca..04d8bd7264 100644 --- a/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java +++ b/core/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.ConfigAttribute; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -42,7 +43,8 @@ import org.springframework.util.ClassUtils; * @author Ben Alex * @since 2.0 */ -public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource implements BeanClassLoaderAware { +public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource + implements BeanClassLoaderAware { //~ Instance fields ================================================================================================ private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); @@ -103,7 +105,7 @@ public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethod * for matching multiple methods. * * @param name type and method name, separated by a dot - * @param attr required authorities associated with the method + * @param attr the security attributes associated with the method */ private void addSecureMethod(String name, List attr) { int lastDotIndex = name.lastIndexOf("."); @@ -175,7 +177,9 @@ public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethod * Adds configuration attributes for a specific method, for example where the method has been * matched using a pointcut expression. If a match already exists in the map for the method, then * the existing match will be retained, so that if this method is called for a more general pointcut - * it will not override a more specific one which has already been added. This + * it will not override a more specific one which has already been added. + *

+ * This method should only be called during initialization of the {@code BeanFactory}. */ public void addSecureMethod(Class javaType, Method method, List attr) { RegisteredMethod key = new RegisteredMethod(method, javaType); diff --git a/itest/context/src/test/java/org/springframework/security/performance/ProtectPointcutPerformanceTests.java b/itest/context/src/test/java/org/springframework/security/performance/ProtectPointcutPerformanceTests.java new file mode 100644 index 0000000000..3c25f08b77 --- /dev/null +++ b/itest/context/src/test/java/org/springframework/security/performance/ProtectPointcutPerformanceTests.java @@ -0,0 +1,52 @@ +package org.springframework.security.performance; + +import static junit.framework.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.StopWatch; + +/** + * @author Luke Taylor + */ +@ContextConfiguration(locations={"/protect-pointcut-performance-app-context.xml"}) +@RunWith(SpringJUnit4ClassRunner.class) +public class ProtectPointcutPerformanceTests implements ApplicationContextAware { + ApplicationContext ctx; + + @Before + public void clearContext() { + SecurityContextHolder.clearContext(); + } + + // Method for use with profiler + @Test + public void usingPrototypeDoesNotParsePointcutOnEachCall() { + StopWatch sw = new StopWatch(); + sw.start(); + for (int i = 0; i < 1000; i++) { + try { + SessionRegistry reg = (SessionRegistry) ctx.getBean("sessionRegistryPrototype"); + reg.getAllPrincipals(); + fail("Expected AuthenticationCredentialsNotFoundException"); + } catch (AuthenticationCredentialsNotFoundException expected) { + } + } + sw.stop(); +// assertTrue(sw.getTotalTimeMillis() < 1000); + + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + ctx = applicationContext; + } +} diff --git a/itest/context/src/test/resources/protect-pointcut-performance-app-context.xml b/itest/context/src/test/resources/protect-pointcut-performance-app-context.xml new file mode 100644 index 0000000000..03080fbedb --- /dev/null +++ b/itest/context/src/test/resources/protect-pointcut-performance-app-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + +