From 620018d16b2639a4ded38f0c05c6277698c5c69f Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Mon, 6 Jun 2011 08:31:18 +0000 Subject: [PATCH] Introduce @EnableLoadTimeWeaving Issue: SPR-8317 --- .../annotation/EnableLoadTimeWeaving.java | 160 ++++++++++++++++++ .../LoadTimeWeavingConfiguration.java | 99 +++++++++++ .../annotation/LoadTimeWeavingConfigurer.java | 44 +++++ .../LoadTimeWeaverBeanDefinitionParser.java | 7 +- .../weaving/AspectJWeavingEnabler.java | 26 +-- .../weaving/DefaultContextLoadTimeWeaver.java | 4 + .../context/config/spring-context-3.1.xsd | 7 +- .../EnableLoadTimeWeavingTests-context.xml | 10 ++ .../EnableLoadTimeWeavingTests.java | 55 ++++++ 9 files changed, 396 insertions(+), 16 deletions(-) create mode 100644 org.springframework.context/src/main/java/org/springframework/context/annotation/EnableLoadTimeWeaving.java create mode 100644 org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java create mode 100644 org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests-context.xml create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/EnableLoadTimeWeaving.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/EnableLoadTimeWeaving.java new file mode 100644 index 00000000000..bb20e205868 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/EnableLoadTimeWeaving.java @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2011 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.context.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.weaving.DefaultContextLoadTimeWeaver; +import org.springframework.instrument.classloading.LoadTimeWeaver; + +/** + * Activates a Spring {@link LoadTimeWeaver} for this application context, available as + * a bean with the name "loadTimeWeaver", similar to the {@code } + * element in Spring XML. + * To be used + * on @{@link org.springframework.context.annotation.Configuration Configuration} classes; + * the simplest possible example of which follows: + *
+ * @Configuration
+ * @EnableLoadTimeWeaving
+ * public class AppConfig {
+ *     // application-specific @Bean definitions ...
+ * }
+ * + * The example above is equivalent to the following Spring XML configuration: + *
+ * {@code
+ * 
+ *     
+ *     
+ * 
+ * }
+ * + *

The {@code LoadTimeWeaverAware} interface

+ * Any bean that implements the {@link + * org.springframework.context.weaving.LoadTimeWeaverAware LoadTimeWeaverAware} interface + * will then receive the {@code LoadTimeWeaver} reference automatically; for example, + * Spring's JPA bootstrap support. + * + *

Customizing the {@code LoadTimeWeaver}

+ * The default weaver is determined automatically. As of Spring 3.1: detecting + * Sun's GlassFish, Oracle's OC4J, Spring's VM agent and any ClassLoader supported by + * Spring's {@link + * org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver + * ReflectiveLoadTimeWeaver} (for example, the {@link + * org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader + * TomcatInstrumentableClassLoader}). + * + *

To customize the weaver used, the {@code @Configuration} class annotated with + * {@code @EnableLoadTimeWeaving} may also implement the {@link LoadTimeWeaverConfigurer} + * interface and return a custom {@code LoadTimeWeaver} instance through the + * {@code #getLoadTimeWeaver} method: + *

+ * @Configuration
+ * @EnableLoadTimeWeaving
+ * public class AppConfig implements LoadTimeWeaverConfigurer {
+ *     @Override
+ *     public LoadTimeWeaver getLoadTimeWeaver() {
+ *         MyLoadTimeWeaver ltw = new MyLoadTimeWeaver();
+ *         ltw.addClassTransformer(myClassFileTransformer);
+ *         // ...
+ *         return ltw;
+ *     }
+ * }
+ * + *

The example above can be compared to the following Spring XML configuration: + *

+ * {@code
+ * 
+ *     
+ * 
+ * }
+ * + *

The code example differs from the XML example in that it actually instantiates the + * {@code MyLoadTimeWeaver} type, meaning that it can also configure the instance, e.g. + * calling the {@code #addClassTransformer} method. This demonstrates how the code-based + * configuration approach is more flexible through direct programmatic access. + * + *

Enabling AspectJ-based weaving

+ * AspectJ load-time weaving may be enabled with the {@link #aspectjWeaving()} + * attribute, which will cause the {@linkplain + * org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter AspectJ class transformer} to + * be registered through {@link LoadTimeWeaver#addTransformer}. AspectJ weaving will be + * activated by default if a "META-INF/aop.xml" resource is present on the classpath. + * Example: + *
+ * @Configuration
+ * @EnableLoadTimeWeaving(aspectjWeaving=ENABLED)
+ * public class AppConfig {
+ * }
+ * + *

The example above can be compared to the following Spring XML configuration: + *

+ * {@code
+ * 
+ *     
+ * 
+ * }
+ * + *

The two examples are equivalent with one significant exception: in the XML case, + * the functionality of {@code } is implicitly enabled when + * {@code aspectj-weaving} is "on". This does not occur when using + * {@code @EnableLoadTimeWeaving(aspectjWeaving=ENABLED)}, although this may change in + * future revisions. + * + * @author Chris Beams + * @since 3.1 + * @see LoadTimeWeaver + * @see DefaultContextLoadTimeWeaver + * @see org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(LoadTimeWeavingConfiguration.class) +public @interface EnableLoadTimeWeaving { + + /** + * Whether AspectJ weaving should be enabled. + */ + AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT; + + public enum AspectJWeaving { + + /** + * Switches on Spring-based AspectJ load-time weaving. + */ + ENABLED, + + /** + * Switches off Spring-based AspectJ load-time weaving (even if a + * "META-INF/aop.xml" resource is present on the classpath). + */ + DISABLED, + + /** + * Switches on AspectJ load-time weaving if a "META-INF/aop.xml" resource + * is present in the classpath. If there is no such resource, then AspectJ + * load-time weaving will be switched off. + */ + AUTODETECT; + } +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java new file mode 100644 index 00000000000..e206a855ad6 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfiguration.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2011 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.context.annotation; + +import static org.springframework.context.weaving.AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE; + +import java.util.Map; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.EnableLoadTimeWeaving.AspectJWeaving; +import org.springframework.context.weaving.AspectJWeavingEnabler; +import org.springframework.context.weaving.DefaultContextLoadTimeWeaver; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.instrument.classloading.LoadTimeWeaver; +import org.springframework.util.Assert; + +/** + * {@code @Configuration} class that registers a {@link LoadTimeWeaver} bean. + * + *

This configuration class is automatically imported when using the @{@link + * EnableLoadTimeWeaving} annotation. See {@code @EnableLoadTimeWeaving} Javadoc for + * complete usage details. + * + * @author Chris Beams + * @since 3.1 + * @see LoadTimeWeavingConfigurer + * @see ConfigurableApplicationContext#LOAD_TIME_WEAVER_BEAN_NAME + */ +@Configuration +public class LoadTimeWeavingConfiguration implements ImportAware, BeanClassLoaderAware { + + private Map enableLTW; + + @Autowired(required=false) + private LoadTimeWeavingConfigurer ltwConfigurer; + + private ClassLoader beanClassLoader; + + public void setImportMetadata(AnnotationMetadata importMetadata) { + this.enableLTW = importMetadata.getAnnotationAttributes(EnableLoadTimeWeaving.class.getName(), false); + Assert.notNull(this.enableLTW, + "@EnableLoadTimeWeaving is not present on importing class " + + importMetadata.getClassName()); + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + @Bean(name=ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public LoadTimeWeaver loadTimeWeaver() { + LoadTimeWeaver loadTimeWeaver = null; + + if (ltwConfigurer != null) { + // the user has provided a custom LTW instance + loadTimeWeaver = ltwConfigurer.getLoadTimeWeaver(); + } + + if (loadTimeWeaver == null) { + // no custom LTW provided -> fall back to the default + loadTimeWeaver = new DefaultContextLoadTimeWeaver(this.beanClassLoader); + } + + switch ((AspectJWeaving) this.enableLTW.get("aspectjWeaving")) { + case DISABLED: + // AJ weaving is disabled -> do nothing + break; + case AUTODETECT: + if (this.beanClassLoader.getResource(ASPECTJ_AOP_XML_RESOURCE) != null) { + // No aop.xml present on the classpath -> treat as 'disabled' + break; + } + // aop.xml is present on the classpath -> fall through and enable + case ENABLED: + AspectJWeavingEnabler.enableAspectJWeaving(loadTimeWeaver, this.beanClassLoader); + } + + return loadTimeWeaver; + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java new file mode 100644 index 00000000000..63322f62966 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/LoadTimeWeavingConfigurer.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2011 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.context.annotation; + +import org.springframework.instrument.classloading.LoadTimeWeaver; + +/** + * Interface to be implemented by @{@link org.springframework.context.annotation.Configuration + * Configuration} classes annotated with @{@link EnableLoadTimeWeaving} that wish to + * customize the {@link LoadTimeWeaver} instance to be used. + * + *

See @{@link EnableAsync} for usage examples and information on how a default + * {@code LoadTimeWeaver} is selected when this interface is not used. + * + * @author Chris Beams + * @since 3.1 + * @see LoadTimeWeavingConfiguration + * @see EnableLoadTimeWeaving + */ +public interface LoadTimeWeavingConfigurer { + + /** + * Create, configure and return the {@code LoadTimeWeaver} instance to be used. Note + * that it is unnecessary to annotate this method with {@code @Bean}, because the + * object returned will automatically be registered as a bean by + * {@link LoadTimeWeavingConfiguration#loadTimeWeaver()} + */ + LoadTimeWeaver getLoadTimeWeaver(); + +} diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java b/org.springframework.context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java index 2da793b3214..5fb8af96b29 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/config/LoadTimeWeaverBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2011 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. @@ -25,6 +25,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.weaving.AspectJWeavingEnabler; import org.springframework.util.ClassUtils; /** @@ -39,8 +40,6 @@ class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionPar private static final String ASPECTJ_WEAVING_ATTRIBUTE = "aspectj-weaving"; - private static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml"; - private static final String DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME = "org.springframework.context.weaving.DefaultContextLoadTimeWeaver"; @@ -86,7 +85,7 @@ class LoadTimeWeaverBeanDefinitionParser extends AbstractSingleBeanDefinitionPar else { // Determine default... ClassLoader cl = parserContext.getReaderContext().getResourceLoader().getClassLoader(); - return (cl.getResource(ASPECTJ_AOP_XML_RESOURCE) != null); + return (cl.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) != null); } } diff --git a/org.springframework.context/src/main/java/org/springframework/context/weaving/AspectJWeavingEnabler.java b/org.springframework.context/src/main/java/org/springframework/context/weaving/AspectJWeavingEnabler.java index 03c51c57fe2..e659b941793 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/weaving/AspectJWeavingEnabler.java +++ b/org.springframework.context/src/main/java/org/springframework/context/weaving/AspectJWeavingEnabler.java @@ -16,12 +16,12 @@ package org.springframework.context.weaving; + import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -47,6 +47,8 @@ public class AspectJWeavingEnabler private LoadTimeWeaver loadTimeWeaver; + public static final String ASPECTJ_AOP_XML_RESOURCE = "META-INF/aop.xml"; + public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; @@ -60,12 +62,14 @@ public class AspectJWeavingEnabler return HIGHEST_PRECEDENCE; } - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - LoadTimeWeaver weaverToUse = this.loadTimeWeaver; + enableAspectJWeaving(this.loadTimeWeaver, this.beanClassLoader); + } + + public static void enableAspectJWeaving(LoadTimeWeaver weaverToUse, ClassLoader beanClassLoader) { if (weaverToUse == null) { if (InstrumentationLoadTimeWeaver.isInstrumentationAvailable()) { - weaverToUse = new InstrumentationLoadTimeWeaver(this.beanClassLoader); + weaverToUse = new InstrumentationLoadTimeWeaver(beanClassLoader); } else { throw new IllegalStateException("No LoadTimeWeaver available"); @@ -76,18 +80,21 @@ public class AspectJWeavingEnabler } - /* - * Decorator to suppress processing AspectJ classes, hence avoiding potential LinkageErrors. - * OC4J and Tomcat (in Glassfish) definitely need such bypassing of AspectJ classes. + /** + * ClassFileTransformer decorator that suppresses processing of AspectJ + * classes in order to avoid potential LinkageErrors. Required especially for OC4J and + * Tomcat (in Glassfish). + * + * @see org.springframework.context.annotation.LoadTimeWeavingConfiguration */ private static class AspectJClassBypassingClassFileTransformer implements ClassFileTransformer { private final ClassFileTransformer delegate; - + public AspectJClassBypassingClassFileTransformer(ClassFileTransformer delegate) { this.delegate = delegate; } - + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { @@ -97,5 +104,4 @@ public class AspectJWeavingEnabler return this.delegate.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); } } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/weaving/DefaultContextLoadTimeWeaver.java b/org.springframework.context/src/main/java/org/springframework/context/weaving/DefaultContextLoadTimeWeaver.java index 9098ecc6fac..68d8db4e038 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/weaving/DefaultContextLoadTimeWeaver.java +++ b/org.springframework.context/src/main/java/org/springframework/context/weaving/DefaultContextLoadTimeWeaver.java @@ -61,6 +61,10 @@ public class DefaultContextLoadTimeWeaver implements LoadTimeWeaver, BeanClassLo private LoadTimeWeaver loadTimeWeaver; + public DefaultContextLoadTimeWeaver(ClassLoader beanClassLoader) { + setBeanClassLoader(beanClassLoader); + } + public void setBeanClassLoader(ClassLoader classLoader) { LoadTimeWeaver serverSpecificLoadTimeWeaver = createServerSpecificLoadTimeWeaver(classLoader); if (serverSpecificLoadTimeWeaver != null) { diff --git a/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd b/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd index a5c8a6c97e8..465ef354de0 100644 --- a/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd +++ b/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd @@ -142,7 +142,7 @@ tag for that purpose. See Javadoc for org.springframework.context.annotation.AnnotationConfigApplicationContext - for information on code-based alternatives to bootstrapping annotation-driven support + for information on code-based alternatives to bootstrapping annotation-driven support. from XML. ]]> @@ -169,7 +169,7 @@ scan settings themselves. See Javadoc for org.springframework.context.annotation.ComponentScan for information - on code-based alternatives to bootstrapping component-scanning from XML. + on code-based alternatives to bootstrapping component-scanning. ]]> @@ -294,6 +294,9 @@ bean factory (typically classes annotated with the @Configurable annotation). This will only happen if the AnnotationBeanConfigurerAspect is on the classpath (i.e. spring-aspects.jar), effectively activating "spring-configured" by default. + + See Javadoc for org.springframework.context.annotation.EnableLoadTimeWeaving + for information on code-based alternatives to bootstrapping load-time weaving support. ]]> diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests-context.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests-context.xml new file mode 100644 index 00000000000..d5d51371646 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests-context.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java new file mode 100644 index 00000000000..33ddc066a7f --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/EnableLoadTimeWeavingTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2011 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.context.annotation; + +import org.junit.Test; +import org.springframework.context.support.GenericXmlApplicationContext; +import org.springframework.instrument.classloading.LoadTimeWeaver; +import org.springframework.instrument.classloading.SimpleLoadTimeWeaver; + +/** + * Unit tests for @EnableLoadTimeWeaving + * + * @author Chris Beams + * @since 3.1 + */ +public class EnableLoadTimeWeavingTests { + + @Test + public void control() { + GenericXmlApplicationContext ctx = + new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml"); + ctx.getBean("loadTimeWeaver"); + } + + @Test + public void test() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(Config.class); + ctx.refresh(); + ctx.getBean("loadTimeWeaver"); + } + + @Configuration + @EnableLoadTimeWeaving + static class Config implements LoadTimeWeavingConfigurer { + + public LoadTimeWeaver getLoadTimeWeaver() { + return new SimpleLoadTimeWeaver(); + } + } +}