diff --git a/build-spring-framework/build.iml b/build-spring-framework/build.iml index 49b4a51471..b5887115b1 100644 --- a/build-spring-framework/build.iml +++ b/build-spring-framework/build.iml @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/build-spring-framework/build.xml b/build-spring-framework/build.xml index d8cbca71f5..dd070acea9 100644 --- a/build-spring-framework/build.xml +++ b/build-spring-framework/build.xml @@ -1,81 +1,81 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.aop/.classpath b/org.springframework.aop/.classpath index 5784a2cf3a..eb916b4144 100644 --- a/org.springframework.aop/.classpath +++ b/org.springframework.aop/.classpath @@ -1,21 +1,21 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + diff --git a/org.springframework.aop/aop.iml b/org.springframework.aop/aop.iml index b650a349da..90a5e38267 100644 --- a/org.springframework.aop/aop.iml +++ b/org.springframework.aop/aop.iml @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.aop/src/main/java/overview.html b/org.springframework.aop/src/main/java/overview.html index d402574024..3c1e198fbd 100644 --- a/org.springframework.aop/src/main/java/overview.html +++ b/org.springframework.aop/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's proxy-based AOP framework. -

- + + +

+Spring's proxy-based AOP framework. +

+ \ No newline at end of file diff --git a/org.springframework.aspects/ivy.xml b/org.springframework.aspects/ivy.xml index be0cfa1b60..cd5e198657 100644 --- a/org.springframework.aspects/ivy.xml +++ b/org.springframework.aspects/ivy.xml @@ -1,44 +1,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.aspects/src/main/java/overview.html b/org.springframework.aspects/src/main/java/overview.html index 1eb7a2e8c1..4edae8742e 100644 --- a/org.springframework.aspects/src/main/java/overview.html +++ b/org.springframework.aspects/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-The Spring Data Binding framework, an internal library used by Spring Web Flow. -

- + + +

+The Spring Data Binding framework, an internal library used by Spring Web Flow. +

+ \ No newline at end of file diff --git a/org.springframework.aspects/src/main/resources/META-INF/aop.xml b/org.springframework.aspects/src/main/resources/META-INF/aop.xml index ae76c4db95..6be5440311 100644 --- a/org.springframework.aspects/src/main/resources/META-INF/aop.xml +++ b/org.springframework.aspects/src/main/resources/META-INF/aop.xml @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/org.springframework.aspects/src/test/java/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml b/org.springframework.aspects/src/test/java/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml index 4af6ec7968..e5f94b8992 100644 --- a/org.springframework.aspects/src/test/java/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml +++ b/org.springframework.aspects/src/test/java/org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml @@ -1,17 +1,17 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/beanConfigurerTests.xml b/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/beanConfigurerTests.xml index 9015415fde..b43254c066 100644 --- a/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/beanConfigurerTests.xml +++ b/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/beanConfigurerTests.xml @@ -1,14 +1,14 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/springConfigured.xml b/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/springConfigured.xml index 1ee36a11da..324f7c3b13 100644 --- a/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/springConfigured.xml +++ b/org.springframework.aspects/src/test/java/org/springframework/beans/factory/aspectj/springConfigured.xml @@ -1,14 +1,14 @@ - - - - - - - - + + + + + + + + diff --git a/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml b/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml index b91de318b0..1fe6a684e7 100644 --- a/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml +++ b/org.springframework.aspects/src/test/java/org/springframework/cache/config/annotation-cache-aspectj.xml @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests-context.xml b/org.springframework.aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests-context.xml index 02ea3f7799..6af0f02db3 100644 --- a/org.springframework.aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests-context.xml +++ b/org.springframework.aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests-context.xml @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/beans.iml b/org.springframework.beans/beans.iml index 641d28b9c7..864ed1a63a 100644 --- a/org.springframework.beans/beans.iml +++ b/org.springframework.beans/beans.iml @@ -1,30 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.beans/src/main/java/overview.html b/org.springframework.beans/src/main/java/overview.html index dec669fcf9..6a29d4bd72 100644 --- a/org.springframework.beans/src/main/java/overview.html +++ b/org.springframework.beans/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's core beans and bean factory support. -

- + + +

+Spring's core beans and bean factory support. +

+ \ No newline at end of file diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml index a57670e789..9a4917c5e9 100644 --- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml +++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/support/security/callbacks.xml @@ -1,93 +1,93 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - foo - bar - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + foo + bar + + + + \ No newline at end of file diff --git a/org.springframework.beans/src/test/resources/com/foo/component-config.xml b/org.springframework.beans/src/test/resources/com/foo/component-config.xml index 33d1223caa..44471ac7e7 100644 --- a/org.springframework.beans/src/test/resources/com/foo/component-config.xml +++ b/org.springframework.beans/src/test/resources/com/foo/component-config.xml @@ -1,17 +1,17 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.beans/src/test/resources/com/foo/component.xsd b/org.springframework.beans/src/test/resources/com/foo/component.xsd index 9868ed0e33..0c33cd4f78 100644 --- a/org.springframework.beans/src/test/resources/com/foo/component.xsd +++ b/org.springframework.beans/src/test/resources/com/foo/component.xsd @@ -1,19 +1,19 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.context.support/.classpath b/org.springframework.context.support/.classpath index c72b7b361a..aa5a57ace0 100644 --- a/org.springframework.context.support/.classpath +++ b/org.springframework.context.support/.classpath @@ -1,33 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context.support/.project b/org.springframework.context.support/.project index 11eeef809b..e58e13be08 100644 --- a/org.springframework.context.support/.project +++ b/org.springframework.context.support/.project @@ -1,23 +1,23 @@ - - - org.springframework.context.support - - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.wst.common.project.facet.core.nature - - + + + org.springframework.context.support + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/org.springframework.context.support/context-support.iml b/org.springframework.context.support/context-support.iml index 8112ea4071..89b372dcf3 100644 --- a/org.springframework.context.support/context-support.iml +++ b/org.springframework.context.support/context-support.iml @@ -1,109 +1,109 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context.support/src/main/java/overview.html b/org.springframework.context.support/src/main/java/overview.html index 6a4824b03a..09300c837a 100644 --- a/org.springframework.context.support/src/main/java/overview.html +++ b/org.springframework.context.support/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Support classes for integrating common third-party libraries into a Spring application context. -

- + + +

+Support classes for integrating common third-party libraries into a Spring application context. +

+ \ No newline at end of file diff --git a/org.springframework.context/.project b/org.springframework.context/.project index ab47436844..db55ff1be7 100644 --- a/org.springframework.context/.project +++ b/org.springframework.context/.project @@ -1,29 +1,29 @@ - - - org.springframework.context - - - - - - org.eclipse.wst.common.project.facet.core.builder - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.springframework.ide.eclipse.core.springbuilder - - - - - - org.springframework.ide.eclipse.core.springnature - org.eclipse.jdt.core.javanature - org.eclipse.wst.common.project.facet.core.nature - - + + + org.springframework.context + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/org.springframework.context/context.iml b/org.springframework.context/context.iml index f9e93f8898..7aa53e9fc0 100644 --- a/org.springframework.context/context.iml +++ b/org.springframework.context/context.iml @@ -1,187 +1,187 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context/src/main/java/org/springframework/cache/ehcache/package-info.java b/org.springframework.context/src/main/java/org/springframework/cache/ehcache/package-info.java index 03d3f6294a..101caa7de7 100644 --- a/org.springframework.context/src/main/java/org/springframework/cache/ehcache/package-info.java +++ b/org.springframework.context/src/main/java/org/springframework/cache/ehcache/package-info.java @@ -1,11 +1,11 @@ - -/** - * - * Support classes for the open source cache - * Ehcache, - * allowing to set up an EHCache CacheManager and Caches - * as beans in a Spring context. - * - */ -package org.springframework.cache.ehcache; - + +/** + * + * Support classes for the open source cache + * Ehcache, + * allowing to set up an EHCache CacheManager and Caches + * as beans in a Spring context. + * + */ +package org.springframework.cache.ehcache; + diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java b/org.springframework.context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java index 51cbc58dfb..488e5008ed 100644 --- a/org.springframework.context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/concurrent/ForkJoinPoolFactoryBean.java @@ -1,108 +1,108 @@ -/* - * 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.scheduling.concurrent; - -import java.util.concurrent.ForkJoinPool; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; - -/** - * A Spring {@link FactoryBean} that builds and exposes a preconfigured {@link ForkJoinPool}. - * May be used on Java 7 as well as on Java 6 with jsr166.jar on the classpath - * (ideally on the VM bootstrap classpath). - * - *

For details on the ForkJoinPool API and its use with RecursiveActions, see the - * JDK 7 javadoc. - * - *

jsr166.jar, containing java.util.concurrent updates for Java 6, can be obtained - * from the concurrency interest website. - * - * @author Juergen Hoeller - * @since 3.1 - */ -public class ForkJoinPoolFactoryBean implements FactoryBean, InitializingBean, DisposableBean { - - private int parallelism = Runtime.getRuntime().availableProcessors(); - - private ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory = ForkJoinPool.defaultForkJoinWorkerThreadFactory; - - private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; - - private boolean asyncMode = false; - - private ForkJoinPool forkJoinPool; - - - /** - * Specify the parallelism level. Default is {@link Runtime#availableProcessors()}. - */ - public void setParallelism(int parallelism) { - this.parallelism = parallelism; - } - - /** - * Set the factory for creating new ForkJoinWorkerThreads. - * Default is {@link ForkJoinPool#defaultForkJoinWorkerThreadFactory}. - */ - public void setThreadFactory(ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory) { - this.threadFactory = threadFactory; - } - - /** - * Set the handler for internal worker threads that terminate due to unrecoverable errors - * encountered while executing tasks. Default is none. - */ - public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { - this.uncaughtExceptionHandler = uncaughtExceptionHandler; - } - - /** - * Specify whether to establish a local first-in-first-out scheduling mode for forked tasks - * that are never joined. This mode (asyncMode = true) may be more appropriate - * than the default locally stack-based mode in applications in which worker threads only - * process event-style asynchronous tasks. Default is false. - */ - public void setAsyncMode(boolean asyncMode) { - this.asyncMode = asyncMode; - } - - public void afterPropertiesSet() { - this.forkJoinPool = - new ForkJoinPool(this.parallelism, this.threadFactory, this.uncaughtExceptionHandler, this.asyncMode); - } - - - public ForkJoinPool getObject() { - return this.forkJoinPool; - } - - public Class getObjectType() { - return ForkJoinPool.class; - } - - public boolean isSingleton() { - return true; - } - - - public void destroy() { - this.forkJoinPool.shutdown(); - } - -} +/* + * 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.scheduling.concurrent; + +import java.util.concurrent.ForkJoinPool; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * A Spring {@link FactoryBean} that builds and exposes a preconfigured {@link ForkJoinPool}. + * May be used on Java 7 as well as on Java 6 with jsr166.jar on the classpath + * (ideally on the VM bootstrap classpath). + * + *

For details on the ForkJoinPool API and its use with RecursiveActions, see the + * JDK 7 javadoc. + * + *

jsr166.jar, containing java.util.concurrent updates for Java 6, can be obtained + * from the concurrency interest website. + * + * @author Juergen Hoeller + * @since 3.1 + */ +public class ForkJoinPoolFactoryBean implements FactoryBean, InitializingBean, DisposableBean { + + private int parallelism = Runtime.getRuntime().availableProcessors(); + + private ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory = ForkJoinPool.defaultForkJoinWorkerThreadFactory; + + private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + private boolean asyncMode = false; + + private ForkJoinPool forkJoinPool; + + + /** + * Specify the parallelism level. Default is {@link Runtime#availableProcessors()}. + */ + public void setParallelism(int parallelism) { + this.parallelism = parallelism; + } + + /** + * Set the factory for creating new ForkJoinWorkerThreads. + * Default is {@link ForkJoinPool#defaultForkJoinWorkerThreadFactory}. + */ + public void setThreadFactory(ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + /** + * Set the handler for internal worker threads that terminate due to unrecoverable errors + * encountered while executing tasks. Default is none. + */ + public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { + this.uncaughtExceptionHandler = uncaughtExceptionHandler; + } + + /** + * Specify whether to establish a local first-in-first-out scheduling mode for forked tasks + * that are never joined. This mode (asyncMode = true) may be more appropriate + * than the default locally stack-based mode in applications in which worker threads only + * process event-style asynchronous tasks. Default is false. + */ + public void setAsyncMode(boolean asyncMode) { + this.asyncMode = asyncMode; + } + + public void afterPropertiesSet() { + this.forkJoinPool = + new ForkJoinPool(this.parallelism, this.threadFactory, this.uncaughtExceptionHandler, this.asyncMode); + } + + + public ForkJoinPool getObject() { + return this.forkJoinPool; + } + + public Class getObjectType() { + return ForkJoinPool.class; + } + + public boolean isSingleton() { + return true; + } + + + public void destroy() { + this.forkJoinPool.shutdown(); + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java b/org.springframework.context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java index f1c1ce9eb5..2e1526ac66 100644 --- a/org.springframework.context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java +++ b/org.springframework.context/src/main/java/org/springframework/scheduling/support/CronSequenceGenerator.java @@ -1,364 +1,364 @@ -/* - * Copyright 2002-2010 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.scheduling.support; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.TimeZone; - -import org.springframework.util.StringUtils; - -/** - * Date sequence generator for a Crontab pattern, - * allowing clients to specify a pattern that the sequence matches. - * - *

The pattern is a list of six single space-separated fields: representing - * second, minute, hour, day, month, weekday. Month and weekday names can be - * given as the first three letters of the English names. - * - *

Example patterns: - *

    - *
  • "0 0 * * * *" = the top of every hour of every day.
  • - *
  • "*/10 * * * * *" = every ten seconds.
  • - *
  • "0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.
  • - *
  • "0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30 and 10 o'clock every day.
  • - *
  • "0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays
  • - *
  • "0 0 0 25 12 ?" = every Christmas Day at midnight
  • - *
- * - * @author Dave Syer - * @author Juergen Hoeller - * @since 3.0 - * @see CronTrigger - */ -public class CronSequenceGenerator { - - private final BitSet seconds = new BitSet(60); - - private final BitSet minutes = new BitSet(60); - - private final BitSet hours = new BitSet(24); - - private final BitSet daysOfWeek = new BitSet(7); - - private final BitSet daysOfMonth = new BitSet(31); - - private final BitSet months = new BitSet(12); - - private final String expression; - - private final TimeZone timeZone; - - /** - * Construct a {@link CronSequenceGenerator} from the pattern provided. - * @param expression a space-separated list of time fields - * @param timeZone the TimeZone to use for generated trigger times - * @throws IllegalArgumentException if the pattern cannot be parsed - */ - public CronSequenceGenerator(String expression, TimeZone timeZone) { - this.expression = expression; - this.timeZone = timeZone; - parse(expression); - } - - /** - * Get the next {@link Date} in the sequence matching the Cron pattern and - * after the value provided. The return value will have a whole number of - * seconds, and will be after the input value. - * @param date a seed value - * @return the next value matching the pattern - */ - public Date next(Date date) { - /* - The plan: - - 1 Round up to the next whole second - - 2 If seconds match move on, otherwise find the next match: - 2.1 If next match is in the next minute then roll forwards - - 3 If minute matches move on, otherwise find the next match - 3.1 If next match is in the next hour then roll forwards - 3.2 Reset the seconds and go to 2 - - 4 If hour matches move on, otherwise find the next match - 4.1 If next match is in the next day then roll forwards, - 4.2 Reset the minutes and seconds and go to 2 - - ... - */ - - Calendar calendar = new GregorianCalendar(); - calendar.setTimeZone(this.timeZone); - calendar.setTime(date); - - // Truncate to the next whole second - calendar.add(Calendar.SECOND, 1); - calendar.set(Calendar.MILLISECOND, 0); - - doNext(calendar, calendar.get(Calendar.YEAR)); - - return calendar.getTime(); - } - - private void doNext(Calendar calendar, int dot) { - List resets = new ArrayList(); - - int second = calendar.get(Calendar.SECOND); - List emptyList = Collections.emptyList(); - int updateSecond = findNext(this.seconds, second, calendar, Calendar.SECOND, Calendar.MINUTE, emptyList); - if (second == updateSecond) { - resets.add(Calendar.SECOND); - } - - int minute = calendar.get(Calendar.MINUTE); - int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets); - if (minute == updateMinute) { - resets.add(Calendar.MINUTE); - } else { - doNext(calendar, dot); - } - - int hour = calendar.get(Calendar.HOUR_OF_DAY); - int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets); - if (hour == updateHour) { - resets.add(Calendar.HOUR_OF_DAY); - } else { - doNext(calendar, dot); - } - - int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); - int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); - int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets); - if (dayOfMonth == updateDayOfMonth) { - resets.add(Calendar.DAY_OF_MONTH); - } else { - doNext(calendar, dot); - } - - int month = calendar.get(Calendar.MONTH); - int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets); - if (month != updateMonth) { - if (calendar.get(Calendar.YEAR) - dot > 4) { - throw new IllegalStateException("Invalid cron expression led to runaway search for next trigger"); - } - doNext(calendar, dot); - } - - } - - private int findNextDay(Calendar calendar, BitSet daysOfMonth, int dayOfMonth, BitSet daysOfWeek, int dayOfWeek, - List resets) { - - int count = 0; - int max = 366; - // the DAY_OF_WEEK values in java.util.Calendar start with 1 (Sunday), - // but in the cron pattern, they start with 0, so we subtract 1 here - while ((!daysOfMonth.get(dayOfMonth) || !daysOfWeek.get(dayOfWeek - 1)) && count++ < max) { - calendar.add(Calendar.DAY_OF_MONTH, 1); - dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); - dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); - reset(calendar, resets); - } - if (count >= max) { - throw new IllegalStateException("Overflow in day for expression=" + this.expression); - } - return dayOfMonth; - } - - /** - * Search the bits provided for the next set bit after the value provided, - * and reset the calendar. - * @param bits a {@link BitSet} representing the allowed values of the field - * @param value the current value of the field - * @param calendar the calendar to increment as we move through the bits - * @param field the field to increment in the calendar (@see - * {@link Calendar} for the static constants defining valid fields) - * @param lowerOrders the Calendar field ids that should be reset (i.e. the - * ones of lower significance than the field of interest) - * @return the value of the calendar field that is next in the sequence - */ - private int findNext(BitSet bits, int value, Calendar calendar, int field, int nextField, List lowerOrders) { - int nextValue = bits.nextSetBit(value); - // roll over if needed - if (nextValue == -1) { - calendar.add(nextField, 1); - reset(calendar, Arrays.asList(field)); - nextValue = bits.nextSetBit(0); - } - if (nextValue != value) { - calendar.set(field, nextValue); - reset(calendar, lowerOrders); - } - return nextValue; - } - - /** - * Reset the calendar setting all the fields provided to zero. - */ - private void reset(Calendar calendar, List fields) { - for (int field : fields) { - calendar.set(field, field == Calendar.DAY_OF_MONTH ? 1 : 0); - } - } - - // Parsing logic invoked by the constructor. - - /** - * Parse the given pattern expression. - */ - private void parse(String expression) throws IllegalArgumentException { - String[] fields = StringUtils.tokenizeToStringArray(expression, " "); - if (fields.length != 6) { - throw new IllegalArgumentException(String.format("" - + "cron expression must consist of 6 fields (found %d in %s)", fields.length, expression)); - } - setNumberHits(this.seconds, fields[0], 0, 60); - setNumberHits(this.minutes, fields[1], 0, 60); - setNumberHits(this.hours, fields[2], 0, 24); - setDaysOfMonth(this.daysOfMonth, fields[3]); - setMonths(this.months, fields[4]); - setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8); - if (this.daysOfWeek.get(7)) { - // Sunday can be represented as 0 or 7 - this.daysOfWeek.set(0); - this.daysOfWeek.clear(7); - } - } - - /** - * Replace the values in the commaSeparatedList (case insensitive) with - * their index in the list. - * @return a new string with the values from the list replaced - */ - private String replaceOrdinals(String value, String commaSeparatedList) { - String[] list = StringUtils.commaDelimitedListToStringArray(commaSeparatedList); - for (int i = 0; i < list.length; i++) { - String item = list[i].toUpperCase(); - value = StringUtils.replace(value.toUpperCase(), item, "" + i); - } - return value; - } - - private void setDaysOfMonth(BitSet bits, String field) { - int max = 31; - // Days of month start with 1 (in Cron and Calendar) so add one - setDays(bits, field, max + 1); - // ... and remove it from the front - bits.clear(0); - } - - private void setDays(BitSet bits, String field, int max) { - if (field.contains("?")) { - field = "*"; - } - setNumberHits(bits, field, 0, max); - } - - private void setMonths(BitSet bits, String value) { - int max = 12; - value = replaceOrdinals(value, "FOO,JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"); - BitSet months = new BitSet(13); - // Months start with 1 in Cron and 0 in Calendar, so push the values first into a longer bit set - setNumberHits(months, value, 1, max + 1); - // ... and then rotate it to the front of the months - for (int i = 1; i <= max; i++) { - if (months.get(i)) { - bits.set(i - 1); - } - } - } - - private void setNumberHits(BitSet bits, String value, int min, int max) { - String[] fields = StringUtils.delimitedListToStringArray(value, ","); - for (String field : fields) { - if (!field.contains("/")) { - // Not an incrementer so it must be a range (possibly empty) - int[] range = getRange(field, min, max); - bits.set(range[0], range[1] + 1); - } else { - String[] split = StringUtils.delimitedListToStringArray(field, "/"); - if (split.length > 2) { - throw new IllegalArgumentException("Incrementer has more than two fields: " + field); - } - int[] range = getRange(split[0], min, max); - if (!split[0].contains("-")) { - range[1] = max - 1; - } - int delta = Integer.valueOf(split[1]); - for (int i = range[0]; i <= range[1]; i += delta) { - bits.set(i); - } - } - } - } - - private int[] getRange(String field, int min, int max) { - int[] result = new int[2]; - if (field.contains("*")) { - result[0] = min; - result[1] = max - 1; - return result; - } - if (!field.contains("-")) { - result[0] = result[1] = Integer.valueOf(field); - } else { - String[] split = StringUtils.delimitedListToStringArray(field, "-"); - if (split.length > 2) { - throw new IllegalArgumentException("Range has more than two fields: " + field); - } - result[0] = Integer.valueOf(split[0]); - result[1] = Integer.valueOf(split[1]); - } - if (result[0] >= max || result[1] >= max) { - throw new IllegalArgumentException("Range exceeds maximum (" + max + "): " + field); - } - if (result[0] < min || result[1] < min) { - throw new IllegalArgumentException("Range less than minimum (" + min + "): " + field); - } - return result; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof CronSequenceGenerator)) { - return false; - } - CronSequenceGenerator cron = (CronSequenceGenerator) obj; - return cron.months.equals(this.months) && cron.daysOfMonth.equals(this.daysOfMonth) - && cron.daysOfWeek.equals(this.daysOfWeek) && cron.hours.equals(this.hours) - && cron.minutes.equals(this.minutes) && cron.seconds.equals(this.seconds); - } - - @Override - public int hashCode() { - return 37 + 17 * this.months.hashCode() + 29 * this.daysOfMonth.hashCode() + 37 * this.daysOfWeek.hashCode() - + 41 * this.hours.hashCode() + 53 * this.minutes.hashCode() + 61 * this.seconds.hashCode(); - } - - @Override - public String toString() { - return getClass().getSimpleName() + ": " + this.expression; - } - -} +/* + * Copyright 2002-2010 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.scheduling.support; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.TimeZone; + +import org.springframework.util.StringUtils; + +/** + * Date sequence generator for a Crontab pattern, + * allowing clients to specify a pattern that the sequence matches. + * + *

The pattern is a list of six single space-separated fields: representing + * second, minute, hour, day, month, weekday. Month and weekday names can be + * given as the first three letters of the English names. + * + *

Example patterns: + *

    + *
  • "0 0 * * * *" = the top of every hour of every day.
  • + *
  • "*/10 * * * * *" = every ten seconds.
  • + *
  • "0 0 8-10 * * *" = 8, 9 and 10 o'clock of every day.
  • + *
  • "0 0/30 8-10 * * *" = 8:00, 8:30, 9:00, 9:30 and 10 o'clock every day.
  • + *
  • "0 0 9-17 * * MON-FRI" = on the hour nine-to-five weekdays
  • + *
  • "0 0 0 25 12 ?" = every Christmas Day at midnight
  • + *
+ * + * @author Dave Syer + * @author Juergen Hoeller + * @since 3.0 + * @see CronTrigger + */ +public class CronSequenceGenerator { + + private final BitSet seconds = new BitSet(60); + + private final BitSet minutes = new BitSet(60); + + private final BitSet hours = new BitSet(24); + + private final BitSet daysOfWeek = new BitSet(7); + + private final BitSet daysOfMonth = new BitSet(31); + + private final BitSet months = new BitSet(12); + + private final String expression; + + private final TimeZone timeZone; + + /** + * Construct a {@link CronSequenceGenerator} from the pattern provided. + * @param expression a space-separated list of time fields + * @param timeZone the TimeZone to use for generated trigger times + * @throws IllegalArgumentException if the pattern cannot be parsed + */ + public CronSequenceGenerator(String expression, TimeZone timeZone) { + this.expression = expression; + this.timeZone = timeZone; + parse(expression); + } + + /** + * Get the next {@link Date} in the sequence matching the Cron pattern and + * after the value provided. The return value will have a whole number of + * seconds, and will be after the input value. + * @param date a seed value + * @return the next value matching the pattern + */ + public Date next(Date date) { + /* + The plan: + + 1 Round up to the next whole second + + 2 If seconds match move on, otherwise find the next match: + 2.1 If next match is in the next minute then roll forwards + + 3 If minute matches move on, otherwise find the next match + 3.1 If next match is in the next hour then roll forwards + 3.2 Reset the seconds and go to 2 + + 4 If hour matches move on, otherwise find the next match + 4.1 If next match is in the next day then roll forwards, + 4.2 Reset the minutes and seconds and go to 2 + + ... + */ + + Calendar calendar = new GregorianCalendar(); + calendar.setTimeZone(this.timeZone); + calendar.setTime(date); + + // Truncate to the next whole second + calendar.add(Calendar.SECOND, 1); + calendar.set(Calendar.MILLISECOND, 0); + + doNext(calendar, calendar.get(Calendar.YEAR)); + + return calendar.getTime(); + } + + private void doNext(Calendar calendar, int dot) { + List resets = new ArrayList(); + + int second = calendar.get(Calendar.SECOND); + List emptyList = Collections.emptyList(); + int updateSecond = findNext(this.seconds, second, calendar, Calendar.SECOND, Calendar.MINUTE, emptyList); + if (second == updateSecond) { + resets.add(Calendar.SECOND); + } + + int minute = calendar.get(Calendar.MINUTE); + int updateMinute = findNext(this.minutes, minute, calendar, Calendar.MINUTE, Calendar.HOUR_OF_DAY, resets); + if (minute == updateMinute) { + resets.add(Calendar.MINUTE); + } else { + doNext(calendar, dot); + } + + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int updateHour = findNext(this.hours, hour, calendar, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK, resets); + if (hour == updateHour) { + resets.add(Calendar.HOUR_OF_DAY); + } else { + doNext(calendar, dot); + } + + int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + int updateDayOfMonth = findNextDay(calendar, this.daysOfMonth, dayOfMonth, daysOfWeek, dayOfWeek, resets); + if (dayOfMonth == updateDayOfMonth) { + resets.add(Calendar.DAY_OF_MONTH); + } else { + doNext(calendar, dot); + } + + int month = calendar.get(Calendar.MONTH); + int updateMonth = findNext(this.months, month, calendar, Calendar.MONTH, Calendar.YEAR, resets); + if (month != updateMonth) { + if (calendar.get(Calendar.YEAR) - dot > 4) { + throw new IllegalStateException("Invalid cron expression led to runaway search for next trigger"); + } + doNext(calendar, dot); + } + + } + + private int findNextDay(Calendar calendar, BitSet daysOfMonth, int dayOfMonth, BitSet daysOfWeek, int dayOfWeek, + List resets) { + + int count = 0; + int max = 366; + // the DAY_OF_WEEK values in java.util.Calendar start with 1 (Sunday), + // but in the cron pattern, they start with 0, so we subtract 1 here + while ((!daysOfMonth.get(dayOfMonth) || !daysOfWeek.get(dayOfWeek - 1)) && count++ < max) { + calendar.add(Calendar.DAY_OF_MONTH, 1); + dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); + reset(calendar, resets); + } + if (count >= max) { + throw new IllegalStateException("Overflow in day for expression=" + this.expression); + } + return dayOfMonth; + } + + /** + * Search the bits provided for the next set bit after the value provided, + * and reset the calendar. + * @param bits a {@link BitSet} representing the allowed values of the field + * @param value the current value of the field + * @param calendar the calendar to increment as we move through the bits + * @param field the field to increment in the calendar (@see + * {@link Calendar} for the static constants defining valid fields) + * @param lowerOrders the Calendar field ids that should be reset (i.e. the + * ones of lower significance than the field of interest) + * @return the value of the calendar field that is next in the sequence + */ + private int findNext(BitSet bits, int value, Calendar calendar, int field, int nextField, List lowerOrders) { + int nextValue = bits.nextSetBit(value); + // roll over if needed + if (nextValue == -1) { + calendar.add(nextField, 1); + reset(calendar, Arrays.asList(field)); + nextValue = bits.nextSetBit(0); + } + if (nextValue != value) { + calendar.set(field, nextValue); + reset(calendar, lowerOrders); + } + return nextValue; + } + + /** + * Reset the calendar setting all the fields provided to zero. + */ + private void reset(Calendar calendar, List fields) { + for (int field : fields) { + calendar.set(field, field == Calendar.DAY_OF_MONTH ? 1 : 0); + } + } + + // Parsing logic invoked by the constructor. + + /** + * Parse the given pattern expression. + */ + private void parse(String expression) throws IllegalArgumentException { + String[] fields = StringUtils.tokenizeToStringArray(expression, " "); + if (fields.length != 6) { + throw new IllegalArgumentException(String.format("" + + "cron expression must consist of 6 fields (found %d in %s)", fields.length, expression)); + } + setNumberHits(this.seconds, fields[0], 0, 60); + setNumberHits(this.minutes, fields[1], 0, 60); + setNumberHits(this.hours, fields[2], 0, 24); + setDaysOfMonth(this.daysOfMonth, fields[3]); + setMonths(this.months, fields[4]); + setDays(this.daysOfWeek, replaceOrdinals(fields[5], "SUN,MON,TUE,WED,THU,FRI,SAT"), 8); + if (this.daysOfWeek.get(7)) { + // Sunday can be represented as 0 or 7 + this.daysOfWeek.set(0); + this.daysOfWeek.clear(7); + } + } + + /** + * Replace the values in the commaSeparatedList (case insensitive) with + * their index in the list. + * @return a new string with the values from the list replaced + */ + private String replaceOrdinals(String value, String commaSeparatedList) { + String[] list = StringUtils.commaDelimitedListToStringArray(commaSeparatedList); + for (int i = 0; i < list.length; i++) { + String item = list[i].toUpperCase(); + value = StringUtils.replace(value.toUpperCase(), item, "" + i); + } + return value; + } + + private void setDaysOfMonth(BitSet bits, String field) { + int max = 31; + // Days of month start with 1 (in Cron and Calendar) so add one + setDays(bits, field, max + 1); + // ... and remove it from the front + bits.clear(0); + } + + private void setDays(BitSet bits, String field, int max) { + if (field.contains("?")) { + field = "*"; + } + setNumberHits(bits, field, 0, max); + } + + private void setMonths(BitSet bits, String value) { + int max = 12; + value = replaceOrdinals(value, "FOO,JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"); + BitSet months = new BitSet(13); + // Months start with 1 in Cron and 0 in Calendar, so push the values first into a longer bit set + setNumberHits(months, value, 1, max + 1); + // ... and then rotate it to the front of the months + for (int i = 1; i <= max; i++) { + if (months.get(i)) { + bits.set(i - 1); + } + } + } + + private void setNumberHits(BitSet bits, String value, int min, int max) { + String[] fields = StringUtils.delimitedListToStringArray(value, ","); + for (String field : fields) { + if (!field.contains("/")) { + // Not an incrementer so it must be a range (possibly empty) + int[] range = getRange(field, min, max); + bits.set(range[0], range[1] + 1); + } else { + String[] split = StringUtils.delimitedListToStringArray(field, "/"); + if (split.length > 2) { + throw new IllegalArgumentException("Incrementer has more than two fields: " + field); + } + int[] range = getRange(split[0], min, max); + if (!split[0].contains("-")) { + range[1] = max - 1; + } + int delta = Integer.valueOf(split[1]); + for (int i = range[0]; i <= range[1]; i += delta) { + bits.set(i); + } + } + } + } + + private int[] getRange(String field, int min, int max) { + int[] result = new int[2]; + if (field.contains("*")) { + result[0] = min; + result[1] = max - 1; + return result; + } + if (!field.contains("-")) { + result[0] = result[1] = Integer.valueOf(field); + } else { + String[] split = StringUtils.delimitedListToStringArray(field, "-"); + if (split.length > 2) { + throw new IllegalArgumentException("Range has more than two fields: " + field); + } + result[0] = Integer.valueOf(split[0]); + result[1] = Integer.valueOf(split[1]); + } + if (result[0] >= max || result[1] >= max) { + throw new IllegalArgumentException("Range exceeds maximum (" + max + "): " + field); + } + if (result[0] < min || result[1] < min) { + throw new IllegalArgumentException("Range less than minimum (" + min + "): " + field); + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CronSequenceGenerator)) { + return false; + } + CronSequenceGenerator cron = (CronSequenceGenerator) obj; + return cron.months.equals(this.months) && cron.daysOfMonth.equals(this.daysOfMonth) + && cron.daysOfWeek.equals(this.daysOfWeek) && cron.hours.equals(this.hours) + && cron.minutes.equals(this.minutes) && cron.seconds.equals(this.seconds); + } + + @Override + public int hashCode() { + return 37 + 17 * this.months.hashCode() + 29 * this.daysOfMonth.hashCode() + 37 * this.daysOfWeek.hashCode() + + 41 * this.hours.hashCode() + 53 * this.minutes.hashCode() + 61 * this.seconds.hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": " + this.expression; + } + +} diff --git a/org.springframework.context/src/main/java/overview.html b/org.springframework.context/src/main/java/overview.html index a0b683199a..a77fbc83ba 100644 --- a/org.springframework.context/src/main/java/overview.html +++ b/org.springframework.context/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's central application context runtime. Also includes scheduling and remoting abstractions. -

- + + +

+Spring's central application context runtime. Also includes scheduling and remoting abstractions. +

+ \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/spr7283.xml b/org.springframework.context/src/test/java/org/springframework/context/support/spr7283.xml index 153462c45e..df0a26a773 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/support/spr7283.xml +++ b/org.springframework.context/src/test/java/org/springframework/context/support/spr7283.xml @@ -1,17 +1,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-dynamic-context.xml b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-dynamic-context.xml index 835f7ba933..f914df2882 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-dynamic-context.xml +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-dynamic-context.xml @@ -1,21 +1,21 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-interface-context.xml b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-interface-context.xml index bd1457c6f3..cfad97c778 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-interface-context.xml +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-interface-context.xml @@ -1,20 +1,20 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-proxy-target-class-context.xml b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-proxy-target-class-context.xml index b483ad880f..66be80b2a8 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-proxy-target-class-context.xml +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-groovy-proxy-target-class-context.xml @@ -1,20 +1,20 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-java-context.xml b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-java-context.xml index ce87a94630..7d0a1dc816 100644 --- a/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-java-context.xml +++ b/org.springframework.context/src/test/java/org/springframework/scripting/groovy/GroovyAspectIntegrationTests-java-context.xml @@ -1,20 +1,20 @@ - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml index f0e83281ee..104a3fbb17 100644 --- a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml +++ b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheConfig.xml @@ -1,47 +1,47 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml index 30127b4352..105a003504 100644 --- a/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml +++ b/org.springframework.context/src/test/resources/org/springframework/cache/config/annotationDrivenCacheNamespace.xml @@ -1,34 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml b/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml index 36201c291d..d9eef4816b 100644 --- a/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml +++ b/org.springframework.context/src/test/resources/org/springframework/cache/config/cache-advice.xml @@ -1,109 +1,109 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.core/.classpath b/org.springframework.core/.classpath index 24218aeeeb..dc1edd94f2 100644 --- a/org.springframework.core/.classpath +++ b/org.springframework.core/.classpath @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.core/core.iml b/org.springframework.core/core.iml index e5edf33be9..de34193430 100644 --- a/org.springframework.core/core.iml +++ b/org.springframework.core/core.iml @@ -1,213 +1,213 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.core/src/main/java/overview.html b/org.springframework.core/src/main/java/overview.html index e6e4f369dd..2acb036f3a 100644 --- a/org.springframework.core/src/main/java/overview.html +++ b/org.springframework.core/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's core utilities, used by many other Spring modules. -

- + + +

+Spring's core utilities, used by many other Spring modules. +

+ \ No newline at end of file diff --git a/org.springframework.expression/.project b/org.springframework.expression/.project index 9d8538bf2b..d6fe11bc7c 100644 --- a/org.springframework.expression/.project +++ b/org.springframework.expression/.project @@ -1,25 +1,25 @@ - - - org.springframework.expression - - - common-build - repository - - - - org.eclipse.jdt.core.javabuilder - - - - - structure101.java.eclipse.plugin.JDMEclipseBuilder - - - - - - org.eclipse.jdt.core.javanature - structure101.java.eclipse.plugin.JDMEclipseNature - - + + + org.springframework.expression + + + common-build + repository + + + + org.eclipse.jdt.core.javabuilder + + + + + structure101.java.eclipse.plugin.JDMEclipseBuilder + + + + + + org.eclipse.jdt.core.javanature + structure101.java.eclipse.plugin.JDMEclipseNature + + diff --git a/org.springframework.expression/expression.iml b/org.springframework.expression/expression.iml index 939acaec99..2838cbdd07 100644 --- a/org.springframework.expression/expression.iml +++ b/org.springframework.expression/expression.iml @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.instrument.tomcat/src/main/java/overview.html b/org.springframework.instrument.tomcat/src/main/java/overview.html index 1eb7a2e8c1..4edae8742e 100644 --- a/org.springframework.instrument.tomcat/src/main/java/overview.html +++ b/org.springframework.instrument.tomcat/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-The Spring Data Binding framework, an internal library used by Spring Web Flow. -

- + + +

+The Spring Data Binding framework, an internal library used by Spring Web Flow. +

+ \ No newline at end of file diff --git a/org.springframework.instrument/instrument.iml b/org.springframework.instrument/instrument.iml index ef0c9607f1..550885dbc3 100644 --- a/org.springframework.instrument/instrument.iml +++ b/org.springframework.instrument/instrument.iml @@ -1,21 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.instrument/src/main/java/overview.html b/org.springframework.instrument/src/main/java/overview.html index d36e9ecefb..33f2071f1a 100644 --- a/org.springframework.instrument/src/main/java/overview.html +++ b/org.springframework.instrument/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's instrumentation agent for JVM bootstrapping. -

- + + +

+Spring's instrumentation agent for JVM bootstrapping. +

+ \ No newline at end of file diff --git a/org.springframework.integration-tests/integration-tests.iml b/org.springframework.integration-tests/integration-tests.iml index 374bd67fbd..634fe03730 100644 --- a/org.springframework.integration-tests/integration-tests.iml +++ b/org.springframework.integration-tests/integration-tests.iml @@ -1,460 +1,460 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.jdbc/jdbc.iml b/org.springframework.jdbc/jdbc.iml index f807eb845a..d5189c40b8 100644 --- a/org.springframework.jdbc/jdbc.iml +++ b/org.springframework.jdbc/jdbc.iml @@ -1,95 +1,95 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.jdbc/src/main/java/overview.html b/org.springframework.jdbc/src/main/java/overview.html index c7b07a561e..32c3392fb5 100644 --- a/org.springframework.jdbc/src/main/java/overview.html +++ b/org.springframework.jdbc/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's JDBC support package. Includes DataSource setup support as well as JDBC access support. -

- + + +

+Spring's JDBC support package. Includes DataSource setup support as well as JDBC access support. +

+ \ No newline at end of file diff --git a/org.springframework.jms/jms.iml b/org.springframework.jms/jms.iml index a19a327b70..e8cb18e3a5 100644 --- a/org.springframework.jms/jms.iml +++ b/org.springframework.jms/jms.iml @@ -1,87 +1,87 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.jms/src/main/java/overview.html b/org.springframework.jms/src/main/java/overview.html index 13e7e7bbd4..53e0238775 100644 --- a/org.springframework.jms/src/main/java/overview.html +++ b/org.springframework.jms/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's JMS support package, covering synchronous JMS access as well as message listener containers. -

- + + +

+Spring's JMS support package, covering synchronous JMS access as well as message listener containers. +

+ \ No newline at end of file diff --git a/org.springframework.orm/orm.iml b/org.springframework.orm/orm.iml index 27ab480268..d880c128d4 100644 --- a/org.springframework.orm/orm.iml +++ b/org.springframework.orm/orm.iml @@ -1,281 +1,281 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.orm/src/main/java/overview.html b/org.springframework.orm/src/main/java/overview.html index a33c6b7c5e..37f532b39b 100644 --- a/org.springframework.orm/src/main/java/overview.html +++ b/org.springframework.orm/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's O/R Mapping package: supporting Hibernate, JPA, JDO, and iBATIS SQL Maps. -

- + + +

+Spring's O/R Mapping package: supporting Hibernate, JPA, JDO, and iBATIS SQL Maps. +

+ \ No newline at end of file diff --git a/org.springframework.spring-parent/build.xml b/org.springframework.spring-parent/build.xml index f23e0c1406..db90f893e4 100644 --- a/org.springframework.spring-parent/build.xml +++ b/org.springframework.spring-parent/build.xml @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/org.springframework.test/src/main/java/overview.html b/org.springframework.test/src/main/java/overview.html index a088abda03..88946a7332 100644 --- a/org.springframework.test/src/main/java/overview.html +++ b/org.springframework.test/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's test context framework. Also includes common Servlet and Portlet API mocks. -

- + + +

+Spring's test context framework. Also includes common Servlet and Portlet API mocks. +

+ \ No newline at end of file diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml b/org.springframework.test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml index f8f0d5eb85..38def23cd7 100644 --- a/org.springframework.test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml +++ b/org.springframework.test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml @@ -1,44 +1,44 @@ - - - - - - - Dave - Andy - - - - - - - - - - #{properties['user.name']} - #{properties['username']} - #{properties[username]} - #{properties.username} - exists - exists also - - - - - - - - - - - - - - + + + + + + + Dave + Andy + + + + + + + + + + #{properties['user.name']} + #{properties['username']} + #{properties[username]} + #{properties.username} + exists + exists also + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.test/test.iml b/org.springframework.test/test.iml index 65ec48e4ae..5fc5636823 100644 --- a/org.springframework.test/test.iml +++ b/org.springframework.test/test.iml @@ -1,135 +1,135 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.transaction/src/main/java/overview.html b/org.springframework.transaction/src/main/java/overview.html index a8378c58b0..3f2136f812 100644 --- a/org.springframework.transaction/src/main/java/overview.html +++ b/org.springframework.transaction/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's transaction infrastructure. Also includes DAO support and JCA integration. -

- + + +

+Spring's transaction infrastructure. Also includes DAO support and JCA integration. +

+ \ No newline at end of file diff --git a/org.springframework.transaction/transaction.iml b/org.springframework.transaction/transaction.iml index ddab1b97b9..e6b0f148d3 100644 --- a/org.springframework.transaction/transaction.iml +++ b/org.springframework.transaction/transaction.iml @@ -1,83 +1,83 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web.portlet/src/main/java/overview.html b/org.springframework.web.portlet/src/main/java/overview.html index c774d266f9..9f31873b6b 100644 --- a/org.springframework.web.portlet/src/main/java/overview.html +++ b/org.springframework.web.portlet/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's MVC framework in its Portlet API version. Includes common Portlet support packages. -

- + + +

+Spring's MVC framework in its Portlet API version. Includes common Portlet support packages. +

+ \ No newline at end of file diff --git a/org.springframework.web.portlet/web-portlet.iml b/org.springframework.web.portlet/web-portlet.iml index e7abff0a9e..0b017fd27d 100644 --- a/org.springframework.web.portlet/web-portlet.iml +++ b/org.springframework.web.portlet/web-portlet.iml @@ -1,62 +1,62 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web.servlet/src/main/java/overview.html b/org.springframework.web.servlet/src/main/java/overview.html index e8c1d8269a..1d338776b4 100644 --- a/org.springframework.web.servlet/src/main/java/overview.html +++ b/org.springframework.web.servlet/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's MVC framework in its Servlet API version. Includes support for common view technologies. -

- + + +

+Spring's MVC framework in its Servlet API version. Includes support for common view technologies. +

+ \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java b/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java index 0762220a77..e868213e05 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -1,570 +1,570 @@ -/* - * Copyright 2002-2010 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.mock.web; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.util.Assert; -import org.springframework.util.LinkedCaseInsensitiveMap; -import org.springframework.web.util.WebUtils; - -/** - * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} - * interface. Supports the Servlet 2.5 API level. - * - *

Used for testing the web framework; also useful for testing - * application controllers. - * - * @author Juergen Hoeller - * @author Rod Johnson - * @since 1.0.2 - */ -public class MockHttpServletResponse implements HttpServletResponse { - - public static final int DEFAULT_SERVER_PORT = 80; - - private static final String CHARSET_PREFIX = "charset="; - - private static final String CONTENT_TYPE_HEADER = "Content-Type"; - - private static final String CONTENT_LENGTH_HEADER = "Content-Length"; - - - //--------------------------------------------------------------------- - // ServletResponse properties - //--------------------------------------------------------------------- - - private boolean outputStreamAccessAllowed = true; - - private boolean writerAccessAllowed = true; - - private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; - - private boolean charset = false; - - private final ByteArrayOutputStream content = new ByteArrayOutputStream(); - - private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content); - - private PrintWriter writer; - - private int contentLength = 0; - - private String contentType; - - private int bufferSize = 4096; - - private boolean committed; - - private Locale locale = Locale.getDefault(); - - - //--------------------------------------------------------------------- - // HttpServletResponse properties - //--------------------------------------------------------------------- - - private final List cookies = new ArrayList(); - - private final Map headers = new LinkedCaseInsensitiveMap(); - - private int status = HttpServletResponse.SC_OK; - - private String errorMessage; - - private String redirectedUrl; - - private String forwardedUrl; - - private final List includedUrls = new ArrayList(); - - - //--------------------------------------------------------------------- - // ServletResponse interface - //--------------------------------------------------------------------- - - /** - * Set whether {@link #getOutputStream()} access is allowed. - *

Default is true. - */ - public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) { - this.outputStreamAccessAllowed = outputStreamAccessAllowed; - } - - /** - * Return whether {@link #getOutputStream()} access is allowed. - */ - public boolean isOutputStreamAccessAllowed() { - return this.outputStreamAccessAllowed; - } - - /** - * Set whether {@link #getWriter()} access is allowed. - *

Default is true. - */ - public void setWriterAccessAllowed(boolean writerAccessAllowed) { - this.writerAccessAllowed = writerAccessAllowed; - } - - /** - * Return whether {@link #getOutputStream()} access is allowed. - */ - public boolean isWriterAccessAllowed() { - return this.writerAccessAllowed; - } - - public void setCharacterEncoding(String characterEncoding) { - this.characterEncoding = characterEncoding; - this.charset = true; - updateContentTypeHeader(); - } - - private void updateContentTypeHeader() { - if (this.contentType != null) { - StringBuilder sb = new StringBuilder(this.contentType); - if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) { - sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding); - } - doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true); - } - } - - public String getCharacterEncoding() { - return this.characterEncoding; - } - - public ServletOutputStream getOutputStream() { - if (!this.outputStreamAccessAllowed) { - throw new IllegalStateException("OutputStream access not allowed"); - } - return this.outputStream; - } - - public PrintWriter getWriter() throws UnsupportedEncodingException { - if (!this.writerAccessAllowed) { - throw new IllegalStateException("Writer access not allowed"); - } - if (this.writer == null) { - Writer targetWriter = (this.characterEncoding != null ? - new OutputStreamWriter(this.content, this.characterEncoding) : new OutputStreamWriter(this.content)); - this.writer = new ResponsePrintWriter(targetWriter); - } - return this.writer; - } - - public byte[] getContentAsByteArray() { - flushBuffer(); - return this.content.toByteArray(); - } - - public String getContentAsString() throws UnsupportedEncodingException { - flushBuffer(); - return (this.characterEncoding != null) ? - this.content.toString(this.characterEncoding) : this.content.toString(); - } - - public void setContentLength(int contentLength) { - this.contentLength = contentLength; - doAddHeaderValue(CONTENT_LENGTH_HEADER, contentLength, true); - } - - public int getContentLength() { - return this.contentLength; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - if (contentType != null) { - int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); - if (charsetIndex != -1) { - String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); - this.characterEncoding = encoding; - this.charset = true; - } - updateContentTypeHeader(); - } - } - - public String getContentType() { - return this.contentType; - } - - public void setBufferSize(int bufferSize) { - this.bufferSize = bufferSize; - } - - public int getBufferSize() { - return this.bufferSize; - } - - public void flushBuffer() { - setCommitted(true); - } - - public void resetBuffer() { - if (isCommitted()) { - throw new IllegalStateException("Cannot reset buffer - response is already committed"); - } - this.content.reset(); - } - - private void setCommittedIfBufferSizeExceeded() { - int bufSize = getBufferSize(); - if (bufSize > 0 && this.content.size() > bufSize) { - setCommitted(true); - } - } - - public void setCommitted(boolean committed) { - this.committed = committed; - } - - public boolean isCommitted() { - return this.committed; - } - - public void reset() { - resetBuffer(); - this.characterEncoding = null; - this.contentLength = 0; - this.contentType = null; - this.locale = null; - this.cookies.clear(); - this.headers.clear(); - this.status = HttpServletResponse.SC_OK; - this.errorMessage = null; - } - - public void setLocale(Locale locale) { - this.locale = locale; - } - - public Locale getLocale() { - return this.locale; - } - - - //--------------------------------------------------------------------- - // HttpServletResponse interface - //--------------------------------------------------------------------- - - public void addCookie(Cookie cookie) { - Assert.notNull(cookie, "Cookie must not be null"); - this.cookies.add(cookie); - } - - public Cookie[] getCookies() { - return this.cookies.toArray(new Cookie[this.cookies.size()]); - } - - public Cookie getCookie(String name) { - Assert.notNull(name, "Cookie name must not be null"); - for (Cookie cookie : this.cookies) { - if (name.equals(cookie.getName())) { - return cookie; - } - } - return null; - } - - public boolean containsHeader(String name) { - return (HeaderValueHolder.getByName(this.headers, name) != null); - } - - /** - * Return the names of all specified headers as a Set of Strings. - * @return the Set of header name Strings, or an empty Set if none - */ - public Set getHeaderNames() { - return this.headers.keySet(); - } - - /** - * Return the primary value for the given header, if any. - *

Will return the first value in case of multiple values. - * @param name the name of the header - * @return the associated header value, or null if none - */ - public String getHeader(String name) { - HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - return (header != null ? header.getValue().toString() : null); - } - - /** - * Return all values for the given header as a List of value objects. - * @param name the name of the header - * @return the associated header values, or an empty List if none - */ - public List getHeaders(String name) { - HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - return (header != null ? header.getStringValues() : Collections.emptyList()); - } - - /** - * The default implementation returns the given URL String as-is. - *

Can be overridden in subclasses, appending a session id or the like. - */ - public String encodeURL(String url) { - return url; - } - - /** - * The default implementation delegates to {@link #encodeURL}, - * returning the given URL String as-is. - *

Can be overridden in subclasses, appending a session id or the like - * in a redirect-specific fashion. For general URL encoding rules, - * override the common {@link #encodeURL} method instead, appyling - * to redirect URLs as well as to general URLs. - */ - public String encodeRedirectURL(String url) { - return encodeURL(url); - } - - public String encodeUrl(String url) { - return encodeURL(url); - } - - public String encodeRedirectUrl(String url) { - return encodeRedirectURL(url); - } - - public void sendError(int status, String errorMessage) throws IOException { - if (isCommitted()) { - throw new IllegalStateException("Cannot set error status - response is already committed"); - } - this.status = status; - this.errorMessage = errorMessage; - setCommitted(true); - } - - public void sendError(int status) throws IOException { - if (isCommitted()) { - throw new IllegalStateException("Cannot set error status - response is already committed"); - } - this.status = status; - setCommitted(true); - } - - public void sendRedirect(String url) throws IOException { - if (isCommitted()) { - throw new IllegalStateException("Cannot send redirect - response is already committed"); - } - Assert.notNull(url, "Redirect URL must not be null"); - this.redirectedUrl = url; - setCommitted(true); - } - - public String getRedirectedUrl() { - return this.redirectedUrl; - } - - public void setDateHeader(String name, long value) { - setHeaderValue(name, value); - } - - public void addDateHeader(String name, long value) { - addHeaderValue(name, value); - } - - public void setHeader(String name, String value) { - setHeaderValue(name, value); - } - - public void addHeader(String name, String value) { - addHeaderValue(name, value); - } - - public void setIntHeader(String name, int value) { - setHeaderValue(name, value); - } - - public void addIntHeader(String name, int value) { - addHeaderValue(name, value); - } - - private void setHeaderValue(String name, Object value) { - if (setSpecialHeader(name, value)) { - return; - } - doAddHeaderValue(name, value, true); - } - - private void addHeaderValue(String name, Object value) { - if (setSpecialHeader(name, value)) { - return; - } - doAddHeaderValue(name, value, false); - } - - private boolean setSpecialHeader(String name, Object value) { - if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) { - setContentType((String) value); - return true; - } - else if (CONTENT_LENGTH_HEADER.equalsIgnoreCase(name)) { - setContentLength(Integer.parseInt((String) value)); - return true; - } - else { - return false; - } - } - - private void doAddHeaderValue(String name, Object value, boolean replace) { - HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - Assert.notNull(value, "Header value must not be null"); - if (header == null) { - header = new HeaderValueHolder(); - this.headers.put(name, header); - } - if (replace) { - header.setValue(value); - } - else { - header.addValue(value); - } - } - - public void setStatus(int status) { - this.status = status; - } - - public void setStatus(int status, String errorMessage) { - this.status = status; - this.errorMessage = errorMessage; - } - - public int getStatus() { - return this.status; - } - - public String getErrorMessage() { - return this.errorMessage; - } - - - //--------------------------------------------------------------------- - // Methods for MockRequestDispatcher - //--------------------------------------------------------------------- - - public void setForwardedUrl(String forwardedUrl) { - this.forwardedUrl = forwardedUrl; - } - - public String getForwardedUrl() { - return this.forwardedUrl; - } - - public void setIncludedUrl(String includedUrl) { - this.includedUrls.clear(); - if (includedUrl != null) { - this.includedUrls.add(includedUrl); - } - } - - public String getIncludedUrl() { - int count = this.includedUrls.size(); - if (count > 1) { - throw new IllegalStateException( - "More than 1 URL included - check getIncludedUrls instead: " + this.includedUrls); - } - return (count == 1 ? this.includedUrls.get(0) : null); - } - - public void addIncludedUrl(String includedUrl) { - Assert.notNull(includedUrl, "Included URL must not be null"); - this.includedUrls.add(includedUrl); - } - - public List getIncludedUrls() { - return this.includedUrls; - } - - - /** - * Inner class that adapts the ServletOutputStream to mark the - * response as committed once the buffer size is exceeded. - */ - private class ResponseServletOutputStream extends DelegatingServletOutputStream { - - public ResponseServletOutputStream(OutputStream out) { - super(out); - } - - public void write(int b) throws IOException { - super.write(b); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void flush() throws IOException { - super.flush(); - setCommitted(true); - } - } - - - /** - * Inner class that adapts the PrintWriter to mark the - * response as committed once the buffer size is exceeded. - */ - private class ResponsePrintWriter extends PrintWriter { - - public ResponsePrintWriter(Writer out) { - super(out, true); - } - - public void write(char buf[], int off, int len) { - super.write(buf, off, len); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void write(String s, int off, int len) { - super.write(s, off, len); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void write(int c) { - super.write(c); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void flush() { - super.flush(); - setCommitted(true); - } - } - -} +/* + * Copyright 2002-2010 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.mock.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.Assert; +import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.web.util.WebUtils; + +/** + * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} + * interface. Supports the Servlet 2.5 API level. + * + *

Used for testing the web framework; also useful for testing + * application controllers. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 1.0.2 + */ +public class MockHttpServletResponse implements HttpServletResponse { + + public static final int DEFAULT_SERVER_PORT = 80; + + private static final String CHARSET_PREFIX = "charset="; + + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + + private static final String CONTENT_LENGTH_HEADER = "Content-Length"; + + + //--------------------------------------------------------------------- + // ServletResponse properties + //--------------------------------------------------------------------- + + private boolean outputStreamAccessAllowed = true; + + private boolean writerAccessAllowed = true; + + private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; + + private boolean charset = false; + + private final ByteArrayOutputStream content = new ByteArrayOutputStream(); + + private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content); + + private PrintWriter writer; + + private int contentLength = 0; + + private String contentType; + + private int bufferSize = 4096; + + private boolean committed; + + private Locale locale = Locale.getDefault(); + + + //--------------------------------------------------------------------- + // HttpServletResponse properties + //--------------------------------------------------------------------- + + private final List cookies = new ArrayList(); + + private final Map headers = new LinkedCaseInsensitiveMap(); + + private int status = HttpServletResponse.SC_OK; + + private String errorMessage; + + private String redirectedUrl; + + private String forwardedUrl; + + private final List includedUrls = new ArrayList(); + + + //--------------------------------------------------------------------- + // ServletResponse interface + //--------------------------------------------------------------------- + + /** + * Set whether {@link #getOutputStream()} access is allowed. + *

Default is true. + */ + public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) { + this.outputStreamAccessAllowed = outputStreamAccessAllowed; + } + + /** + * Return whether {@link #getOutputStream()} access is allowed. + */ + public boolean isOutputStreamAccessAllowed() { + return this.outputStreamAccessAllowed; + } + + /** + * Set whether {@link #getWriter()} access is allowed. + *

Default is true. + */ + public void setWriterAccessAllowed(boolean writerAccessAllowed) { + this.writerAccessAllowed = writerAccessAllowed; + } + + /** + * Return whether {@link #getOutputStream()} access is allowed. + */ + public boolean isWriterAccessAllowed() { + return this.writerAccessAllowed; + } + + public void setCharacterEncoding(String characterEncoding) { + this.characterEncoding = characterEncoding; + this.charset = true; + updateContentTypeHeader(); + } + + private void updateContentTypeHeader() { + if (this.contentType != null) { + StringBuilder sb = new StringBuilder(this.contentType); + if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) { + sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding); + } + doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true); + } + } + + public String getCharacterEncoding() { + return this.characterEncoding; + } + + public ServletOutputStream getOutputStream() { + if (!this.outputStreamAccessAllowed) { + throw new IllegalStateException("OutputStream access not allowed"); + } + return this.outputStream; + } + + public PrintWriter getWriter() throws UnsupportedEncodingException { + if (!this.writerAccessAllowed) { + throw new IllegalStateException("Writer access not allowed"); + } + if (this.writer == null) { + Writer targetWriter = (this.characterEncoding != null ? + new OutputStreamWriter(this.content, this.characterEncoding) : new OutputStreamWriter(this.content)); + this.writer = new ResponsePrintWriter(targetWriter); + } + return this.writer; + } + + public byte[] getContentAsByteArray() { + flushBuffer(); + return this.content.toByteArray(); + } + + public String getContentAsString() throws UnsupportedEncodingException { + flushBuffer(); + return (this.characterEncoding != null) ? + this.content.toString(this.characterEncoding) : this.content.toString(); + } + + public void setContentLength(int contentLength) { + this.contentLength = contentLength; + doAddHeaderValue(CONTENT_LENGTH_HEADER, contentLength, true); + } + + public int getContentLength() { + return this.contentLength; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + if (contentType != null) { + int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); + if (charsetIndex != -1) { + String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); + this.characterEncoding = encoding; + this.charset = true; + } + updateContentTypeHeader(); + } + } + + public String getContentType() { + return this.contentType; + } + + public void setBufferSize(int bufferSize) { + this.bufferSize = bufferSize; + } + + public int getBufferSize() { + return this.bufferSize; + } + + public void flushBuffer() { + setCommitted(true); + } + + public void resetBuffer() { + if (isCommitted()) { + throw new IllegalStateException("Cannot reset buffer - response is already committed"); + } + this.content.reset(); + } + + private void setCommittedIfBufferSizeExceeded() { + int bufSize = getBufferSize(); + if (bufSize > 0 && this.content.size() > bufSize) { + setCommitted(true); + } + } + + public void setCommitted(boolean committed) { + this.committed = committed; + } + + public boolean isCommitted() { + return this.committed; + } + + public void reset() { + resetBuffer(); + this.characterEncoding = null; + this.contentLength = 0; + this.contentType = null; + this.locale = null; + this.cookies.clear(); + this.headers.clear(); + this.status = HttpServletResponse.SC_OK; + this.errorMessage = null; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } + + public Locale getLocale() { + return this.locale; + } + + + //--------------------------------------------------------------------- + // HttpServletResponse interface + //--------------------------------------------------------------------- + + public void addCookie(Cookie cookie) { + Assert.notNull(cookie, "Cookie must not be null"); + this.cookies.add(cookie); + } + + public Cookie[] getCookies() { + return this.cookies.toArray(new Cookie[this.cookies.size()]); + } + + public Cookie getCookie(String name) { + Assert.notNull(name, "Cookie name must not be null"); + for (Cookie cookie : this.cookies) { + if (name.equals(cookie.getName())) { + return cookie; + } + } + return null; + } + + public boolean containsHeader(String name) { + return (HeaderValueHolder.getByName(this.headers, name) != null); + } + + /** + * Return the names of all specified headers as a Set of Strings. + * @return the Set of header name Strings, or an empty Set if none + */ + public Set getHeaderNames() { + return this.headers.keySet(); + } + + /** + * Return the primary value for the given header, if any. + *

Will return the first value in case of multiple values. + * @param name the name of the header + * @return the associated header value, or null if none + */ + public String getHeader(String name) { + HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); + return (header != null ? header.getValue().toString() : null); + } + + /** + * Return all values for the given header as a List of value objects. + * @param name the name of the header + * @return the associated header values, or an empty List if none + */ + public List getHeaders(String name) { + HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); + return (header != null ? header.getStringValues() : Collections.emptyList()); + } + + /** + * The default implementation returns the given URL String as-is. + *

Can be overridden in subclasses, appending a session id or the like. + */ + public String encodeURL(String url) { + return url; + } + + /** + * The default implementation delegates to {@link #encodeURL}, + * returning the given URL String as-is. + *

Can be overridden in subclasses, appending a session id or the like + * in a redirect-specific fashion. For general URL encoding rules, + * override the common {@link #encodeURL} method instead, appyling + * to redirect URLs as well as to general URLs. + */ + public String encodeRedirectURL(String url) { + return encodeURL(url); + } + + public String encodeUrl(String url) { + return encodeURL(url); + } + + public String encodeRedirectUrl(String url) { + return encodeRedirectURL(url); + } + + public void sendError(int status, String errorMessage) throws IOException { + if (isCommitted()) { + throw new IllegalStateException("Cannot set error status - response is already committed"); + } + this.status = status; + this.errorMessage = errorMessage; + setCommitted(true); + } + + public void sendError(int status) throws IOException { + if (isCommitted()) { + throw new IllegalStateException("Cannot set error status - response is already committed"); + } + this.status = status; + setCommitted(true); + } + + public void sendRedirect(String url) throws IOException { + if (isCommitted()) { + throw new IllegalStateException("Cannot send redirect - response is already committed"); + } + Assert.notNull(url, "Redirect URL must not be null"); + this.redirectedUrl = url; + setCommitted(true); + } + + public String getRedirectedUrl() { + return this.redirectedUrl; + } + + public void setDateHeader(String name, long value) { + setHeaderValue(name, value); + } + + public void addDateHeader(String name, long value) { + addHeaderValue(name, value); + } + + public void setHeader(String name, String value) { + setHeaderValue(name, value); + } + + public void addHeader(String name, String value) { + addHeaderValue(name, value); + } + + public void setIntHeader(String name, int value) { + setHeaderValue(name, value); + } + + public void addIntHeader(String name, int value) { + addHeaderValue(name, value); + } + + private void setHeaderValue(String name, Object value) { + if (setSpecialHeader(name, value)) { + return; + } + doAddHeaderValue(name, value, true); + } + + private void addHeaderValue(String name, Object value) { + if (setSpecialHeader(name, value)) { + return; + } + doAddHeaderValue(name, value, false); + } + + private boolean setSpecialHeader(String name, Object value) { + if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) { + setContentType((String) value); + return true; + } + else if (CONTENT_LENGTH_HEADER.equalsIgnoreCase(name)) { + setContentLength(Integer.parseInt((String) value)); + return true; + } + else { + return false; + } + } + + private void doAddHeaderValue(String name, Object value, boolean replace) { + HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); + Assert.notNull(value, "Header value must not be null"); + if (header == null) { + header = new HeaderValueHolder(); + this.headers.put(name, header); + } + if (replace) { + header.setValue(value); + } + else { + header.addValue(value); + } + } + + public void setStatus(int status) { + this.status = status; + } + + public void setStatus(int status, String errorMessage) { + this.status = status; + this.errorMessage = errorMessage; + } + + public int getStatus() { + return this.status; + } + + public String getErrorMessage() { + return this.errorMessage; + } + + + //--------------------------------------------------------------------- + // Methods for MockRequestDispatcher + //--------------------------------------------------------------------- + + public void setForwardedUrl(String forwardedUrl) { + this.forwardedUrl = forwardedUrl; + } + + public String getForwardedUrl() { + return this.forwardedUrl; + } + + public void setIncludedUrl(String includedUrl) { + this.includedUrls.clear(); + if (includedUrl != null) { + this.includedUrls.add(includedUrl); + } + } + + public String getIncludedUrl() { + int count = this.includedUrls.size(); + if (count > 1) { + throw new IllegalStateException( + "More than 1 URL included - check getIncludedUrls instead: " + this.includedUrls); + } + return (count == 1 ? this.includedUrls.get(0) : null); + } + + public void addIncludedUrl(String includedUrl) { + Assert.notNull(includedUrl, "Included URL must not be null"); + this.includedUrls.add(includedUrl); + } + + public List getIncludedUrls() { + return this.includedUrls; + } + + + /** + * Inner class that adapts the ServletOutputStream to mark the + * response as committed once the buffer size is exceeded. + */ + private class ResponseServletOutputStream extends DelegatingServletOutputStream { + + public ResponseServletOutputStream(OutputStream out) { + super(out); + } + + public void write(int b) throws IOException { + super.write(b); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void flush() throws IOException { + super.flush(); + setCommitted(true); + } + } + + + /** + * Inner class that adapts the PrintWriter to mark the + * response as committed once the buffer size is exceeded. + */ + private class ResponsePrintWriter extends PrintWriter { + + public ResponsePrintWriter(Writer out) { + super(out, true); + } + + public void write(char buf[], int off, int len) { + super.write(buf, off, len); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void write(String s, int off, int len) { + super.write(s, off, len); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void write(int c) { + super.write(c); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void flush() { + super.flush(); + setCommitted(true); + } + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/tiles2/tiles-definitions.xml b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/tiles2/tiles-definitions.xml index 0b18f927be..8aac5812da 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/tiles2/tiles-definitions.xml +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/tiles2/tiles-definitions.xml @@ -1,10 +1,10 @@ - - - - - - - - + + + + + + + + diff --git a/org.springframework.web.servlet/web-servlet.iml b/org.springframework.web.servlet/web-servlet.iml index e81b004687..172c522193 100644 --- a/org.springframework.web.servlet/web-servlet.iml +++ b/org.springframework.web.servlet/web-servlet.iml @@ -1,365 +1,365 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web.struts/ivy.xml b/org.springframework.web.struts/ivy.xml index 78a6de187c..47e1dfb082 100644 --- a/org.springframework.web.struts/ivy.xml +++ b/org.springframework.web.struts/ivy.xml @@ -1,53 +1,53 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web/src/main/java/overview.html b/org.springframework.web/src/main/java/overview.html index e5807a2922..cef93c7002 100644 --- a/org.springframework.web/src/main/java/overview.html +++ b/org.springframework.web/src/main/java/overview.html @@ -1,7 +1,7 @@ - - -

-Spring's core web support packages, for any kind of web environment. -

- + + +

+Spring's core web support packages, for any kind of web environment. +

+ \ No newline at end of file diff --git a/org.springframework.web/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java b/org.springframework.web/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java index fa07a8e954..baf0ad60b2 100644 --- a/org.springframework.web/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/org.springframework.web/src/test/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -1,569 +1,569 @@ -/* - * 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.mock.web; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.util.Assert; -import org.springframework.util.LinkedCaseInsensitiveMap; -import org.springframework.web.util.WebUtils; - -/** - * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} - * interface. Supports the Servlet 3.0 API level - * - *

Used for testing the web framework; also useful for testing - * application controllers. - * - * @author Juergen Hoeller - * @author Rod Johnson - * @since 1.0.2 - */ -public class MockHttpServletResponse implements HttpServletResponse { - - public static final int DEFAULT_SERVER_PORT = 80; - - private static final String CHARSET_PREFIX = "charset="; - - private static final String CONTENT_TYPE_HEADER = "Content-Type"; - - private static final String CONTENT_LENGTH_HEADER = "Content-Length"; - - - //--------------------------------------------------------------------- - // ServletResponse properties - //--------------------------------------------------------------------- - - private boolean outputStreamAccessAllowed = true; - - private boolean writerAccessAllowed = true; - - private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; - - private boolean charset = false; - - private final ByteArrayOutputStream content = new ByteArrayOutputStream(); - - private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content); - - private PrintWriter writer; - - private int contentLength = 0; - - private String contentType; - - private int bufferSize = 4096; - - private boolean committed; - - private Locale locale = Locale.getDefault(); - - - //--------------------------------------------------------------------- - // HttpServletResponse properties - //--------------------------------------------------------------------- - - private final List cookies = new ArrayList(); - - private final Map headers = new LinkedCaseInsensitiveMap(); - - private int status = HttpServletResponse.SC_OK; - - private String errorMessage; - - private String redirectedUrl; - - private String forwardedUrl; - - private final List includedUrls = new ArrayList(); - - - //--------------------------------------------------------------------- - // ServletResponse interface - //--------------------------------------------------------------------- - - /** - * Set whether {@link #getOutputStream()} access is allowed. - *

Default is true. - */ - public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) { - this.outputStreamAccessAllowed = outputStreamAccessAllowed; - } - - /** - * Return whether {@link #getOutputStream()} access is allowed. - */ - public boolean isOutputStreamAccessAllowed() { - return this.outputStreamAccessAllowed; - } - - /** - * Set whether {@link #getWriter()} access is allowed. - *

Default is true. - */ - public void setWriterAccessAllowed(boolean writerAccessAllowed) { - this.writerAccessAllowed = writerAccessAllowed; - } - - /** - * Return whether {@link #getOutputStream()} access is allowed. - */ - public boolean isWriterAccessAllowed() { - return this.writerAccessAllowed; - } - - public void setCharacterEncoding(String characterEncoding) { - this.characterEncoding = characterEncoding; - this.charset = true; - updateContentTypeHeader(); - } - - private void updateContentTypeHeader() { - if (this.contentType != null) { - StringBuilder sb = new StringBuilder(this.contentType); - if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) { - sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding); - } - doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true); - } - } - - public String getCharacterEncoding() { - return this.characterEncoding; - } - - public ServletOutputStream getOutputStream() { - if (!this.outputStreamAccessAllowed) { - throw new IllegalStateException("OutputStream access not allowed"); - } - return this.outputStream; - } - - public PrintWriter getWriter() throws UnsupportedEncodingException { - if (!this.writerAccessAllowed) { - throw new IllegalStateException("Writer access not allowed"); - } - if (this.writer == null) { - Writer targetWriter = (this.characterEncoding != null ? - new OutputStreamWriter(this.content, this.characterEncoding) : new OutputStreamWriter(this.content)); - this.writer = new ResponsePrintWriter(targetWriter); - } - return this.writer; - } - - public byte[] getContentAsByteArray() { - flushBuffer(); - return this.content.toByteArray(); - } - - public String getContentAsString() throws UnsupportedEncodingException { - flushBuffer(); - return (this.characterEncoding != null) ? - this.content.toString(this.characterEncoding) : this.content.toString(); - } - - public void setContentLength(int contentLength) { - this.contentLength = contentLength; - doAddHeaderValue(CONTENT_LENGTH_HEADER, contentLength, true); - } - - public int getContentLength() { - return this.contentLength; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - if (contentType != null) { - int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); - if (charsetIndex != -1) { - String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); - this.characterEncoding = encoding; - this.charset = true; - } - updateContentTypeHeader(); - } - } - - public String getContentType() { - return this.contentType; - } - - public void setBufferSize(int bufferSize) { - this.bufferSize = bufferSize; - } - - public int getBufferSize() { - return this.bufferSize; - } - - public void flushBuffer() { - setCommitted(true); - } - - public void resetBuffer() { - if (isCommitted()) { - throw new IllegalStateException("Cannot reset buffer - response is already committed"); - } - this.content.reset(); - } - - private void setCommittedIfBufferSizeExceeded() { - int bufSize = getBufferSize(); - if (bufSize > 0 && this.content.size() > bufSize) { - setCommitted(true); - } - } - - public void setCommitted(boolean committed) { - this.committed = committed; - } - - public boolean isCommitted() { - return this.committed; - } - - public void reset() { - resetBuffer(); - this.characterEncoding = null; - this.contentLength = 0; - this.contentType = null; - this.locale = null; - this.cookies.clear(); - this.headers.clear(); - this.status = HttpServletResponse.SC_OK; - this.errorMessage = null; - } - - public void setLocale(Locale locale) { - this.locale = locale; - } - - public Locale getLocale() { - return this.locale; - } - - - //--------------------------------------------------------------------- - // HttpServletResponse interface - //--------------------------------------------------------------------- - - public void addCookie(Cookie cookie) { - Assert.notNull(cookie, "Cookie must not be null"); - this.cookies.add(cookie); - } - - public Cookie[] getCookies() { - return this.cookies.toArray(new Cookie[this.cookies.size()]); - } - - public Cookie getCookie(String name) { - Assert.notNull(name, "Cookie name must not be null"); - for (Cookie cookie : this.cookies) { - if (name.equals(cookie.getName())) { - return cookie; - } - } - return null; - } - - public boolean containsHeader(String name) { - return (HeaderValueHolder.getByName(this.headers, name) != null); - } - - /** - * Return the names of all specified headers as a Set of Strings. - * @return the Set of header name Strings, or an empty Set if none - */ - public Set getHeaderNames() { - return this.headers.keySet(); - } - - /** - * Return the primary value for the given header, if any. - *

Will return the first value in case of multiple values. - * @param name the name of the header - * @return the associated header value, or null if none - */ - public String getHeader(String name) { - HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - return (header != null ? header.getValue().toString() : null); - } - - /** - * Return all values for the given header as a List of value objects. - * @param name the name of the header - * @return the associated header values, or an empty List if none - */ - public List getHeaders(String name) { - HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - return (header != null ? header.getStringValues() : Collections.emptyList()); - } - - /** - * The default implementation returns the given URL String as-is. - *

Can be overridden in subclasses, appending a session id or the like. - */ - public String encodeURL(String url) { - return url; - } - - /** - * The default implementation delegates to {@link #encodeURL}, - * returning the given URL String as-is. - *

Can be overridden in subclasses, appending a session id or the like - * in a redirect-specific fashion. For general URL encoding rules, - * override the common {@link #encodeURL} method instead, appyling - * to redirect URLs as well as to general URLs. - */ - public String encodeRedirectURL(String url) { - return encodeURL(url); - } - - public String encodeUrl(String url) { - return encodeURL(url); - } - - public String encodeRedirectUrl(String url) { - return encodeRedirectURL(url); - } - - public void sendError(int status, String errorMessage) throws IOException { - if (isCommitted()) { - throw new IllegalStateException("Cannot set error status - response is already committed"); - } - this.status = status; - this.errorMessage = errorMessage; - setCommitted(true); - } - - public void sendError(int status) throws IOException { - if (isCommitted()) { - throw new IllegalStateException("Cannot set error status - response is already committed"); - } - this.status = status; - setCommitted(true); - } - - public void sendRedirect(String url) throws IOException { - if (isCommitted()) { - throw new IllegalStateException("Cannot send redirect - response is already committed"); - } - Assert.notNull(url, "Redirect URL must not be null"); - this.redirectedUrl = url; - setCommitted(true); - } - - public String getRedirectedUrl() { - return this.redirectedUrl; - } - - public void setDateHeader(String name, long value) { - setHeaderValue(name, value); - } - - public void addDateHeader(String name, long value) { - addHeaderValue(name, value); - } - - public void setHeader(String name, String value) { - setHeaderValue(name, value); - } - - public void addHeader(String name, String value) { - addHeaderValue(name, value); - } - - public void setIntHeader(String name, int value) { - setHeaderValue(name, value); - } - - public void addIntHeader(String name, int value) { - addHeaderValue(name, value); - } - - private void setHeaderValue(String name, Object value) { - if (setSpecialHeader(name, value)) { - return; - } - doAddHeaderValue(name, value, true); - } - - private void addHeaderValue(String name, Object value) { - if (setSpecialHeader(name, value)) { - return; - } - doAddHeaderValue(name, value, false); - } - - private boolean setSpecialHeader(String name, Object value) { - if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) { - setContentType((String) value); - return true; - } - else if (CONTENT_LENGTH_HEADER.equalsIgnoreCase(name)) { - setContentLength(Integer.parseInt((String) value)); - return true; - } - else { - return false; - } - } - - private void doAddHeaderValue(String name, Object value, boolean replace) { - HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - Assert.notNull(value, "Header value must not be null"); - if (header == null) { - header = new HeaderValueHolder(); - this.headers.put(name, header); - } - if (replace) { - header.setValue(value); - } - else { - header.addValue(value); - } - } - - public void setStatus(int status) { - this.status = status; - } - - public void setStatus(int status, String errorMessage) { - this.status = status; - this.errorMessage = errorMessage; - } - - public int getStatus() { - return this.status; - } - - public String getErrorMessage() { - return this.errorMessage; - } - - - //--------------------------------------------------------------------- - // Methods for MockRequestDispatcher - //--------------------------------------------------------------------- - - public void setForwardedUrl(String forwardedUrl) { - this.forwardedUrl = forwardedUrl; - } - - public String getForwardedUrl() { - return this.forwardedUrl; - } - - public void setIncludedUrl(String includedUrl) { - this.includedUrls.clear(); - if (includedUrl != null) { - this.includedUrls.add(includedUrl); - } - } - - public String getIncludedUrl() { - int count = this.includedUrls.size(); - if (count > 1) { - throw new IllegalStateException( - "More than 1 URL included - check getIncludedUrls instead: " + this.includedUrls); - } - return (count == 1 ? this.includedUrls.get(0) : null); - } - - public void addIncludedUrl(String includedUrl) { - Assert.notNull(includedUrl, "Included URL must not be null"); - this.includedUrls.add(includedUrl); - } - - public List getIncludedUrls() { - return this.includedUrls; - } - - - /** - * Inner class that adapts the ServletOutputStream to mark the - * response as committed once the buffer size is exceeded. - */ - private class ResponseServletOutputStream extends DelegatingServletOutputStream { - - public ResponseServletOutputStream(OutputStream out) { - super(out); - } - - public void write(int b) throws IOException { - super.write(b); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void flush() throws IOException { - super.flush(); - setCommitted(true); - } - } - - - /** - * Inner class that adapts the PrintWriter to mark the - * response as committed once the buffer size is exceeded. - */ - private class ResponsePrintWriter extends PrintWriter { - - public ResponsePrintWriter(Writer out) { - super(out, true); - } - - public void write(char buf[], int off, int len) { - super.write(buf, off, len); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void write(String s, int off, int len) { - super.write(s, off, len); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void write(int c) { - super.write(c); - super.flush(); - setCommittedIfBufferSizeExceeded(); - } - - public void flush() { - super.flush(); - setCommitted(true); - } - } - -} +/* + * 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.mock.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.util.Assert; +import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.web.util.WebUtils; + +/** + * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} + * interface. Supports the Servlet 3.0 API level + * + *

Used for testing the web framework; also useful for testing + * application controllers. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 1.0.2 + */ +public class MockHttpServletResponse implements HttpServletResponse { + + public static final int DEFAULT_SERVER_PORT = 80; + + private static final String CHARSET_PREFIX = "charset="; + + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + + private static final String CONTENT_LENGTH_HEADER = "Content-Length"; + + + //--------------------------------------------------------------------- + // ServletResponse properties + //--------------------------------------------------------------------- + + private boolean outputStreamAccessAllowed = true; + + private boolean writerAccessAllowed = true; + + private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; + + private boolean charset = false; + + private final ByteArrayOutputStream content = new ByteArrayOutputStream(); + + private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content); + + private PrintWriter writer; + + private int contentLength = 0; + + private String contentType; + + private int bufferSize = 4096; + + private boolean committed; + + private Locale locale = Locale.getDefault(); + + + //--------------------------------------------------------------------- + // HttpServletResponse properties + //--------------------------------------------------------------------- + + private final List cookies = new ArrayList(); + + private final Map headers = new LinkedCaseInsensitiveMap(); + + private int status = HttpServletResponse.SC_OK; + + private String errorMessage; + + private String redirectedUrl; + + private String forwardedUrl; + + private final List includedUrls = new ArrayList(); + + + //--------------------------------------------------------------------- + // ServletResponse interface + //--------------------------------------------------------------------- + + /** + * Set whether {@link #getOutputStream()} access is allowed. + *

Default is true. + */ + public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) { + this.outputStreamAccessAllowed = outputStreamAccessAllowed; + } + + /** + * Return whether {@link #getOutputStream()} access is allowed. + */ + public boolean isOutputStreamAccessAllowed() { + return this.outputStreamAccessAllowed; + } + + /** + * Set whether {@link #getWriter()} access is allowed. + *

Default is true. + */ + public void setWriterAccessAllowed(boolean writerAccessAllowed) { + this.writerAccessAllowed = writerAccessAllowed; + } + + /** + * Return whether {@link #getOutputStream()} access is allowed. + */ + public boolean isWriterAccessAllowed() { + return this.writerAccessAllowed; + } + + public void setCharacterEncoding(String characterEncoding) { + this.characterEncoding = characterEncoding; + this.charset = true; + updateContentTypeHeader(); + } + + private void updateContentTypeHeader() { + if (this.contentType != null) { + StringBuilder sb = new StringBuilder(this.contentType); + if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) { + sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding); + } + doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true); + } + } + + public String getCharacterEncoding() { + return this.characterEncoding; + } + + public ServletOutputStream getOutputStream() { + if (!this.outputStreamAccessAllowed) { + throw new IllegalStateException("OutputStream access not allowed"); + } + return this.outputStream; + } + + public PrintWriter getWriter() throws UnsupportedEncodingException { + if (!this.writerAccessAllowed) { + throw new IllegalStateException("Writer access not allowed"); + } + if (this.writer == null) { + Writer targetWriter = (this.characterEncoding != null ? + new OutputStreamWriter(this.content, this.characterEncoding) : new OutputStreamWriter(this.content)); + this.writer = new ResponsePrintWriter(targetWriter); + } + return this.writer; + } + + public byte[] getContentAsByteArray() { + flushBuffer(); + return this.content.toByteArray(); + } + + public String getContentAsString() throws UnsupportedEncodingException { + flushBuffer(); + return (this.characterEncoding != null) ? + this.content.toString(this.characterEncoding) : this.content.toString(); + } + + public void setContentLength(int contentLength) { + this.contentLength = contentLength; + doAddHeaderValue(CONTENT_LENGTH_HEADER, contentLength, true); + } + + public int getContentLength() { + return this.contentLength; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + if (contentType != null) { + int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); + if (charsetIndex != -1) { + String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); + this.characterEncoding = encoding; + this.charset = true; + } + updateContentTypeHeader(); + } + } + + public String getContentType() { + return this.contentType; + } + + public void setBufferSize(int bufferSize) { + this.bufferSize = bufferSize; + } + + public int getBufferSize() { + return this.bufferSize; + } + + public void flushBuffer() { + setCommitted(true); + } + + public void resetBuffer() { + if (isCommitted()) { + throw new IllegalStateException("Cannot reset buffer - response is already committed"); + } + this.content.reset(); + } + + private void setCommittedIfBufferSizeExceeded() { + int bufSize = getBufferSize(); + if (bufSize > 0 && this.content.size() > bufSize) { + setCommitted(true); + } + } + + public void setCommitted(boolean committed) { + this.committed = committed; + } + + public boolean isCommitted() { + return this.committed; + } + + public void reset() { + resetBuffer(); + this.characterEncoding = null; + this.contentLength = 0; + this.contentType = null; + this.locale = null; + this.cookies.clear(); + this.headers.clear(); + this.status = HttpServletResponse.SC_OK; + this.errorMessage = null; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } + + public Locale getLocale() { + return this.locale; + } + + + //--------------------------------------------------------------------- + // HttpServletResponse interface + //--------------------------------------------------------------------- + + public void addCookie(Cookie cookie) { + Assert.notNull(cookie, "Cookie must not be null"); + this.cookies.add(cookie); + } + + public Cookie[] getCookies() { + return this.cookies.toArray(new Cookie[this.cookies.size()]); + } + + public Cookie getCookie(String name) { + Assert.notNull(name, "Cookie name must not be null"); + for (Cookie cookie : this.cookies) { + if (name.equals(cookie.getName())) { + return cookie; + } + } + return null; + } + + public boolean containsHeader(String name) { + return (HeaderValueHolder.getByName(this.headers, name) != null); + } + + /** + * Return the names of all specified headers as a Set of Strings. + * @return the Set of header name Strings, or an empty Set if none + */ + public Set getHeaderNames() { + return this.headers.keySet(); + } + + /** + * Return the primary value for the given header, if any. + *

Will return the first value in case of multiple values. + * @param name the name of the header + * @return the associated header value, or null if none + */ + public String getHeader(String name) { + HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); + return (header != null ? header.getValue().toString() : null); + } + + /** + * Return all values for the given header as a List of value objects. + * @param name the name of the header + * @return the associated header values, or an empty List if none + */ + public List getHeaders(String name) { + HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); + return (header != null ? header.getStringValues() : Collections.emptyList()); + } + + /** + * The default implementation returns the given URL String as-is. + *

Can be overridden in subclasses, appending a session id or the like. + */ + public String encodeURL(String url) { + return url; + } + + /** + * The default implementation delegates to {@link #encodeURL}, + * returning the given URL String as-is. + *

Can be overridden in subclasses, appending a session id or the like + * in a redirect-specific fashion. For general URL encoding rules, + * override the common {@link #encodeURL} method instead, appyling + * to redirect URLs as well as to general URLs. + */ + public String encodeRedirectURL(String url) { + return encodeURL(url); + } + + public String encodeUrl(String url) { + return encodeURL(url); + } + + public String encodeRedirectUrl(String url) { + return encodeRedirectURL(url); + } + + public void sendError(int status, String errorMessage) throws IOException { + if (isCommitted()) { + throw new IllegalStateException("Cannot set error status - response is already committed"); + } + this.status = status; + this.errorMessage = errorMessage; + setCommitted(true); + } + + public void sendError(int status) throws IOException { + if (isCommitted()) { + throw new IllegalStateException("Cannot set error status - response is already committed"); + } + this.status = status; + setCommitted(true); + } + + public void sendRedirect(String url) throws IOException { + if (isCommitted()) { + throw new IllegalStateException("Cannot send redirect - response is already committed"); + } + Assert.notNull(url, "Redirect URL must not be null"); + this.redirectedUrl = url; + setCommitted(true); + } + + public String getRedirectedUrl() { + return this.redirectedUrl; + } + + public void setDateHeader(String name, long value) { + setHeaderValue(name, value); + } + + public void addDateHeader(String name, long value) { + addHeaderValue(name, value); + } + + public void setHeader(String name, String value) { + setHeaderValue(name, value); + } + + public void addHeader(String name, String value) { + addHeaderValue(name, value); + } + + public void setIntHeader(String name, int value) { + setHeaderValue(name, value); + } + + public void addIntHeader(String name, int value) { + addHeaderValue(name, value); + } + + private void setHeaderValue(String name, Object value) { + if (setSpecialHeader(name, value)) { + return; + } + doAddHeaderValue(name, value, true); + } + + private void addHeaderValue(String name, Object value) { + if (setSpecialHeader(name, value)) { + return; + } + doAddHeaderValue(name, value, false); + } + + private boolean setSpecialHeader(String name, Object value) { + if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) { + setContentType((String) value); + return true; + } + else if (CONTENT_LENGTH_HEADER.equalsIgnoreCase(name)) { + setContentLength(Integer.parseInt((String) value)); + return true; + } + else { + return false; + } + } + + private void doAddHeaderValue(String name, Object value, boolean replace) { + HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); + Assert.notNull(value, "Header value must not be null"); + if (header == null) { + header = new HeaderValueHolder(); + this.headers.put(name, header); + } + if (replace) { + header.setValue(value); + } + else { + header.addValue(value); + } + } + + public void setStatus(int status) { + this.status = status; + } + + public void setStatus(int status, String errorMessage) { + this.status = status; + this.errorMessage = errorMessage; + } + + public int getStatus() { + return this.status; + } + + public String getErrorMessage() { + return this.errorMessage; + } + + + //--------------------------------------------------------------------- + // Methods for MockRequestDispatcher + //--------------------------------------------------------------------- + + public void setForwardedUrl(String forwardedUrl) { + this.forwardedUrl = forwardedUrl; + } + + public String getForwardedUrl() { + return this.forwardedUrl; + } + + public void setIncludedUrl(String includedUrl) { + this.includedUrls.clear(); + if (includedUrl != null) { + this.includedUrls.add(includedUrl); + } + } + + public String getIncludedUrl() { + int count = this.includedUrls.size(); + if (count > 1) { + throw new IllegalStateException( + "More than 1 URL included - check getIncludedUrls instead: " + this.includedUrls); + } + return (count == 1 ? this.includedUrls.get(0) : null); + } + + public void addIncludedUrl(String includedUrl) { + Assert.notNull(includedUrl, "Included URL must not be null"); + this.includedUrls.add(includedUrl); + } + + public List getIncludedUrls() { + return this.includedUrls; + } + + + /** + * Inner class that adapts the ServletOutputStream to mark the + * response as committed once the buffer size is exceeded. + */ + private class ResponseServletOutputStream extends DelegatingServletOutputStream { + + public ResponseServletOutputStream(OutputStream out) { + super(out); + } + + public void write(int b) throws IOException { + super.write(b); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void flush() throws IOException { + super.flush(); + setCommitted(true); + } + } + + + /** + * Inner class that adapts the PrintWriter to mark the + * response as committed once the buffer size is exceeded. + */ + private class ResponsePrintWriter extends PrintWriter { + + public ResponsePrintWriter(Writer out) { + super(out, true); + } + + public void write(char buf[], int off, int len) { + super.write(buf, off, len); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void write(String s, int off, int len) { + super.write(s, off, len); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void write(int c) { + super.write(c); + super.flush(); + setCommittedIfBufferSizeExceeded(); + } + + public void flush() { + super.flush(); + setCommitted(true); + } + } + +} diff --git a/spring-framework-reference/src/cache.xml b/spring-framework-reference/src/cache.xml index f3d8b337c8..d56f03ff07 100644 --- a/spring-framework-reference/src/cache.xml +++ b/spring-framework-reference/src/cache.xml @@ -1,584 +1,584 @@ - - - - - Cache Abstraction - -

- Introduction - - Since version 3.1, Spring Framework provides support for transparently - adding caching into an existing Spring application. Similar to the transaction - support, the caching abstraction allows consistent use of various caching - solutions with minimal impact on the code. -
- -
- Understanding the cache abstraction - - - Cache vs Buffer - The terms "buffer" and "cache" tend to be used interchangeably; note however they represent different things. - A buffer is used traditionally as an intermediate temporary store for data between a fast and a slow entity. As one - party would have to wait for the other affecting performance, the buffer alleviates this by - allowing entire blocks of data to move at once rather then in small chunks. The data is written and read only once from - the buffer. Further more, the buffers are visible to at least one party which is aware of it. - A cache on the other hand by definition is hidden and neither party is aware that caching occurs.It as well improves - performance but does that by allowing the same data to be read multiple times in a fast fashion. - - A further explanation of the differences between two can be found - here. - - - At its core, the abstraction applies caching to Java methods, reducing thus the number of executions based on the - information available in the cache. That is, each time a targeted method is invoked, the abstraction - will apply a caching behaviour checking whether the method has been already executed for the given arguments. If it has, - then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the - result cached and returned to the user so that, the next time the method is invoked, the cached result is returned. - This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result - reused without having to actually execute the method again. The caching logic is applied transparently without any interference - to the invoker. - - Obviously this approach works only for methods that are guaranteed to return the same output (result) for a given input - (or arguments) no matter how many times it is being executed. - - To use the cache abstraction, the developer needs to take care of two aspects: - - caching declaration - identify the methods that need to be cached and their policy - cache configuration - the backing cache where the data is stored and read from - - - - Note that just like other services in Spring Framework, the caching service is an abstraction (not a cache implementation) and requires - the use of an actual storage to store the cache data - that is, the abstraction frees the developer from having to write the caching - logic but does not provide the actual stores. There are two integrations available out of the box, for JDK java.util.concurrent.ConcurrentMap - and Ehcache - see for more information on plugging in other cache stores/providers. -
- -
- Declarative annotation-based caching - - For caching declaration, the abstraction provides two Java annotations: @Cacheable and @CacheEvict which allow methods - to trigger cache population or cache eviction. Let us take a closer look at each annotation: - -
- <literal>@Cacheable</literal> annotation - - As the name implies, @Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache - so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method. In its simplest form, - the annotation declaration requires the name of the cache associated with the annotated method: - - - - In the snippet above, the method findBook is associated with the cache named books. Each time the method is called, the cache - is checked to see whether the invocation has been already executed and does not have to be repeated. While in most cases, only one cache is declared, the annotation allows multiple - names to be specified so that more then one cache are being used. In this case, each of the caches will be checked before executing the method - if at least one cache is hit, - then the associated value will be returned: - All the other caches that do not contain the method will be updated as well even though the cached method was not actually - executed. - - - -
- Default Key Generation - - Since caches are essentially key-value stores, each invocation of a cached method needs to be translated into a suitable key for cache access. - Out of the box, the caching abstraction uses a simple KeyGenerator based on the following algorithm: - - If no params are given, return 0. - If only one param is given, return that instance. - If more the one param is given, return a key computed from the hashes of all parameters. - - - This approach works well for objects with natural keys as long as the hashCode() reflects that. If that is not the case then - for distributed or persistent environments, the strategy needs to be changed as the objects hashCode is not preserved. - In fact, depending on the JVM implementation or running conditions, the same hashCode can be reused for different objects, in the same VM instance. - - To provide a different default key generator, one needs to implement the org.springframework.cache.KeyGenerator interface. - Once configured, the generator will be used for each declaration that doesn not specify its own key generation strategy (see below). - -
- -
- Custom Key Generation Declaration - - Since caching is generic, it is quite likely the target methods have various signatures that cannot be simply mapped on top of the cache structure. This tends to become - obvious when the target method has multiple arguments out of which only some are suitable for caching (while the rest are used only by the method logic). For example: - - - - At first glance, while the two boolean arguments influence the way the book is found, they are no use for the cache. Further more what if only one of the two - is important while the other is not? - - For such cases, the @Cacheable annotation allows the user to specify how the key is generated through its key attribute. - The developer can use SpEL to pick the arguments of interest (or their nested properties), perform operations or even invoke arbitrary methods without - having to write any code or implement any interface. This is the recommended approach over the default generator since - methods tend to be quite different in signatures as the code base grows; while the default strategy might work for some methods, it rarely does for all methods. - - - Below are some examples of various SpEL declarations - if you are not familiar with it, do yourself a favour and read : - - - -@Cacheable(value="books", key="#isbn" -public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) - - -@Cacheable(value="books", key="#isbn.rawNumber") -public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) - - -@Cacheable(value="books", key="T(someType).hash(#isbn)") -public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) - - The snippets above, show how easy it is to select a certain argument, one of its properties or even an arbitrary (static) method. -
- -
- Conditional caching - - Sometimes, a method might not be suitable for caching all the time (for example, it might depend on the given arguments). The cache annotations support such functionality - through the conditional parameter which takes a SpEL expression that is evaluated to either true or false. - If true, the method is cached - if not, it behaves as if the method is not cached, that is executed every since time no matter what values are in the cache or what - arguments are used. A quick example - the following method will be cached, only if the argument name has a length shorter then 32: - - -
- -
- Available caching <literal>SpEL</literal> evaluation context - - Each SpEL expression evaluates again a dedicated context. In addition - to the build in parameters, the framework provides dedicated caching related metadata such as the argument names. The next table lists the items made available to the context - so one can use them for key and conditional(see next section) computations: - - - Cache SpEL available metadata - - - - - Name - Location - Description - Example - - - - - methodName - root object - The name of the method being invoked - #root.methodName - - - method - root object - The method being invoked - #root.method.name - - - target - root object - The target object being invoked - #root.target - - - targetClass - root object - The class of the target being invoked - #root.targetClass - - - params - root object - The arguments (as array) used for invoking the target - #root.params[0] - - - caches - root object - Collection of caches against which the current method is executed - #root.caches[0].name - - - parameter name - evaluation context - Name of any of the method parameter. If for some reason the names are not available (ex: no debug information), - the parameter names are also available under the ]]> where - stands for the parameter index (starting from 0). - iban or p0 - - - -
-
-
- -
- <literal>@CachePut</literal> annotation - - For cases where the cache needs to be updated without interferring with the method execution, one can use the @CachePut annotation. That is, the method will always - be executed and its result placed into the cache (according to the @CachePut options). It supports the same options as @Cacheable and should be used - for cache population rather then method flow optimization. - - Note that using @CachePut and @Cacheable annotations on the same method is generaly discouraged because they have different behaviours. While the latter - causes the method execution to be skipped by using the cache, the former forces the execution in order to execute a cache update. This leads to unexpected behaviour and with the exception of specific - corner-cases (such as annotations having conditions that exclude them from each other), such declarations should be avoided. -
- -
- <literal>@CacheEvict</literal> annotation - - The cache abstraction allows not just population of a cache store but also eviction. This process is useful for removing stale or unused data from the cache. Opposed to - @Cacheable, annotation @CacheEvict demarcates methods that perform cache eviction, that is methods that act as triggers - for removing data from the cache. Just like its sibling, @CacheEvict requires one to specify one (or multiple) caches that are affected by the action, allows a - key or a condition to be specified but in addition, features an extra parameter allEntries which indicates whether a cache-wide eviction needs to be performed - rather then just an entry one (based on the key): - - - - This option comes in handy when an entire cache region needs to be cleared out - rather then evicting each entry (which would take a long time since it is inefficient), - all the entires are removed in one operation as shown above. Note that the framework will ignore any key specified in this scenario as it does not apply (the entire cache is evicted not just - one entry). - - One can also indicate whether the eviction should occur after (the default) or before the method executes through the beforeInvocation attribute. - The former provides the same semantics as the rest of the annotations - once the method completes successfully, an action (in this case eviction) on the cache is executed. If the method does not - execute (as it might be cached) or an exception is thrown, the eviction does not occur. The latter (beforeInvocation=true) causes the eviction to occur always, before the method - is invoked - this is useful in cases where the eviction does not need to be tied to the method outcome. - - It is important to note that void methods can be used with @CacheEvict - as the methods act as triggers, the return values are ignored (as they don't interact with - the cache) - this is not the case with @Cacheable which adds/update data into the cache and thus requires a result. -
- -
- <literal>@Caching</literal> annotation - - There are cases when multiple annotations of the same type, such as @CacheEvict or @CachePut need to be specified, for example because the condition or the key - expression is different between different caches. Unfortunately Java does not support such declarations however there is a workaround - using a enclosing annotation, in this case, - @Caching. @Caching allows multiple nested @Cacheable, @CachePut and @CacheEvict to be used on the same method: - - - -
- -
- Enable caching annotations - - It is important to note that even though declaring the cache annotations does not automatically triggers their actions - like many things in Spring, the feature has to be declaratively - enabled (which means if you ever suspect caching is to blame, you can disable it by removing only one configuration line rather then all the annotations in your code). In practice, this - translates to one line that informs Spring that it should process the cache annotations, namely: - - - xmlns:cache="http://www.springframework.org/schema/cache" - http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd]]> - ]]> -]]> - - The namespace allows various options to be specified that influence the way the caching behaviour is added to the application through AOP. The configuration is similar (on purpose) - with that of tx:annotation-driven: - - - - <literal><cache:annotation-driven/></literal> - settings - - - - - Attribute - - Default - - Description - - - - - - cache-manager - - cacheManager - - Name of cache manager to use. Only required - if the name of the cache manager is not - cacheManager, as in the example - above. - - - - mode - - proxy - - The default mode "proxy" processes annotated - beans to be proxied using Spring's AOP framework (following - proxy semantics, as discussed above, applying to method calls - coming in through the proxy only). The alternative mode - "aspectj" instead weaves the affected classes with Spring's - AspectJ caching aspect, modifying the target class byte - code to apply to any kind of method call. AspectJ weaving - requires spring-aspects.jar in the classpath as well as - load-time weaving (or compile-time weaving) enabled. (See - for details on how to set - up load-time weaving.) - - - - proxy-target-class - - false - - Applies to proxy mode only. Controls what type of - caching proxies are created for classes annotated with - the @Cacheable or @CacheEvict annotations. - If the proxy-target-class attribute is set - to true, then class-based proxies are - created. If proxy-target-class is - false or if the attribute is omitted, then - standard JDK interface-based proxies are created. (See for a detailed examination of the - different proxy types.) - - - - order - - Ordered.LOWEST_PRECEDENCE - - Defines the order of the cache advice that - is applied to beans annotated with - @Cacheable or @CacheEvict. - (For more - information about the rules related to ordering of AOP advice, - see .) No - specified ordering means that the AOP subsystem determines the - order of the advice. - - - -
- - - <cache:annotation-driven/> only looks for - @Cacheable/@CacheEvict on beans in the same - application context it is defined in. This means that, if you put - <cache:annotation-driven/> in a - WebApplicationContext for a - DispatcherServlet, it only checks for - @Cacheable/@CacheEvict beans in your - controllers, and not your services. See for more information. - - - - Method visibility and - <interfacename>@Cacheable/@CachePut/@CacheEvict</interfacename> - - When using proxies, you should apply the - @Cache* annotations only to - methods with public visibility. If you do - annotate protected, private or package-visible methods with these annotations, - no error is raised, but the annotated method does not exhibit the configured - caching settings. Consider the use of AspectJ (see below) if you - need to annotate non-public methods as it changes the bytecode itself. - - - - Spring recommends that you only annotate concrete classes (and - methods of concrete classes) with the - @Cache* annotation, as opposed - to annotating interfaces. You certainly can place the - @Cache* annotation on an - interface (or an interface method), but this works only as you would - expect it to if you are using interface-based proxies. The fact that - Java annotations are not inherited from interfaces - means that if you are using class-based proxies - (proxy-target-class="true") or the weaving-based - aspect (mode="aspectj"), then the caching - settings are not recognized by the proxying and weaving - infrastructure, and the object will not be wrapped in a - caching proxy, which would be decidedly - bad. - - - - In proxy mode (which is the default), only external method calls - coming in through the proxy are intercepted. This means that - self-invocation, in effect, a method within the target object calling - another method of the target object, will not lead to an actual - caching at runtime even if the invoked method is marked with - @Cacheable - considering using the aspectj mode in this case. - -
- -
- Using custom annotations - - The caching abstraction allows one to use her own annotations to identify what method trigger cache population or eviction. This is quite handy as a template mechanism as it eliminates - the need to duplicate cache annotation declarations (especially useful if the key or condition are specified) or if the foreign imports (org.springframework) are not allowed - in your code base. Similar to the rest of the stereotype annotations, both @Cacheable and @CacheEvict - can be used as meta-annotations, that is annotations that can annotate other annotations. To wit, let us replace a common @Cacheable declaration with our own, custom - annotation: - - - - - Above, we have defined our own SlowService annotation which itself is annotated with @Cacheable - now we can replace the following code: - - - - with: - - - - Even though @SlowService is not a Spring annotation, the container automatically picks up its declaration at runtime and understands its meaning. Note that as - mentined above, the annotation-driven behaviour needs to be enabled. -
-
- -
- Declarative XML-based caching - - If annotations are not an option (no access to the sources or no external code), one can use XML for declarative caching. So instead of annotating the methods for caching, one specifies - the target method and the caching directives externally (similar to the declarative transaction management advice). The previous example - can be translated into: - - - - - - - - - - - - - - - - -... -// cache manager definition omitted -]]> - - - In the configuration above, the bookService is made cacheable. The caching semantics to apply are encapsulated in the cache:advice definition which - instructs method findBooks to be used for putting data into the cache while method loadBooks for evicting data. Both definitions are working against the - books cache. - - The aop:config definition applies the cache advice to the appropriate points in the program by using the AspectJ pointcut expression (more information is available - in ). In the example above, all methods from the BookService are considered and the cache advice applied to them. - - The declarative XML caching supports all of the annotation-based model so moving between the two should be fairly easy - further more both can be used inside the same application. - The XML based approach does not touch the target code however it is inherently more verbose; when dealing with classes with overloaded methods that are targeted for caching, identifying the - proper methods does take an extra effort since the method argument is not a good discriminator - in these cases, the AspectJ pointcut can be used to cherry pick the target - methods and apply the appropriate caching functionality. Howeve through XML, it is easier to apply a package/group/interface-wide caching (again due to the AspectJ poincut) and to create - template-like definitions (as we did in the example above by defining the target cache through the cache:definitions cache attribute). - -
- -
- Configuring the cache storage - - Out of the box, the cache abstraction provides integration with two storages - one on top of the JDK ConcurrentMap and one - for ehcache library. To use them, one needs to simply declare an appropriate CacheManager - an entity that controls and manages - Caches and can be used to retrieve these for storage. - -
- JDK <interfacename>ConcurrentMap</interfacename>-based <interfacename>Cache</interfacename> - - The JDK-based Cache implementation resides under org.springframework.cache.concurrent package. It allows one to use - ConcurrentHashMap as a backing Cache store. - - - - - - - - - -]]> - - The snippet above uses the SimpleCacheManager to create a CacheManager for the two, nested Concurrent - Cache implementations named default and books. - Note that the names are configured directly for each cache. - - As the cache is created by the application, it is bound to its lifecycle, making it suitable for basic use cases, tests or simple applications. The cache scales well and is very fast - but it does not provide any management or persistence capabilities nor eviction contracts. -
- -
- Ehcache-based <interfacename>Cache</interfacename> - - The Ehcache implementation is located under org.springframework.cache.ehcache package. Again, to use it, one simply needs to declare the appropriate - CacheManager: - - - - -]]> - - This setup bootstraps ehcache library inside Spring IoC (through bean ehcache) which is then wired into the dedicated CacheManager - implementation. Note the entire ehcache-specific configuration is read from the resource ehcache.xml. -
- -
- Dealing with caches without a backing store - - Sometimes when switching environments or doing testing, one might have cache declarations without an actual backing cache configured. As this is an invalid configuration, at runtime an - exception will be through since the caching infrastructure is unable to find a suitable store. In situations like this, rather then removing the cache declarations (which can prove tedious), - one can wire in a simple, dummy cache that performs no caching - that is, forces the cached methods to be executed every time: - - - - - - - -]]> - - The CompositeCacheManager above chains multiple CacheManagers and aditionally, through the addNoOpManager flag, adds a - no op cache that for all the definitions not handled by the configured cache managers. That is, every cache definition not found in either jdkCache - or gemfireCache (configured above) will be handled by the no op cache, which will not store any information causing the target method to be executed every time. - -
-
- -
- Plugging-in different back-end caches - - Clearly there are plenty of caching products out there that can be used as a backing store. To plug them in, one needs to provide a CacheManager and - Cache implementation since unfortunately there is no available standard that we can use instead. This may sound harder then it is since in practice, - the classes tend to be simple adapters that map the caching abstraction framework on top of the storage API as the ehcache classes can show. - Most CacheManager classes can use the classes in org.springframework.cache.support package, such as AbstractCacheManager - which takes care of the boiler-plate code leaving only the actual mapping to be completed. We hope that in time, the libraries that provide integration with Spring - can fill in this small configuration gap. -
- -
- How can I set the TTL/TTI/Eviction policy/XXX feature? - - Directly through your cache provider. The cache abstraction is... well, an abstraction not a cache implementation. The solution you are using might support various data policies and different - topologies which other solutions do not (take for example the JDK ConcurrentHashMap) - exposing that in the cache abstraction would be useless simply because there would - no backing support. Such functionality should be controlled directly through the backing cache, when configuring it or through its native API. - -
- - + + + + + Cache Abstraction + +
+ Introduction + + Since version 3.1, Spring Framework provides support for transparently + adding caching into an existing Spring application. Similar to the transaction + support, the caching abstraction allows consistent use of various caching + solutions with minimal impact on the code. +
+ +
+ Understanding the cache abstraction + + + Cache vs Buffer + The terms "buffer" and "cache" tend to be used interchangeably; note however they represent different things. + A buffer is used traditionally as an intermediate temporary store for data between a fast and a slow entity. As one + party would have to wait for the other affecting performance, the buffer alleviates this by + allowing entire blocks of data to move at once rather then in small chunks. The data is written and read only once from + the buffer. Further more, the buffers are visible to at least one party which is aware of it. + A cache on the other hand by definition is hidden and neither party is aware that caching occurs.It as well improves + performance but does that by allowing the same data to be read multiple times in a fast fashion. + + A further explanation of the differences between two can be found + here. + + + At its core, the abstraction applies caching to Java methods, reducing thus the number of executions based on the + information available in the cache. That is, each time a targeted method is invoked, the abstraction + will apply a caching behaviour checking whether the method has been already executed for the given arguments. If it has, + then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the + result cached and returned to the user so that, the next time the method is invoked, the cached result is returned. + This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result + reused without having to actually execute the method again. The caching logic is applied transparently without any interference + to the invoker. + + Obviously this approach works only for methods that are guaranteed to return the same output (result) for a given input + (or arguments) no matter how many times it is being executed. + + To use the cache abstraction, the developer needs to take care of two aspects: + + caching declaration - identify the methods that need to be cached and their policy + cache configuration - the backing cache where the data is stored and read from + + + + Note that just like other services in Spring Framework, the caching service is an abstraction (not a cache implementation) and requires + the use of an actual storage to store the cache data - that is, the abstraction frees the developer from having to write the caching + logic but does not provide the actual stores. There are two integrations available out of the box, for JDK java.util.concurrent.ConcurrentMap + and Ehcache - see for more information on plugging in other cache stores/providers. +
+ +
+ Declarative annotation-based caching + + For caching declaration, the abstraction provides two Java annotations: @Cacheable and @CacheEvict which allow methods + to trigger cache population or cache eviction. Let us take a closer look at each annotation: + +
+ <literal>@Cacheable</literal> annotation + + As the name implies, @Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache + so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method. In its simplest form, + the annotation declaration requires the name of the cache associated with the annotated method: + + + + In the snippet above, the method findBook is associated with the cache named books. Each time the method is called, the cache + is checked to see whether the invocation has been already executed and does not have to be repeated. While in most cases, only one cache is declared, the annotation allows multiple + names to be specified so that more then one cache are being used. In this case, each of the caches will be checked before executing the method - if at least one cache is hit, + then the associated value will be returned: + All the other caches that do not contain the method will be updated as well even though the cached method was not actually + executed. + + + +
+ Default Key Generation + + Since caches are essentially key-value stores, each invocation of a cached method needs to be translated into a suitable key for cache access. + Out of the box, the caching abstraction uses a simple KeyGenerator based on the following algorithm: + + If no params are given, return 0. + If only one param is given, return that instance. + If more the one param is given, return a key computed from the hashes of all parameters. + + + This approach works well for objects with natural keys as long as the hashCode() reflects that. If that is not the case then + for distributed or persistent environments, the strategy needs to be changed as the objects hashCode is not preserved. + In fact, depending on the JVM implementation or running conditions, the same hashCode can be reused for different objects, in the same VM instance. + + To provide a different default key generator, one needs to implement the org.springframework.cache.KeyGenerator interface. + Once configured, the generator will be used for each declaration that doesn not specify its own key generation strategy (see below). + +
+ +
+ Custom Key Generation Declaration + + Since caching is generic, it is quite likely the target methods have various signatures that cannot be simply mapped on top of the cache structure. This tends to become + obvious when the target method has multiple arguments out of which only some are suitable for caching (while the rest are used only by the method logic). For example: + + + + At first glance, while the two boolean arguments influence the way the book is found, they are no use for the cache. Further more what if only one of the two + is important while the other is not? + + For such cases, the @Cacheable annotation allows the user to specify how the key is generated through its key attribute. + The developer can use SpEL to pick the arguments of interest (or their nested properties), perform operations or even invoke arbitrary methods without + having to write any code or implement any interface. This is the recommended approach over the default generator since + methods tend to be quite different in signatures as the code base grows; while the default strategy might work for some methods, it rarely does for all methods. + + + Below are some examples of various SpEL declarations - if you are not familiar with it, do yourself a favour and read : + + + +@Cacheable(value="books", key="#isbn" +public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + +@Cacheable(value="books", key="#isbn.rawNumber") +public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + +@Cacheable(value="books", key="T(someType).hash(#isbn)") +public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) + + The snippets above, show how easy it is to select a certain argument, one of its properties or even an arbitrary (static) method. +
+ +
+ Conditional caching + + Sometimes, a method might not be suitable for caching all the time (for example, it might depend on the given arguments). The cache annotations support such functionality + through the conditional parameter which takes a SpEL expression that is evaluated to either true or false. + If true, the method is cached - if not, it behaves as if the method is not cached, that is executed every since time no matter what values are in the cache or what + arguments are used. A quick example - the following method will be cached, only if the argument name has a length shorter then 32: + + +
+ +
+ Available caching <literal>SpEL</literal> evaluation context + + Each SpEL expression evaluates again a dedicated context. In addition + to the build in parameters, the framework provides dedicated caching related metadata such as the argument names. The next table lists the items made available to the context + so one can use them for key and conditional(see next section) computations: + + + Cache SpEL available metadata + + + + + Name + Location + Description + Example + + + + + methodName + root object + The name of the method being invoked + #root.methodName + + + method + root object + The method being invoked + #root.method.name + + + target + root object + The target object being invoked + #root.target + + + targetClass + root object + The class of the target being invoked + #root.targetClass + + + params + root object + The arguments (as array) used for invoking the target + #root.params[0] + + + caches + root object + Collection of caches against which the current method is executed + #root.caches[0].name + + + parameter name + evaluation context + Name of any of the method parameter. If for some reason the names are not available (ex: no debug information), + the parameter names are also available under the ]]> where + stands for the parameter index (starting from 0). + iban or p0 + + + +
+
+
+ +
+ <literal>@CachePut</literal> annotation + + For cases where the cache needs to be updated without interferring with the method execution, one can use the @CachePut annotation. That is, the method will always + be executed and its result placed into the cache (according to the @CachePut options). It supports the same options as @Cacheable and should be used + for cache population rather then method flow optimization. + + Note that using @CachePut and @Cacheable annotations on the same method is generaly discouraged because they have different behaviours. While the latter + causes the method execution to be skipped by using the cache, the former forces the execution in order to execute a cache update. This leads to unexpected behaviour and with the exception of specific + corner-cases (such as annotations having conditions that exclude them from each other), such declarations should be avoided. +
+ +
+ <literal>@CacheEvict</literal> annotation + + The cache abstraction allows not just population of a cache store but also eviction. This process is useful for removing stale or unused data from the cache. Opposed to + @Cacheable, annotation @CacheEvict demarcates methods that perform cache eviction, that is methods that act as triggers + for removing data from the cache. Just like its sibling, @CacheEvict requires one to specify one (or multiple) caches that are affected by the action, allows a + key or a condition to be specified but in addition, features an extra parameter allEntries which indicates whether a cache-wide eviction needs to be performed + rather then just an entry one (based on the key): + + + + This option comes in handy when an entire cache region needs to be cleared out - rather then evicting each entry (which would take a long time since it is inefficient), + all the entires are removed in one operation as shown above. Note that the framework will ignore any key specified in this scenario as it does not apply (the entire cache is evicted not just + one entry). + + One can also indicate whether the eviction should occur after (the default) or before the method executes through the beforeInvocation attribute. + The former provides the same semantics as the rest of the annotations - once the method completes successfully, an action (in this case eviction) on the cache is executed. If the method does not + execute (as it might be cached) or an exception is thrown, the eviction does not occur. The latter (beforeInvocation=true) causes the eviction to occur always, before the method + is invoked - this is useful in cases where the eviction does not need to be tied to the method outcome. + + It is important to note that void methods can be used with @CacheEvict - as the methods act as triggers, the return values are ignored (as they don't interact with + the cache) - this is not the case with @Cacheable which adds/update data into the cache and thus requires a result. +
+ +
+ <literal>@Caching</literal> annotation + + There are cases when multiple annotations of the same type, such as @CacheEvict or @CachePut need to be specified, for example because the condition or the key + expression is different between different caches. Unfortunately Java does not support such declarations however there is a workaround - using a enclosing annotation, in this case, + @Caching. @Caching allows multiple nested @Cacheable, @CachePut and @CacheEvict to be used on the same method: + + + +
+ +
+ Enable caching annotations + + It is important to note that even though declaring the cache annotations does not automatically triggers their actions - like many things in Spring, the feature has to be declaratively + enabled (which means if you ever suspect caching is to blame, you can disable it by removing only one configuration line rather then all the annotations in your code). In practice, this + translates to one line that informs Spring that it should process the cache annotations, namely: + + + xmlns:cache="http://www.springframework.org/schema/cache" + http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd]]> + ]]> +]]> + + The namespace allows various options to be specified that influence the way the caching behaviour is added to the application through AOP. The configuration is similar (on purpose) + with that of tx:annotation-driven: + + + + <literal><cache:annotation-driven/></literal> + settings + + + + + Attribute + + Default + + Description + + + + + + cache-manager + + cacheManager + + Name of cache manager to use. Only required + if the name of the cache manager is not + cacheManager, as in the example + above. + + + + mode + + proxy + + The default mode "proxy" processes annotated + beans to be proxied using Spring's AOP framework (following + proxy semantics, as discussed above, applying to method calls + coming in through the proxy only). The alternative mode + "aspectj" instead weaves the affected classes with Spring's + AspectJ caching aspect, modifying the target class byte + code to apply to any kind of method call. AspectJ weaving + requires spring-aspects.jar in the classpath as well as + load-time weaving (or compile-time weaving) enabled. (See + for details on how to set + up load-time weaving.) + + + + proxy-target-class + + false + + Applies to proxy mode only. Controls what type of + caching proxies are created for classes annotated with + the @Cacheable or @CacheEvict annotations. + If the proxy-target-class attribute is set + to true, then class-based proxies are + created. If proxy-target-class is + false or if the attribute is omitted, then + standard JDK interface-based proxies are created. (See for a detailed examination of the + different proxy types.) + + + + order + + Ordered.LOWEST_PRECEDENCE + + Defines the order of the cache advice that + is applied to beans annotated with + @Cacheable or @CacheEvict. + (For more + information about the rules related to ordering of AOP advice, + see .) No + specified ordering means that the AOP subsystem determines the + order of the advice. + + + +
+ + + <cache:annotation-driven/> only looks for + @Cacheable/@CacheEvict on beans in the same + application context it is defined in. This means that, if you put + <cache:annotation-driven/> in a + WebApplicationContext for a + DispatcherServlet, it only checks for + @Cacheable/@CacheEvict beans in your + controllers, and not your services. See for more information. + + + + Method visibility and + <interfacename>@Cacheable/@CachePut/@CacheEvict</interfacename> + + When using proxies, you should apply the + @Cache* annotations only to + methods with public visibility. If you do + annotate protected, private or package-visible methods with these annotations, + no error is raised, but the annotated method does not exhibit the configured + caching settings. Consider the use of AspectJ (see below) if you + need to annotate non-public methods as it changes the bytecode itself. + + + + Spring recommends that you only annotate concrete classes (and + methods of concrete classes) with the + @Cache* annotation, as opposed + to annotating interfaces. You certainly can place the + @Cache* annotation on an + interface (or an interface method), but this works only as you would + expect it to if you are using interface-based proxies. The fact that + Java annotations are not inherited from interfaces + means that if you are using class-based proxies + (proxy-target-class="true") or the weaving-based + aspect (mode="aspectj"), then the caching + settings are not recognized by the proxying and weaving + infrastructure, and the object will not be wrapped in a + caching proxy, which would be decidedly + bad. + + + + In proxy mode (which is the default), only external method calls + coming in through the proxy are intercepted. This means that + self-invocation, in effect, a method within the target object calling + another method of the target object, will not lead to an actual + caching at runtime even if the invoked method is marked with + @Cacheable - considering using the aspectj mode in this case. + +
+ +
+ Using custom annotations + + The caching abstraction allows one to use her own annotations to identify what method trigger cache population or eviction. This is quite handy as a template mechanism as it eliminates + the need to duplicate cache annotation declarations (especially useful if the key or condition are specified) or if the foreign imports (org.springframework) are not allowed + in your code base. Similar to the rest of the stereotype annotations, both @Cacheable and @CacheEvict + can be used as meta-annotations, that is annotations that can annotate other annotations. To wit, let us replace a common @Cacheable declaration with our own, custom + annotation: + + + + + Above, we have defined our own SlowService annotation which itself is annotated with @Cacheable - now we can replace the following code: + + + + with: + + + + Even though @SlowService is not a Spring annotation, the container automatically picks up its declaration at runtime and understands its meaning. Note that as + mentined above, the annotation-driven behaviour needs to be enabled. +
+
+ +
+ Declarative XML-based caching + + If annotations are not an option (no access to the sources or no external code), one can use XML for declarative caching. So instead of annotating the methods for caching, one specifies + the target method and the caching directives externally (similar to the declarative transaction management advice). The previous example + can be translated into: + + + + + + + + + + + + + + + + +... +// cache manager definition omitted +]]> + + + In the configuration above, the bookService is made cacheable. The caching semantics to apply are encapsulated in the cache:advice definition which + instructs method findBooks to be used for putting data into the cache while method loadBooks for evicting data. Both definitions are working against the + books cache. + + The aop:config definition applies the cache advice to the appropriate points in the program by using the AspectJ pointcut expression (more information is available + in ). In the example above, all methods from the BookService are considered and the cache advice applied to them. + + The declarative XML caching supports all of the annotation-based model so moving between the two should be fairly easy - further more both can be used inside the same application. + The XML based approach does not touch the target code however it is inherently more verbose; when dealing with classes with overloaded methods that are targeted for caching, identifying the + proper methods does take an extra effort since the method argument is not a good discriminator - in these cases, the AspectJ pointcut can be used to cherry pick the target + methods and apply the appropriate caching functionality. Howeve through XML, it is easier to apply a package/group/interface-wide caching (again due to the AspectJ poincut) and to create + template-like definitions (as we did in the example above by defining the target cache through the cache:definitions cache attribute). + +
+ +
+ Configuring the cache storage + + Out of the box, the cache abstraction provides integration with two storages - one on top of the JDK ConcurrentMap and one + for ehcache library. To use them, one needs to simply declare an appropriate CacheManager - an entity that controls and manages + Caches and can be used to retrieve these for storage. + +
+ JDK <interfacename>ConcurrentMap</interfacename>-based <interfacename>Cache</interfacename> + + The JDK-based Cache implementation resides under org.springframework.cache.concurrent package. It allows one to use + ConcurrentHashMap as a backing Cache store. + + + + + + + + + +]]> + + The snippet above uses the SimpleCacheManager to create a CacheManager for the two, nested Concurrent + Cache implementations named default and books. + Note that the names are configured directly for each cache. + + As the cache is created by the application, it is bound to its lifecycle, making it suitable for basic use cases, tests or simple applications. The cache scales well and is very fast + but it does not provide any management or persistence capabilities nor eviction contracts. +
+ +
+ Ehcache-based <interfacename>Cache</interfacename> + + The Ehcache implementation is located under org.springframework.cache.ehcache package. Again, to use it, one simply needs to declare the appropriate + CacheManager: + + + + +]]> + + This setup bootstraps ehcache library inside Spring IoC (through bean ehcache) which is then wired into the dedicated CacheManager + implementation. Note the entire ehcache-specific configuration is read from the resource ehcache.xml. +
+ +
+ Dealing with caches without a backing store + + Sometimes when switching environments or doing testing, one might have cache declarations without an actual backing cache configured. As this is an invalid configuration, at runtime an + exception will be through since the caching infrastructure is unable to find a suitable store. In situations like this, rather then removing the cache declarations (which can prove tedious), + one can wire in a simple, dummy cache that performs no caching - that is, forces the cached methods to be executed every time: + + + + + + + +]]> + + The CompositeCacheManager above chains multiple CacheManagers and aditionally, through the addNoOpManager flag, adds a + no op cache that for all the definitions not handled by the configured cache managers. That is, every cache definition not found in either jdkCache + or gemfireCache (configured above) will be handled by the no op cache, which will not store any information causing the target method to be executed every time. + +
+
+ +
+ Plugging-in different back-end caches + + Clearly there are plenty of caching products out there that can be used as a backing store. To plug them in, one needs to provide a CacheManager and + Cache implementation since unfortunately there is no available standard that we can use instead. This may sound harder then it is since in practice, + the classes tend to be simple adapters that map the caching abstraction framework on top of the storage API as the ehcache classes can show. + Most CacheManager classes can use the classes in org.springframework.cache.support package, such as AbstractCacheManager + which takes care of the boiler-plate code leaving only the actual mapping to be completed. We hope that in time, the libraries that provide integration with Spring + can fill in this small configuration gap. +
+ +
+ How can I set the TTL/TTI/Eviction policy/XXX feature? + + Directly through your cache provider. The cache abstraction is... well, an abstraction not a cache implementation. The solution you are using might support various data policies and different + topologies which other solutions do not (take for example the JDK ConcurrentHashMap) - exposing that in the cache abstraction would be useless simply because there would + no backing support. Such functionality should be controlled directly through the backing cache, when configuring it or through its native API. + +
+ +
diff --git a/spring-framework-reference/src/classic-aop-spring.xml b/spring-framework-reference/src/classic-aop-spring.xml index a0794f289e..eaf0b73d08 100644 --- a/spring-framework-reference/src/classic-aop-spring.xml +++ b/spring-framework-reference/src/classic-aop-spring.xml @@ -1,1950 +1,1950 @@ - - - - Classic Spring AOP Usage - - In this appendix we discuss - the lower-level Spring AOP APIs and the AOP support used in Spring 1.2 applications. - For new applications, we recommend the use of the Spring 2.0 AOP support - described in the AOP chapter, but when working with existing applications, - or when reading books and articles, you may come across Spring 1.2 style examples. - Spring 2.0 is fully backwards compatible with Spring 1.2 and everything described - in this appendix is fully supported in Spring 2.0. - -
- Pointcut API in Spring - - Let's look at how Spring handles the crucial pointcut concept. - -
- Concepts - - Spring's pointcut model enables pointcut reuse independent of - advice types. It's possible to target different advice using the same - pointcut. - - The org.springframework.aop.Pointcut interface - is the central interface, used to target advices to particular classes - and methods. The complete interface is shown below: - - - - Splitting the Pointcut interface into two parts - allows reuse of class and method matching parts, and fine-grained - composition operations (such as performing a "union" with another method - matcher). - - The ClassFilter interface is used to restrict - the pointcut to a given set of target classes. If the - matches() method always returns true, all target - classes will be matched: - - - - The MethodMatcher interface is normally more - important. The complete interface is shown below: - - - - The matches(Method, Class) method is used to - test whether this pointcut will ever match a given method on a target - class. This evaluation can be performed when an AOP proxy is created, to - avoid the need for a test on every method invocation. If the 2-argument - matches method returns true for a given method, and the - isRuntime() method for the MethodMatcher returns - true, the 3-argument matches method will be invoked on every method - invocation. This enables a pointcut to look at the arguments passed to - the method invocation immediately before the target advice is to - execute. - - Most MethodMatchers are static, meaning that their - isRuntime() method returns false. In this case, the - 3-argument matches method will never be invoked. - - - If possible, try to make pointcuts static, allowing the AOP - framework to cache the results of pointcut evaluation when an AOP proxy - is created. - -
- -
- Operations on pointcuts - - Spring supports operations on pointcuts: notably, - union and intersection. - - - - Union means the methods that either pointcut matches. - - - Intersection means the methods that both pointcuts match. - - - Union is usually more useful. - - - Pointcuts can be composed using the static methods in the - org.springframework.aop.support.Pointcuts class, or - using the ComposablePointcut class in the same - package. However, using AspectJ pointcut expressions is usually a - simpler approach. - - - -
- -
- AspectJ expression pointcuts - - Since 2.0, the most important type of pointcut used by Spring is - org.springframework.aop.aspectj.AspectJExpressionPointcut. - This is a pointcut that uses an AspectJ supplied library to parse an AspectJ - pointcut expression string. - - See the previous chapter for a discussion of supported AspectJ pointcut - primitives. - -
- -
- Convenience pointcut implementations - - Spring provides several convenient pointcut implementations. Some - can be used out of the box; others are intended to be subclassed in - application-specific pointcuts. - -
- Static pointcuts - - Static pointcuts are based on method and target class, and - cannot take into account the method's arguments. Static pointcuts are - sufficient - and best - for most usages. It's possible for Spring to - evaluate a static pointcut only once, when a method is first invoked: - after that, there is no need to evaluate the pointcut again with each - method invocation. - - Let's consider some static pointcut implementations included - with Spring. - -
- Regular expression pointcuts - - One obvious way to specify static pointcuts is regular - expressions. Several AOP frameworks besides Spring make this - possible. - org.springframework.aop.support.Perl5RegexpMethodPointcut - is a generic regular expression pointcut, using Perl 5 regular - expression syntax. The Perl5RegexpMethodPointcut - class depends on Jakarta ORO for regular expression matching. Spring - also provides the JdkRegexpMethodPointcut class - that uses the regular expression support in JDK 1.4+. - - Using the Perl5RegexpMethodPointcut class, - you can provide a list of pattern Strings. If any of these is a - match, the pointcut will evaluate to true. (So the result is - effectively the union of these pointcuts.) - - The usage is shown below: - - <bean id="settersAndAbsquatulatePointcut" - class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> - <property name="patterns"> - <list> - <value>.*set.*</value> - <value>.*absquatulate</value> - </list> - </property> -</bean> - - Spring provides a convenience class, - RegexpMethodPointcutAdvisor, that allows us to - also reference an Advice (remember that an Advice can be an - interceptor, before advice, throws advice etc.). Behind the scenes, - Spring will use a JdkRegexpMethodPointcut. Using - RegexpMethodPointcutAdvisor simplifies wiring, - as the one bean encapsulates both pointcut and advice, as shown - below: - - <bean id="settersAndAbsquatulateAdvisor" - class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> - <property name="advice"> - <ref local="beanNameOfAopAllianceInterceptor"/> - </property> - <property name="patterns"> - <list> - <value>.*set.*</value> - <value>.*absquatulate</value> - </list> - </property> -</bean> - - RegexpMethodPointcutAdvisor can be used - with any Advice type. -
- -
- Attribute-driven pointcuts - - An important type of static pointcut is a - metadata-driven pointcut. This uses the values - of metadata attributes: typically, source-level metadata. -
-
- -
- Dynamic pointcuts - - Dynamic pointcuts are costlier to evaluate than static - pointcuts. They take into account method - arguments, as well as static information. This - means that they must be evaluated with every method invocation; the - result cannot be cached, as arguments will vary. - - The main example is the control flow - pointcut. - -
- Control flow pointcuts - - Spring control flow pointcuts are conceptually similar to - AspectJ cflow pointcuts, although less - powerful. (There is currently no way to specify that a pointcut - executes below a join point matched by another pointcut.) - A control flow pointcut matches - the current call stack. For example, it might fire if the join point - was invoked by a method in the com.mycompany.web - package, or by the SomeCaller class. Control flow - pointcuts are specified using the - org.springframework.aop.support.ControlFlowPointcut - class. - Control flow pointcuts are significantly more expensive to - evaluate at runtime than even other dynamic pointcuts. In Java 1.4, - the cost is about 5 times that of other dynamic pointcuts. - -
-
-
- -
- Pointcut superclasses - - Spring provides useful pointcut superclasses to help you to - implement your own pointcuts. - - Because static pointcuts are most useful, you'll probably subclass - StaticMethodMatcherPointcut, as shown below. This requires implementing - just one abstract method (although it's possible to override other - methods to customize behavior): - - class TestStaticPointcut extends StaticMethodMatcherPointcut { - - public boolean matches(Method m, Class targetClass) { - // return true if custom criteria match - } -}There are also superclasses for dynamic pointcuts. - - You can use custom pointcuts with any advice type in Spring 1.0 - RC2 and above. -
- -
- Custom pointcuts - - Because pointcuts in Spring AOP are Java classes, rather than - language features (as in AspectJ) it's possible to declare custom - pointcuts, whether static or dynamic. Custom pointcuts in Spring can be - arbitrarily complex. However, using the AspectJ pointcut expression - language is recommended if possible. - - - Later versions of Spring may offer support for "semantic - pointcuts" as offered by JAC: for example, "all methods that change - instance variables in the target object." - -
-
- -
- Advice API in Spring - - Let's now look at how Spring AOP handles advice. - -
- Advice lifecycles - - Each advice is a Spring bean. An advice instance can be shared across all - advised objects, or unique - to each advised object. This corresponds to - per-class or per-instance - advice. - - Per-class advice is used most often. It is appropriate for generic - advice such as transaction advisors. These do not depend on the state of - the proxied object or add new state; they merely act on the method and - arguments. - - Per-instance advice is appropriate for introductions, to support - mixins. In this case, the advice adds state to the proxied - object. - - It's possible to use a mix of shared and per-instance advice in - the same AOP proxy. -
- -
- Advice types in Spring - - Spring provides several advice types out of the box, and is - extensible to support arbitrary advice types. Let us look at the basic - concepts and standard advice types. - -
- Interception around advice - - The most fundamental advice type in Spring is - interception around advice. - - Spring is compliant with the AOP Alliance interface for around - advice using method interception. MethodInterceptors implementing - around advice should implement the following interface: - - public interface MethodInterceptor extends Interceptor { - - Object invoke(MethodInvocation invocation) throws Throwable; -} - - The MethodInvocation argument to the - invoke() method exposes the method being invoked; - the target join point; the AOP proxy; and the arguments to the method. - The invoke() method should return the - invocation's result: the return value of the join point. - - A simple MethodInterceptor implementation - looks as follows: - - public class DebugInterceptor implements MethodInterceptor { - - public Object invoke(MethodInvocation invocation) throws Throwable { - System.out.println("Before: invocation=[" + invocation + "]"); - Object rval = invocation.proceed(); - System.out.println("Invocation returned"); - return rval; - } -} - - Note the call to the MethodInvocation's - proceed() method. This proceeds down the - interceptor chain towards the join point. Most interceptors will invoke - this method, and return its return value. However, a - MethodInterceptor, like any around advice, can return a different - value or throw an exception rather than invoke the proceed method. - However, you don't want to do this without good reason! - - MethodInterceptors offer interoperability with other AOP - Alliance-compliant AOP implementations. The other advice types - discussed in the remainder of this section implement common AOP - concepts, but in a Spring-specific way. While there is an advantage in - using the most specific advice type, stick with MethodInterceptor - around advice if you are likely to want to run the aspect in another - AOP framework. Note that pointcuts are not currently interoperable - between frameworks, and the AOP Alliance does not currently define - pointcut interfaces. -
- -
- Before advice - - A simpler advice type is a before - advice. This does not need a - MethodInvocation object, since it will only be - called before entering the method. - - The main advantage of a before advice is that there is no need - to invoke the proceed() method, and therefore no - possibility of inadvertently failing to proceed down the interceptor - chain. - - The MethodBeforeAdvice interface is shown - below. (Spring's API design would allow for field before advice, - although the usual objects apply to field interception and it's - unlikely that Spring will ever implement it). - - public interface MethodBeforeAdvice extends BeforeAdvice { - - void before(Method m, Object[] args, Object target) throws Throwable; -} - - Note the return type is void. Before - advice can insert custom behavior before the join point executes, but - cannot change the return value. If a before advice throws an - exception, this will abort further execution of the interceptor chain. - The exception will propagate back up the interceptor chain. If it is - unchecked, or on the signature of the invoked method, it will be - passed directly to the client; otherwise it will be wrapped in an - unchecked exception by the AOP proxy. - - An example of a before advice in Spring, which counts all method - invocations: - - public class CountingBeforeAdvice implements MethodBeforeAdvice { - - private int count; - - public void before(Method m, Object[] args, Object target) throws Throwable { - ++count; - } - - public int getCount() { - return count; - } -} - - Before advice can be used with any pointcut. -
- -
- Throws advice - - Throws advice is invoked after - the return of the join point if the join point threw an exception. - Spring offers typed throws advice. Note that this means that the - org.springframework.aop.ThrowsAdvice interface does - not contain any methods: It is a tag interface identifying that the - given object implements one or more typed throws advice methods. These - should be in the form of: - - afterThrowing([Method, args, target], subclassOfThrowable) - - Only the last argument is required. The method signatures may - have either one or four arguments, depending on whether the advice - method is interested in the method and arguments. The following - classes are examples of throws advice. - - The advice below is invoked if a RemoteException - is thrown (including subclasses): - - // Do something with remote exception - - The following advice is invoked if a - ServletException is thrown. Unlike the above - advice, it declares 4 arguments, so that it has access to the invoked - method, method arguments and target object: - - // Do something with all arguments - - The final example illustrates how these two methods could be - used in a single class, which handles both - RemoteException and - ServletException. Any number of throws advice - methods can be combined in a single class. - - public static class CombinedThrowsAdvice implements ThrowsAdvice { - - public void afterThrowing(RemoteException ex) throws Throwable { - // Do something with remote exception - } - - public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { - // Do something with all arguments - } -} - - Note: If a throws-advice method throws an exception itself, - it will override the original exception (i.e. change the exception thrown to the user). - The overriding exception will typically be a RuntimeException; this is compatible with - any method signature. However, if a throws-advice method throws a checked exception, - it will have to match the declared exceptions of the target method and is hence to some - degree coupled to specific target method signatures. Do not throw an undeclared - checked exception that is incompatible with the target method's signature! - - Throws advice can be used with any pointcut. -
- -
- After Returning advice - - An after returning advice in Spring must implement the - org.springframework.aop.AfterReturningAdvice - interface, shown below: - - public interface AfterReturningAdvice extends Advice { - - void afterReturning(Object returnValue, Method m, Object[] args, Object target) - throws Throwable; -} - - An after returning advice has access to the return value (which - it cannot modify), invoked method, methods arguments and - target. - - The following after returning advice counts all successful - method invocations that have not thrown exceptions: - - public class CountingAfterReturningAdvice implements AfterReturningAdvice { - - private int count; - - public void afterReturning(Object returnValue, Method m, Object[] args, Object target) - throws Throwable { - ++count; - } - - public int getCount() { - return count; - } -} - - This advice doesn't change the execution path. If it throws an - exception, this will be thrown up the interceptor chain instead of the - return value. - - After returning advice can be used with any pointcut. -
- -
- Introduction advice - Spring treats introduction advice as a special kind of - interception advice. - Introduction requires an IntroductionAdvisor, - and an IntroductionInterceptor, implementing the - following interface: - - public interface IntroductionInterceptor extends MethodInterceptor { - - boolean implementsInterface(Class intf); -} - - The invoke() method inherited from the AOP - Alliance MethodInterceptor interface must implement - the introduction: that is, if the invoked method is on an introduced - interface, the introduction interceptor is responsible for handling - the method call - it cannot invoke proceed(). - - - - Introduction advice cannot be used with any pointcut, as it - applies only at class, rather than method, level. You can only use - introduction advice with the IntroductionAdvisor, - which has the following methods: - - - - public interface IntroductionAdvisor extends Advisor, IntroductionInfo { - - ClassFilter getClassFilter(); - - void validateInterfaces() throws IllegalArgumentException; -} - -public interface IntroductionInfo { - - Class[] getInterfaces(); -} - - - - There is no MethodMatcher, and hence no - Pointcut, associated with introduction advice. Only - class filtering is logical. - - - - The getInterfaces() method returns the - interfaces introduced by this advisor. - - The - - validateInterfaces() - - method is used internally to see whether or not the introduced interfaces can be implemented by the configured - - IntroductionInterceptor - - . - - Let's look at a simple example from the Spring test suite. Let's - suppose we want to introduce the following interface to one or more - objects: - - - - - public interface Lockable { - void lock(); - void unlock(); - boolean locked(); -} - - - - - This illustrates a mixin. We - want to be able to cast advised objects to Lockable, whatever their - type, and call lock and unlock methods. If we call the lock() method, - we want all setter methods to throw a - LockedException. Thus we can add an aspect that - provides the ability to make objects immutable, without them having - any knowledge of it: a good example of AOP. - - - - Firstly, we'll need an - IntroductionInterceptor that does the heavy - lifting. In this case, we extend the - org.springframework.aop.support.DelegatingIntroductionInterceptor - convenience class. We could implement IntroductionInterceptor - directly, but using - DelegatingIntroductionInterceptor is best for most - cases. - - - - The DelegatingIntroductionInterceptor is - designed to delegate an introduction to an actual implementation of - the introduced interface(s), concealing the use of interception to do - so. The delegate can be set to any object using a constructor - argument; the default delegate (when the no-arg constructor is used) - is this. Thus in the example below, the delegate is the - LockMixin subclass of - DelegatingIntroductionInterceptor. Given a delegate - (by default itself), a - DelegatingIntroductionInterceptor instance looks - for all interfaces implemented by the delegate (other than - IntroductionInterceptor), and will support introductions against any - of them. It's possible for subclasses such as - LockMixin to call the - suppressInterface(Class intf) method to suppress - interfaces that should not be exposed. However, no matter how many - interfaces an IntroductionInterceptor is prepared - to support, the IntroductionAdvisor used will - control which interfaces are actually exposed. An introduced interface - will conceal any implementation of the same interface by the - target. - - - - Thus LockMixin subclasses - DelegatingIntroductionInterceptor and implements - Lockable itself. The superclass automatically picks up that Lockable - can be supported for introduction, so we don't need to specify that. - We could introduce any number of interfaces in this way. - - - - Note the use of the locked instance variable. - This effectively adds additional state to that held in the target - object. - - - - - public class LockMixin extends DelegatingIntroductionInterceptor - implements Lockable { - - private boolean locked; - - public void lock() { - this.locked = true; - } - - public void unlock() { - this.locked = false; - } - - public boolean locked() { - return this.locked; - } - - public Object invoke(MethodInvocation invocation) throws Throwable { - if (locked() && invocation.getMethod().getName().indexOf("set") == 0) - throw new LockedException(); - return super.invoke(invocation); - } - -} - - - - - Often it isn't necessary to override the invoke() - method: the - DelegatingIntroductionInterceptor - implementation - which calls the delegate method if the method is - introduced, otherwise proceeds towards the join point - is usually - sufficient. In the present case, we need to add a check: no setter - method can be invoked if in locked mode. - - - - The introduction advisor required is simple. All it needs to do - is hold a distinct LockMixin instance, and specify - the introduced interfaces - in this case, just - Lockable. A more complex example might take a - reference to the introduction interceptor (which would be defined as a - prototype): in this case, there's no configuration relevant for a - LockMixin, so we simply create it using - new. - - - - - public class LockMixinAdvisor extends DefaultIntroductionAdvisor { - - public LockMixinAdvisor() { - super(new LockMixin(), Lockable.class); - } -} - - - - - We can apply this advisor very simply: it requires no - configuration. (However, it is necessary: It's - impossible to use an IntroductionInterceptor - without an IntroductionAdvisor.) As usual with - introductions, the advisor must be per-instance, as it is stateful. We - need a different instance of LockMixinAdvisor, and - hence LockMixin, for each advised object. The - advisor comprises part of the advised object's state. - - - - We can apply this advisor programmatically, using the - Advised.addAdvisor() method, or (the recommended - way) in XML configuration, like any other advisor. All proxy creation - choices discussed below, including "auto proxy creators," correctly - handle introductions and stateful mixins. - - -
-
-
- -
- Advisor API in Spring - - In Spring, an Advisor is an aspect that contains just a single advice - object associated with a pointcut expression. - - Apart from the special case of introductions, any advisor can be - used with any advice. - org.springframework.aop.support.DefaultPointcutAdvisor - is the most commonly used advisor class. For example, it can be used with - a MethodInterceptor, BeforeAdvice or - ThrowsAdvice. - - It is possible to mix advisor and advice types in Spring in the same - AOP proxy. For example, you could use a interception around advice, throws - advice and before advice in one proxy configuration: Spring will - automatically create the necessary interceptor chain. -
- -
- Using the ProxyFactoryBean to create AOP proxies - - If you're using the Spring IoC container (an ApplicationContext or - BeanFactory) for your business objects - and you should be! - you will want - to use one of Spring's AOP FactoryBeans. (Remember that a factory bean - introduces a layer of indirection, enabling it to create objects of a - different type.) - - - The Spring 2.0 AOP support also uses factory beans under the covers. - - - The basic way to create an AOP proxy in Spring is to use the - org.springframework.aop.framework.ProxyFactoryBean. - This gives complete control over the pointcuts and advice that will apply, - and their ordering. However, there are simpler options that are preferable - if you don't need such control. - -
- Basics - - The ProxyFactoryBean, like other Spring - FactoryBean implementations, introduces a level of - indirection. If you define a ProxyFactoryBean with - name foo, what objects referencing - foo see is not the - ProxyFactoryBean instance itself, but an object - created by the ProxyFactoryBean's implementation of - the getObject() method. This method will create an - AOP proxy wrapping a target object. - - One of the most important benefits of using a - ProxyFactoryBean or another IoC-aware class to create - AOP proxies, is that it means that advices and pointcuts can also be - managed by IoC. This is a powerful feature, enabling certain approaches - that are hard to achieve with other AOP frameworks. For example, an - advice may itself reference application objects (besides the target, - which should be available in any AOP framework), benefiting from all the - pluggability provided by Dependency Injection. -
- -
- JavaBean properties - - In common with most FactoryBean implementations - provided with Spring, the ProxyFactoryBean class is - itself a JavaBean. Its properties are used to: - - - - Specify the target you want to proxy. - - - Specify whether to use CGLIB (see below and also - ). - - - - Some key properties are inherited from - org.springframework.aop.framework.ProxyConfig (the - superclass for all AOP proxy factories in Spring). These key properties include: - - - - - proxyTargetClass: true if the - target class is to be proxied, rather than the target class' interfaces. - If this property value is set to true, then CGLIB proxies - will be created (but see also below ). - - - - - optimize: controls whether or not aggressive - optimizations are applied to proxies created via CGLIB. - One should not blithely use this setting unless one fully understands - how the relevant AOP proxy handles optimization. This is currently used only - for CGLIB proxies; it has no effect with JDK dynamic proxies. - - - - frozen: if a proxy configuration is frozen, - then changes to the configuration are no longer allowed. This is useful both as - a slight optimization and for those cases when you don't want callers to be able - to manipulate the proxy (via the Advised interface) - after the proxy has been created. The default value of this property is - false, so changes such as adding additional advice are allowed. - - - - exposeProxy: determines whether or not the current - proxy should be exposed in a ThreadLocal so that - it can be accessed by the target. If a target needs to obtain - the proxy and the exposeProxy property is set to - true, the target can use the - AopContext.currentProxy() method. - - - - - aopProxyFactory: the implementation of - AopProxyFactory to use. Offers a way of - customizing whether to use dynamic proxies, CGLIB or any other proxy - strategy. The default implementation will choose dynamic proxies or - CGLIB appropriately. There should be no need to use this property; - it is intended to allow the addition of new proxy types in Spring 1.1. - - - - - Other properties specific to ProxyFactoryBean include: - - - - - proxyInterfaces: array of String interface - names. If this isn't supplied, a CGLIB proxy for the target class - will be used (but see also below ). - - - - - interceptorNames: String array of - Advisor, interceptor or other advice - names to apply. Ordering is significant, on a first come-first served - basis. That is to say that the first interceptor in the list - will be the first to be able to intercept the invocation. - - - The names are bean names in the current factory, including - bean names from ancestor factories. You can't mention bean - references here since doing so would result in the - ProxyFactoryBean ignoring the singleton - setting of the advice. - - - You can append an interceptor name with an asterisk - (*). This will result in the application of all - advisor beans with names starting with the part before the asterisk - to be applied. An example of using this feature can be found in - . - - - - - singleton: whether or not the factory should return a single - object, no matter how often the getObject() - method is called. Several FactoryBean - implementations offer such a method. The default value is - true. If you want to use stateful advice - - for example, for stateful mixins - use prototype advices along - with a singleton value of false. - - - -
- -
- JDK- and CGLIB-based proxies - - This section serves as the definitive documentation on how the - ProxyFactoryBean chooses to create one of - either a JDK- and CGLIB-based proxy for a particular target object - (that is to be proxied). - - - - The behavior of the ProxyFactoryBean with regard - to creating JDK- or CGLIB-based proxies changed between versions 1.2.x and - 2.0 of Spring. The ProxyFactoryBean now - exhibits similar semantics with regard to auto-detecting interfaces - as those of the TransactionProxyFactoryBean class. - - - - If the class of a target object that is to be proxied (hereafter simply - referred to as the target class) doesn't implement any interfaces, then - a CGLIB-based proxy will be created. This is the easiest scenario, because - JDK proxies are interface based, and no interfaces means JDK proxying - isn't even possible. One simply plugs in the target bean, and specifies the - list of interceptors via the interceptorNames property. - Note that a CGLIB-based proxy will be created even if the - proxyTargetClass property of the - ProxyFactoryBean has been set to false. - (Obviously this makes no sense, and is best removed from the bean - definition because it is at best redundant, and at worst confusing.) - - - If the target class implements one (or more) interfaces, then the type of - proxy that is created depends on the configuration of the - ProxyFactoryBean. - - - If the proxyTargetClass property of the - ProxyFactoryBean has been set to true, - then a CGLIB-based proxy will be created. This makes sense, and is in - keeping with the principle of least surprise. Even if the - proxyInterfaces property of the - ProxyFactoryBean has been set to one or more - fully qualified interface names, the fact that the - proxyTargetClass property is set to - true will cause - CGLIB-based proxying to be in effect. - - - If the proxyInterfaces property of the - ProxyFactoryBean has been set to one or more - fully qualified interface names, then a JDK-based proxy will be created. - The created proxy will implement all of the interfaces that were specified - in the proxyInterfaces property; if the target class - happens to implement a whole lot more interfaces than those specified in - the proxyInterfaces property, that is all well and - good but those additional interfaces will not be implemented by the - returned proxy. - - - If the proxyInterfaces property of the - ProxyFactoryBean has not been - set, but the target class does implement one (or more) - interfaces, then the ProxyFactoryBean will auto-detect - the fact that the target class does actually implement at least one interface, - and a JDK-based proxy will be created. The interfaces that are actually - proxied will be all of the interfaces that the target - class implements; in effect, this is the same as simply supplying a list - of each and every interface that the target class implements to the - proxyInterfaces property. However, it is significantly less - work, and less prone to typos. - - -
- -
- Proxying interfaces - - - Let's look at a simple example of ProxyFactoryBean - in action. This example involves: - - - - - A target bean that will be proxied. This - is the "personTarget" bean definition in the example below. - - - - An Advisor and an Interceptor used to provide advice. - - - - An AOP proxy bean definition specifying the target object (the - personTarget bean) and the interfaces to proxy, along with the - advices to apply. - - - - <bean id="personTarget" class="com.mycompany.PersonImpl"> - <property name="name"><value>Tony</value></property> - <property name="age"><value>51</value></property> -</bean> - -<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> - <property name="someProperty"><value>Custom string property value</value></property> -</bean> - -<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> -</bean> - -<bean id="person" - class="org.springframework.aop.framework.ProxyFactoryBean"> - <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> - - <property name="target"><ref local="personTarget"/></property> - <property name="interceptorNames"> - <list> - <value>myAdvisor</value> - <value>debugInterceptor</value> - </list> - </property> -</bean> - - Note that the interceptorNames property takes a - list of String: the bean names of the interceptor or advisors in the - current factory. Advisors, interceptors, before, after returning and - throws advice objects can be used. The ordering of advisors is - significant. - - - You might be wondering why the list doesn't hold bean - references. The reason for this is that if the ProxyFactoryBean's - singleton property is set to false, it must be able to return - independent proxy instances. If any of the advisors is itself a - prototype, an independent instance would need to be returned, so it's - necessary to be able to obtain an instance of the prototype from the - factory; holding a reference isn't sufficient. - - - The "person" bean definition above can be used in place of a - Person implementation, as follows: - - Person person = (Person) factory.getBean("person"); - - Other beans in the same IoC context can express a strongly typed - dependency on it, as with an ordinary Java object: - - <bean id="personUser" class="com.mycompany.PersonUser"> - <property name="person"><ref local="person" /></property> -</bean> - - The PersonUser class in this example would - expose a property of type Person. As far as it's concerned, the AOP - proxy can be used transparently in place of a "real" person - implementation. However, its class would be a dynamic proxy class. It - would be possible to cast it to the Advised interface - (discussed below). - - It's possible to conceal the distinction between target and proxy - using an anonymous inner bean, as follows. Only the - ProxyFactoryBean definition is different; the advice - is included only for completeness: - - <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> - <property name="someProperty"><value>Custom string property value</value></property> -</bean> - -<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> - -<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> - <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> - <!-- Use inner bean, not local reference to target --> - <property name="target"> - <bean class="com.mycompany.PersonImpl"> - <property name="name"><value>Tony</value></property> - <property name="age"><value>51</value></property> - </bean> - </property> - <property name="interceptorNames"> - <list> - <value>myAdvisor</value> - <value>debugInterceptor</value> - </list> - </property> -</bean> - - This has the advantage that there's only one object of type - Person: useful if we want to prevent users of the - application context from obtaining a reference to the un-advised object, or - need to avoid any ambiguity with Spring IoC - autowiring. There's also arguably an advantage in - that the ProxyFactoryBean definition is self-contained. However, there - are times when being able to obtain the un-advised target from the - factory might actually be an advantage: for - example, in certain test scenarios. -
- -
- Proxying classes - - What if you need to proxy a class, rather than one or more - interfaces? - - Imagine that in our example above, there was no - Person interface: we needed to advise a class called - Person that didn't implement any business interface. - In this case, you can configure Spring to use CGLIB proxying, rather - than dynamic proxies. Simply set the proxyTargetClass - property on the ProxyFactoryBean above to true. While it's best to - program to interfaces, rather than classes, the ability to advise - classes that don't implement interfaces can be useful when working with - legacy code. (In general, Spring isn't prescriptive. While it makes it - easy to apply good practices, it avoids forcing a particular - approach.) - - If you want to, you can force the use of CGLIB in any case, even if - you do have interfaces. - - CGLIB proxying works by generating a subclass of the target class - at runtime. Spring configures this generated subclass to delegate method - calls to the original target: the subclass is used to implement the - Decorator pattern, weaving in the advice. - - CGLIB proxying should generally be transparent to users. However, - there are some issues to consider: - - - - Final methods can't be advised, as they - can't be overridden. - - - - You'll need the CGLIB 2 binaries on your classpath; dynamic - proxies are available with the JDK. - - - - There's little performance difference between CGLIB proxying and - dynamic proxies. As of Spring 1.0, dynamic proxies are slightly faster. - However, this may change in the future. Performance should not be a - decisive consideration in this case. -
- -
- Using 'global' advisors - - By appending an asterisk to an interceptor name, all advisors with - bean names matching the part before the asterisk, will be added to the - advisor chain. This can come in handy if you need to add a standard set - of 'global' advisors: -<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> - <property name="target" ref="service"/> - <property name="interceptorNames"> - <list> - <value>global*</value> - </list> - </property> -</bean> - -<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> -<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/> - -
-
- -
- Concise proxy definitions - - Especially when defining transactional proxies, you may end up with - many similar proxy definitions. The use of parent and child bean - definitions, along with inner bean definitions, can result in much cleaner - and more concise proxy definitions. - - First a parent, template, bean definition is - created for the proxy: - - <bean id="txProxyTemplate" abstract="true" - class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> - <property name="transactionManager" ref="transactionManager"/> - <property name="transactionAttributes"> - <props> - <prop key="*">PROPAGATION_REQUIRED</prop> - </props> - </property> -</bean> - - This will never be instantiated itself, so may actually be - incomplete. Then each proxy which needs to be created is just a child bean - definition, which wraps the target of the proxy as an inner bean - definition, since the target will never be used on its own - anyway.<bean id="myService" parent="txProxyTemplate"> - <property name="target"> - <bean class="org.springframework.samples.MyServiceImpl"> - </bean> - </property> -</bean> - - It is of course possible to override properties from the parent - template, such as in this case, the transaction propagation - settings:<bean id="mySpecialService" parent="txProxyTemplate"> - <property name="target"> - <bean class="org.springframework.samples.MySpecialServiceImpl"> - </bean> - </property> - <property name="transactionAttributes"> - <props> - <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> - <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> - <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop> - <prop key="store*">PROPAGATION_REQUIRED</prop> - </props> - </property> -</bean> - - Note that in the example above, we have explicitly marked the parent - bean definition as abstract by using the - abstract attribute, as described previously, so that it may - not actually ever be instantiated. Application contexts (but not simple - bean factories) will by default pre-instantiate all singletons. It is therefore - important (at least for singleton beans) that if you have a (parent) - bean definition which you intend to use only as a template, and this - definition specifies a class, you must make sure to set the - abstract attribute to true, - otherwise the application context will actually try to pre-instantiate - it. -
- -
- Creating AOP proxies programmatically with the ProxyFactory - - It's easy to create AOP proxies programmatically using Spring. This - enables you to use Spring AOP without dependency on Spring IoC. - - The following listing shows creation of a proxy for a target object, - with one interceptor and one advisor. The interfaces implemented by the - target object will automatically be proxied: - - ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); -factory.addInterceptor(myMethodInterceptor); -factory.addAdvisor(myAdvisor); -MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy(); - - The first step is to construct an object of type - org.springframework.aop.framework.ProxyFactory. You can - create this with a target object, as in the above example, or specify the - interfaces to be proxied in an alternate constructor. - - You can add interceptors or advisors, and manipulate them for the - life of the ProxyFactory. If you add an - IntroductionInterceptionAroundAdvisor you can cause the proxy to implement - additional interfaces. - - There are also convenience methods on ProxyFactory (inherited from - AdvisedSupport) which allow you to add other advice types - such as before and throws advice. AdvisedSupport is the superclass of both - ProxyFactory and ProxyFactoryBean. - - - Integrating AOP proxy creation with the IoC framework is best - practice in most applications. We recommend that you externalize - configuration from Java code with AOP, as in general. - -
- -
- Manipulating advised objects - - However you create AOP proxies, you can manipulate them using the - org.springframework.aop.framework.Advised interface. - Any AOP proxy can be cast to this interface, whichever other interfaces it - implements. This interface includes the following methods: - - Advisor[] getAdvisors(); - -void addAdvice(Advice advice) throws AopConfigException; - -void addAdvice(int pos, Advice advice) - throws AopConfigException; - -void addAdvisor(Advisor advisor) throws AopConfigException; - -void addAdvisor(int pos, Advisor advisor) throws AopConfigException; - -int indexOf(Advisor advisor); - -boolean removeAdvisor(Advisor advisor) throws AopConfigException; - -void removeAdvisor(int index) throws AopConfigException; - -boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; - -boolean isFrozen(); - - The getAdvisors() method will return an Advisor - for every advisor, interceptor or other advice type that has been added to - the factory. If you added an Advisor, the returned advisor at this index - will be the object that you added. If you added an interceptor or other - advice type, Spring will have wrapped this in an advisor with a pointcut - that always returns true. Thus if you added a - MethodInterceptor, the advisor returned for this index - will be an DefaultPointcutAdvisor returning your - MethodInterceptor and a pointcut that matches all - classes and methods. - - The addAdvisor() methods can be used to add any - Advisor. Usually the advisor holding pointcut and advice will be the - generic DefaultPointcutAdvisor, which can be used with - any advice or pointcut (but not for introductions). - - By default, it's possible to add or remove advisors or interceptors - even once a proxy has been created. The only restriction is that it's - impossible to add or remove an introduction advisor, as existing proxies - from the factory will not show the interface change. (You can obtain a new - proxy from the factory to avoid this problem.) - - A simple example of casting an AOP proxy to the - Advised interface and examining and manipulating its - advice: - - Advised advised = (Advised) myObject; -Advisor[] advisors = advised.getAdvisors(); -int oldAdvisorCount = advisors.length; -System.out.println(oldAdvisorCount + " advisors"); - -// Add an advice like an interceptor without a pointcut -// Will match all proxied methods -// Can use for interceptors, before, after returning or throws advice -advised.addAdvice(new DebugInterceptor()); - -// Add selective advice using a pointcut -advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); - -assertEquals("Added two advisors", - oldAdvisorCount + 2, advised.getAdvisors().length); - - - It's questionable whether it's advisable (no pun intended) to - modify advice on a business object in production, although there are no - doubt legitimate usage cases. However, it can be very useful in - development: for example, in tests. I have sometimes found it very useful - to be able to add test code in the form of an interceptor or other advice, - getting inside a method invocation I want to test. (For example, the - advice can get inside a transaction created for that method: for example, - to run SQL to check that a database was correctly updated, before marking - the transaction for roll back.) - - - Depending on how you created the proxy, you can usually set a - frozen flag, in which case the - Advised isFrozen() method will - return true, and any attempts to modify advice through addition or removal - will result in an AopConfigException. The ability to - freeze the state of an advised object is useful in some cases, for - example, to prevent calling code removing a security interceptor. It may - also be used in Spring 1.1 to allow aggressive optimization if runtime - advice modification is known not to be required. -
- -
- Using the "autoproxy" facility - - So far we've considered explicit creation of AOP proxies using a - ProxyFactoryBean or similar factory bean. - - Spring also allows us to use "autoproxy" bean definitions, which can - automatically proxy selected bean definitions. This is built on Spring - "bean post processor" infrastructure, which enables modification of any - bean definition as the container loads. - - In this model, you set up some special bean definitions in your XML - bean definition file to configure the auto proxy infrastructure. This - allows you just to declare the targets eligible for autoproxying: you - don't need to use ProxyFactoryBean. - - There are two ways to do this: - - - - Using an autoproxy creator that refers to specific beans in the - current context. - - - - A special case of autoproxy creation that deserves to be - considered separately; autoproxy creation driven by source-level - metadata attributes. - - - -
- Autoproxy bean definitions - - The org.springframework.aop.framework.autoproxy - package provides the following standard autoproxy creators. - -
- BeanNameAutoProxyCreator - - The BeanNameAutoProxyCreator class is a - BeanPostProcessor that automatically creates AOP proxies - for beans with names matching literal values or wildcards. - - <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> - <property name="beanNames"><value>jdk*,onlyJdk</value></property> - <property name="interceptorNames"> - <list> - <value>myInterceptor</value> - </list> - </property> -</bean> - - As with ProxyFactoryBean, there is an - interceptorNames property rather than a list of interceptors, to allow - correct behavior for prototype advisors. Named "interceptors" can be - advisors or any advice type. - - As with auto proxying in general, the main point of using - BeanNameAutoProxyCreator is to apply the same - configuration consistently to multiple objects, with minimal - volume of configuration. It is a popular choice for applying - declarative transactions to multiple objects. - - Bean definitions whose names match, such as "jdkMyBean" and - "onlyJdk" in the above example, are plain old bean definitions with - the target class. An AOP proxy will be created automatically by the - BeanNameAutoProxyCreator. The same advice will be - applied to all matching beans. Note that if advisors are used (rather - than the interceptor in the above example), the pointcuts may apply - differently to different beans. -
- -
- DefaultAdvisorAutoProxyCreator - - A more general and extremely powerful auto proxy creator is - DefaultAdvisorAutoProxyCreator. This will - automagically apply eligible advisors in the current context, without - the need to include specific bean names in the autoproxy advisor's - bean definition. It offers the same merit of consistent configuration - and avoidance of duplication as - BeanNameAutoProxyCreator. - - Using this mechanism involves: - - - - Specifying a - DefaultAdvisorAutoProxyCreator bean - definition. - - - - Specifying any number of Advisors in the same or related - contexts. Note that these must be Advisors, - not just interceptors or other advices. This is necessary because - there must be a pointcut to evaluate, to check the eligibility of - each advice to candidate bean definitions. - - - - The DefaultAdvisorAutoProxyCreator will - automatically evaluate the pointcut contained in each advisor, to see - what (if any) advice it should apply to each business object (such as - "businessObject1" and "businessObject2" in the example). - - This means that any number of advisors can be applied - automatically to each business object. If no pointcut in any of the - advisors matches any method in a business object, the object will not - be proxied. As bean definitions are added for new business objects, - they will automatically be proxied if necessary. - - Autoproxying in general has the advantage of making it - impossible for callers or dependencies to obtain an un-advised object. - Calling getBean("businessObject1") on this ApplicationContext will - return an AOP proxy, not the target business object. (The "inner bean" - idiom shown earlier also offers this benefit.) - - <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> - -<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> - <property name="transactionInterceptor" ref="transactionInterceptor"/> -</bean> - -<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/> - -<bean id="businessObject1" class="com.mycompany.BusinessObject1"> - <!-- Properties omitted --> -</bean> - -<bean id="businessObject2" class="com.mycompany.BusinessObject2"/> - - - The DefaultAdvisorAutoProxyCreator is very - useful if you want to apply the same advice consistently to many - business objects. Once the infrastructure definitions are in place, - you can simply add new business objects without including specific - proxy configuration. You can also drop in additional aspects very - easily - for example, tracing or performance monitoring aspects - with - minimal change to configuration. - - The DefaultAdvisorAutoProxyCreator offers support for filtering - (using a naming convention so that only certain advisors are - evaluated, allowing use of multiple, differently configured, - AdvisorAutoProxyCreators in the same factory) and ordering. Advisors - can implement the org.springframework.core.Ordered - interface to ensure correct ordering if this is an issue. The - TransactionAttributeSourceAdvisor used in the above example has a - configurable order value; the default setting is unordered. -
- -
- AbstractAdvisorAutoProxyCreator - - This is the superclass of DefaultAdvisorAutoProxyCreator. You - can create your own autoproxy creators by subclassing this class, in - the unlikely event that advisor definitions offer insufficient - customization to the behavior of the framework - DefaultAdvisorAutoProxyCreator. -
-
- -
- Using metadata-driven auto-proxying - - A particularly important type of autoproxying is driven by - metadata. This produces a similar programming model to .NET - ServicedComponents. Instead of using XML deployment - descriptors as in EJB, configuration for transaction management and - other enterprise services is held in source-level attributes. - - In this case, you use the - DefaultAdvisorAutoProxyCreator, in combination with - Advisors that understand metadata attributes. The metadata specifics are - held in the pointcut part of the candidate advisors, rather than in the - autoproxy creation class itself. - - This is really a special case of the - DefaultAdvisorAutoProxyCreator, but deserves - consideration on its own. (The metadata-aware code is in the pointcuts - contained in the advisors, not the AOP framework itself.) - - The /attributes directory of the JPetStore - sample application shows the use of attribute-driven autoproxying. In - this case, there's no need to use the - TransactionProxyFactoryBean. Simply defining - transactional attributes on business objects is sufficient, because of - the use of metadata-aware pointcuts. The bean definitions include the - following code, in /WEB-INF/declarativeServices.xml. - Note that this is generic, and can be used outside the JPetStore: - - <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> - -<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> - <property name="transactionInterceptor" ref="transactionInterceptor"/> -</bean> - -<bean id="transactionInterceptor" - class="org.springframework.transaction.interceptor.TransactionInterceptor"> - <property name="transactionManager" ref="transactionManager"/> - <property name="transactionAttributeSource"> - <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"> - <property name="attributes" ref="attributes"/> - </bean> - </property> -</bean> - -<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/> - - The DefaultAdvisorAutoProxyCreator bean - definition (the name is not significant, hence it can even be omitted) - will pick up all eligible pointcuts in the current application context. - In this case, the "transactionAdvisor" bean definition, of type - TransactionAttributeSourceAdvisor, will apply to - classes or methods carrying a transaction attribute. The - TransactionAttributeSourceAdvisor depends on a TransactionInterceptor, - via constructor dependency. The example resolves this via autowiring. - The AttributesTransactionAttributeSource depends on - an implementation of the - org.springframework.metadata.Attributes interface. In - this fragment, the "attributes" bean satisfies this, using the Jakarta - Commons Attributes API to obtain attribute information. (The application - code must have been compiled using the Commons Attributes compilation - task.) - - The /annotation directory of the JPetStore - sample application contains an analogous example for auto-proxying - driven by JDK 1.5+ annotations. The following configuration enables - automatic detection of Spring's Transactional - annotation, leading to implicit proxies for beans containing that - annotation: - - <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> - -<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> - <property name="transactionInterceptor" ref="transactionInterceptor"/> -</bean> - -<bean id="transactionInterceptor" - class="org.springframework.transaction.interceptor.TransactionInterceptor"> - <property name="transactionManager" ref="transactionManager"/> - <property name="transactionAttributeSource"> - <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> - </property> -</bean> - - The TransactionInterceptor defined here depends - on a PlatformTransactionManager definition, which is - not included in this generic file (although it could be) because it will - be specific to the application's transaction requirements (typically - JTA, as in this example, or Hibernate, JDO or JDBC): - - <bean id="transactionManager" - class="org.springframework.transaction.jta.JtaTransactionManager"/> - - - If you require only declarative transaction management, using - these generic XML definitions will result in Spring automatically - proxying all classes or methods with transaction attributes. You won't - need to work directly with AOP, and the programming model is similar to - that of .NET ServicedComponents. - - - This mechanism is extensible. It's possible to do autoproxying - based on custom attributes. You need to: - - - - Define your custom attribute. - - - - Specify an Advisor with the necessary advice, including a - pointcut that is triggered by the presence of the custom attribute - on a class or method. You may be able to use an existing advice, - merely implementing a static pointcut that picks up the custom - attribute. - - - - It's possible for such advisors to be unique to each advised class - (for example, mixins): they simply need to be defined as prototype, - rather than singleton, bean definitions. For example, the - LockMixin introduction interceptor from the Spring - test suite, shown above, could be used in conjunction with an - attribute-driven pointcut to target a mixin, as shown here. We use the - generic DefaultPointcutAdvisor, configured using - JavaBean properties: - - <bean id="lockMixin" class="org.springframework.aop.LockMixin" - scope="prototype"/> - -<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" - scope="prototype"> - <property name="pointcut" ref="myAttributeAwarePointcut"/> - <property name="advice" ref="lockMixin"/> -</bean> - -<bean id="anyBean" class="anyclass" ... - - If the attribute aware pointcut matches any methods in the - anyBean or other bean definitions, the mixin will be - applied. Note that both lockMixin and - lockableAdvisor definitions are prototypes. The - myAttributeAwarePointcut pointcut can be a singleton - definition, as it doesn't hold state for individual advised - objects. -
-
- -
- Using TargetSources - - Spring offers the concept of a TargetSource, - expressed in the org.springframework.aop.TargetSource - interface. This interface is responsible for returning the "target object" - implementing the join point. The TargetSource - implementation is asked for a target instance each time the AOP proxy - handles a method invocation. - - Developers using Spring AOP don't normally need to work directly - with TargetSources, but this provides a powerful means of supporting - pooling, hot swappable and other sophisticated targets. For example, a - pooling TargetSource can return a different target instance for each - invocation, using a pool to manage instances. - - If you do not specify a TargetSource, a default implementation is - used that wraps a local object. The same target is returned for each - invocation (as you would expect). - - Let's look at the standard target sources provided with Spring, and - how you can use them. - - - When using a custom target source, your target will usually need - to be a prototype rather than a singleton bean definition. This allows - Spring to create a new target instance when required. - - -
- Hot swappable target sources - - The - org.springframework.aop.target.HotSwappableTargetSource - exists to allow the target of an AOP proxy to be switched while allowing - callers to keep their references to it. - - Changing the target source's target takes effect immediately. The - HotSwappableTargetSource is threadsafe. - - You can change the target via the swap() method - on HotSwappableTargetSource as follows: - - HotSwappableTargetSource swapper = - (HotSwappableTargetSource) beanFactory.getBean("swapper"); -Object oldTarget = swapper.swap(newTarget); - - The XML definitions required look as follows: - - <bean id="initialTarget" class="mycompany.OldTarget"/> - -<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource"> - <constructor-arg ref="initialTarget"/> -</bean> - -<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean"> - <property name="targetSource" ref="swapper"/> -</bean> - - The above swap() call changes the target of the - swappable bean. Clients who hold a reference to that bean will be - unaware of the change, but will immediately start hitting the new - target. - - Although this example doesn't add any advice - and it's not - necessary to add advice to use a TargetSource - of - course any TargetSource can be used in conjunction - with arbitrary advice. -
- -
- Pooling target sources - - Using a pooling target source provides a similar programming model - to stateless session EJBs, in which a pool of identical instances is - maintained, with method invocations going to free objects in the - pool. - - A crucial difference between Spring pooling and SLSB pooling is - that Spring pooling can be applied to any POJO. As with Spring in - general, this service can be applied in a non-invasive way. - - Spring provides out-of-the-box support for Jakarta Commons Pool - 1.3, which provides a fairly efficient pooling implementation. You'll - need the commons-pool Jar on your application's classpath to use this - feature. It's also possible to subclass - org.springframework.aop.target.AbstractPoolingTargetSource - to support any other pooling API. - - Sample configuration is shown below: - - <bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" - scope="prototype"> - ... properties omitted -</bean> - -<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource"> - <property name="targetBeanName" value="businessObjectTarget"/> - <property name="maxSize" value="25"/> -</bean> - -<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"> - <property name="targetSource" ref="poolTargetSource"/> - <property name="interceptorNames" value="myInterceptor"/> -</bean> - - Note that the target object - "businessObjectTarget" in the - example - must be a prototype. This allows the - PoolingTargetSource implementation to create new - instances of the target to grow the pool as necessary. See the havadoc - for AbstractPoolingTargetSource and the concrete - subclass you wish to use for information about its properties: "maxSize" - is the most basic, and always guaranteed to be present. - - In this case, "myInterceptor" is the name of an interceptor that - would need to be defined in the same IoC context. However, it isn't - necessary to specify interceptors to use pooling. If you want only - pooling, and no other advice, don't set the interceptorNames property at - all. - - It's possible to configure Spring so as to be able to cast any - pooled object to the - org.springframework.aop.target.PoolingConfig - interface, which exposes information about the configuration and current - size of the pool through an introduction. You'll need to define an - advisor like this: - - <bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> - <property name="targetObject" ref="poolTargetSource"/> - <property name="targetMethod" value="getPoolingConfigMixin"/> -</bean> - - This advisor is obtained by calling a convenience method on the - AbstractPoolingTargetSource class, hence the use of - MethodInvokingFactoryBean. This advisor's name ("poolConfigAdvisor" - here) must be in the list of interceptors names in the ProxyFactoryBean - exposing the pooled object. - - The cast will look as follows: - - - - - Pooling stateless service objects is not usually necessary. We - don't believe it should be the default choice, as most stateless objects - are naturally thread safe, and instance pooling is problematic if - resources are cached. - - - Simpler pooling is available using autoproxying. It's possible to - set the TargetSources used by any autoproxy creator. -
- -
- Prototype target sources - - Setting up a "prototype" target source is similar to a pooling - TargetSource. In this case, a new instance of the target will be created - on every method invocation. Although the cost of creating a new object - isn't high in a modern JVM, the cost of wiring up the new object - (satisfying its IoC dependencies) may be more expensive. Thus you - shouldn't use this approach without very good reason. - - To do this, you could modify the - poolTargetSource definition shown above as follows. - (I've also changed the name, for clarity.) - - - -]]> - - There's only one property: the name of the target bean. - Inheritance is used in the TargetSource implementations to ensure - consistent naming. As with the pooling target source, the target bean - must be a prototype bean definition. -
- -
- <classname>ThreadLocal</classname> target sources - - ThreadLocal target sources are useful if you need an object to be - created for each incoming request (per thread that is). The concept of a - ThreadLocal provide a JDK-wide facility to - transparently store resource alongside a thread. Setting up a - ThreadLocalTargetSource is pretty much the same as was explained for the - other types of target source: - - - -]]> - - - ThreadLocals come with serious issues (potentially - resulting in memory leaks) when incorrectly using them in a - multi-threaded and multi-classloader environments. One should always - consider wrapping a threadlocal in some other class and never directly - use the ThreadLocal itself (except of course in the wrapper class). - Also, one should always remember to correctly set and unset (where the - latter simply involved a call to ThreadLocal.set(null)) the resource - local to the thread. Unsetting should be done in any case since not - unsetting it might result in problematic behavior. Spring's ThreadLocal - support does this for you and should always be considered in favor - of using ThreadLocals without other proper handling - code. - -
-
- -
- Defining new <interfacename>Advice</interfacename> types - - Spring AOP is designed to be extensible. While the interception - implementation strategy is presently used internally, it is possible to - support arbitrary advice types in addition to the out-of-the-box interception around advice, - before, throws advice and after returning advice. - - The org.springframework.aop.framework.adapter - package is an SPI package allowing support for new custom advice types to - be added without changing the core framework. The only constraint on a - custom Advice type is that it must implement the - org.aopalliance.aop.Advice tag interface. - - Please refer to the - org.springframework.aop.framework.adapter package's - Javadocs for further information. -
- -
- Further resources - - Please refer to the Spring sample applications for further examples - of Spring AOP: - - - - The JPetStore's default configuration illustrates the use of the - TransactionProxyFactoryBean for declarative transaction - management. - - - - The /attributes directory of the JPetStore - illustrates the use of attribute-driven declarative transaction management. - - - -
- -
+ + + + Classic Spring AOP Usage + + In this appendix we discuss + the lower-level Spring AOP APIs and the AOP support used in Spring 1.2 applications. + For new applications, we recommend the use of the Spring 2.0 AOP support + described in the AOP chapter, but when working with existing applications, + or when reading books and articles, you may come across Spring 1.2 style examples. + Spring 2.0 is fully backwards compatible with Spring 1.2 and everything described + in this appendix is fully supported in Spring 2.0. + +
+ Pointcut API in Spring + + Let's look at how Spring handles the crucial pointcut concept. + +
+ Concepts + + Spring's pointcut model enables pointcut reuse independent of + advice types. It's possible to target different advice using the same + pointcut. + + The org.springframework.aop.Pointcut interface + is the central interface, used to target advices to particular classes + and methods. The complete interface is shown below: + + + + Splitting the Pointcut interface into two parts + allows reuse of class and method matching parts, and fine-grained + composition operations (such as performing a "union" with another method + matcher). + + The ClassFilter interface is used to restrict + the pointcut to a given set of target classes. If the + matches() method always returns true, all target + classes will be matched: + + + + The MethodMatcher interface is normally more + important. The complete interface is shown below: + + + + The matches(Method, Class) method is used to + test whether this pointcut will ever match a given method on a target + class. This evaluation can be performed when an AOP proxy is created, to + avoid the need for a test on every method invocation. If the 2-argument + matches method returns true for a given method, and the + isRuntime() method for the MethodMatcher returns + true, the 3-argument matches method will be invoked on every method + invocation. This enables a pointcut to look at the arguments passed to + the method invocation immediately before the target advice is to + execute. + + Most MethodMatchers are static, meaning that their + isRuntime() method returns false. In this case, the + 3-argument matches method will never be invoked. + + + If possible, try to make pointcuts static, allowing the AOP + framework to cache the results of pointcut evaluation when an AOP proxy + is created. + +
+ +
+ Operations on pointcuts + + Spring supports operations on pointcuts: notably, + union and intersection. + + + + Union means the methods that either pointcut matches. + + + Intersection means the methods that both pointcuts match. + + + Union is usually more useful. + + + Pointcuts can be composed using the static methods in the + org.springframework.aop.support.Pointcuts class, or + using the ComposablePointcut class in the same + package. However, using AspectJ pointcut expressions is usually a + simpler approach. + + + +
+ +
+ AspectJ expression pointcuts + + Since 2.0, the most important type of pointcut used by Spring is + org.springframework.aop.aspectj.AspectJExpressionPointcut. + This is a pointcut that uses an AspectJ supplied library to parse an AspectJ + pointcut expression string. + + See the previous chapter for a discussion of supported AspectJ pointcut + primitives. + +
+ +
+ Convenience pointcut implementations + + Spring provides several convenient pointcut implementations. Some + can be used out of the box; others are intended to be subclassed in + application-specific pointcuts. + +
+ Static pointcuts + + Static pointcuts are based on method and target class, and + cannot take into account the method's arguments. Static pointcuts are + sufficient - and best - for most usages. It's possible for Spring to + evaluate a static pointcut only once, when a method is first invoked: + after that, there is no need to evaluate the pointcut again with each + method invocation. + + Let's consider some static pointcut implementations included + with Spring. + +
+ Regular expression pointcuts + + One obvious way to specify static pointcuts is regular + expressions. Several AOP frameworks besides Spring make this + possible. + org.springframework.aop.support.Perl5RegexpMethodPointcut + is a generic regular expression pointcut, using Perl 5 regular + expression syntax. The Perl5RegexpMethodPointcut + class depends on Jakarta ORO for regular expression matching. Spring + also provides the JdkRegexpMethodPointcut class + that uses the regular expression support in JDK 1.4+. + + Using the Perl5RegexpMethodPointcut class, + you can provide a list of pattern Strings. If any of these is a + match, the pointcut will evaluate to true. (So the result is + effectively the union of these pointcuts.) + + The usage is shown below: + + <bean id="settersAndAbsquatulatePointcut" + class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> + <property name="patterns"> + <list> + <value>.*set.*</value> + <value>.*absquatulate</value> + </list> + </property> +</bean> + + Spring provides a convenience class, + RegexpMethodPointcutAdvisor, that allows us to + also reference an Advice (remember that an Advice can be an + interceptor, before advice, throws advice etc.). Behind the scenes, + Spring will use a JdkRegexpMethodPointcut. Using + RegexpMethodPointcutAdvisor simplifies wiring, + as the one bean encapsulates both pointcut and advice, as shown + below: + + <bean id="settersAndAbsquatulateAdvisor" + class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> + <property name="advice"> + <ref local="beanNameOfAopAllianceInterceptor"/> + </property> + <property name="patterns"> + <list> + <value>.*set.*</value> + <value>.*absquatulate</value> + </list> + </property> +</bean> + + RegexpMethodPointcutAdvisor can be used + with any Advice type. +
+ +
+ Attribute-driven pointcuts + + An important type of static pointcut is a + metadata-driven pointcut. This uses the values + of metadata attributes: typically, source-level metadata. +
+
+ +
+ Dynamic pointcuts + + Dynamic pointcuts are costlier to evaluate than static + pointcuts. They take into account method + arguments, as well as static information. This + means that they must be evaluated with every method invocation; the + result cannot be cached, as arguments will vary. + + The main example is the control flow + pointcut. + +
+ Control flow pointcuts + + Spring control flow pointcuts are conceptually similar to + AspectJ cflow pointcuts, although less + powerful. (There is currently no way to specify that a pointcut + executes below a join point matched by another pointcut.) + A control flow pointcut matches + the current call stack. For example, it might fire if the join point + was invoked by a method in the com.mycompany.web + package, or by the SomeCaller class. Control flow + pointcuts are specified using the + org.springframework.aop.support.ControlFlowPointcut + class. + Control flow pointcuts are significantly more expensive to + evaluate at runtime than even other dynamic pointcuts. In Java 1.4, + the cost is about 5 times that of other dynamic pointcuts. + +
+
+
+ +
+ Pointcut superclasses + + Spring provides useful pointcut superclasses to help you to + implement your own pointcuts. + + Because static pointcuts are most useful, you'll probably subclass + StaticMethodMatcherPointcut, as shown below. This requires implementing + just one abstract method (although it's possible to override other + methods to customize behavior): + + class TestStaticPointcut extends StaticMethodMatcherPointcut { + + public boolean matches(Method m, Class targetClass) { + // return true if custom criteria match + } +}There are also superclasses for dynamic pointcuts. + + You can use custom pointcuts with any advice type in Spring 1.0 + RC2 and above. +
+ +
+ Custom pointcuts + + Because pointcuts in Spring AOP are Java classes, rather than + language features (as in AspectJ) it's possible to declare custom + pointcuts, whether static or dynamic. Custom pointcuts in Spring can be + arbitrarily complex. However, using the AspectJ pointcut expression + language is recommended if possible. + + + Later versions of Spring may offer support for "semantic + pointcuts" as offered by JAC: for example, "all methods that change + instance variables in the target object." + +
+
+ +
+ Advice API in Spring + + Let's now look at how Spring AOP handles advice. + +
+ Advice lifecycles + + Each advice is a Spring bean. An advice instance can be shared across all + advised objects, or unique + to each advised object. This corresponds to + per-class or per-instance + advice. + + Per-class advice is used most often. It is appropriate for generic + advice such as transaction advisors. These do not depend on the state of + the proxied object or add new state; they merely act on the method and + arguments. + + Per-instance advice is appropriate for introductions, to support + mixins. In this case, the advice adds state to the proxied + object. + + It's possible to use a mix of shared and per-instance advice in + the same AOP proxy. +
+ +
+ Advice types in Spring + + Spring provides several advice types out of the box, and is + extensible to support arbitrary advice types. Let us look at the basic + concepts and standard advice types. + +
+ Interception around advice + + The most fundamental advice type in Spring is + interception around advice. + + Spring is compliant with the AOP Alliance interface for around + advice using method interception. MethodInterceptors implementing + around advice should implement the following interface: + + public interface MethodInterceptor extends Interceptor { + + Object invoke(MethodInvocation invocation) throws Throwable; +} + + The MethodInvocation argument to the + invoke() method exposes the method being invoked; + the target join point; the AOP proxy; and the arguments to the method. + The invoke() method should return the + invocation's result: the return value of the join point. + + A simple MethodInterceptor implementation + looks as follows: + + public class DebugInterceptor implements MethodInterceptor { + + public Object invoke(MethodInvocation invocation) throws Throwable { + System.out.println("Before: invocation=[" + invocation + "]"); + Object rval = invocation.proceed(); + System.out.println("Invocation returned"); + return rval; + } +} + + Note the call to the MethodInvocation's + proceed() method. This proceeds down the + interceptor chain towards the join point. Most interceptors will invoke + this method, and return its return value. However, a + MethodInterceptor, like any around advice, can return a different + value or throw an exception rather than invoke the proceed method. + However, you don't want to do this without good reason! + + MethodInterceptors offer interoperability with other AOP + Alliance-compliant AOP implementations. The other advice types + discussed in the remainder of this section implement common AOP + concepts, but in a Spring-specific way. While there is an advantage in + using the most specific advice type, stick with MethodInterceptor + around advice if you are likely to want to run the aspect in another + AOP framework. Note that pointcuts are not currently interoperable + between frameworks, and the AOP Alliance does not currently define + pointcut interfaces. +
+ +
+ Before advice + + A simpler advice type is a before + advice. This does not need a + MethodInvocation object, since it will only be + called before entering the method. + + The main advantage of a before advice is that there is no need + to invoke the proceed() method, and therefore no + possibility of inadvertently failing to proceed down the interceptor + chain. + + The MethodBeforeAdvice interface is shown + below. (Spring's API design would allow for field before advice, + although the usual objects apply to field interception and it's + unlikely that Spring will ever implement it). + + public interface MethodBeforeAdvice extends BeforeAdvice { + + void before(Method m, Object[] args, Object target) throws Throwable; +} + + Note the return type is void. Before + advice can insert custom behavior before the join point executes, but + cannot change the return value. If a before advice throws an + exception, this will abort further execution of the interceptor chain. + The exception will propagate back up the interceptor chain. If it is + unchecked, or on the signature of the invoked method, it will be + passed directly to the client; otherwise it will be wrapped in an + unchecked exception by the AOP proxy. + + An example of a before advice in Spring, which counts all method + invocations: + + public class CountingBeforeAdvice implements MethodBeforeAdvice { + + private int count; + + public void before(Method m, Object[] args, Object target) throws Throwable { + ++count; + } + + public int getCount() { + return count; + } +} + + Before advice can be used with any pointcut. +
+ +
+ Throws advice + + Throws advice is invoked after + the return of the join point if the join point threw an exception. + Spring offers typed throws advice. Note that this means that the + org.springframework.aop.ThrowsAdvice interface does + not contain any methods: It is a tag interface identifying that the + given object implements one or more typed throws advice methods. These + should be in the form of: + + afterThrowing([Method, args, target], subclassOfThrowable) + + Only the last argument is required. The method signatures may + have either one or four arguments, depending on whether the advice + method is interested in the method and arguments. The following + classes are examples of throws advice. + + The advice below is invoked if a RemoteException + is thrown (including subclasses): + + // Do something with remote exception + + The following advice is invoked if a + ServletException is thrown. Unlike the above + advice, it declares 4 arguments, so that it has access to the invoked + method, method arguments and target object: + + // Do something with all arguments + + The final example illustrates how these two methods could be + used in a single class, which handles both + RemoteException and + ServletException. Any number of throws advice + methods can be combined in a single class. + + public static class CombinedThrowsAdvice implements ThrowsAdvice { + + public void afterThrowing(RemoteException ex) throws Throwable { + // Do something with remote exception + } + + public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) { + // Do something with all arguments + } +} + + Note: If a throws-advice method throws an exception itself, + it will override the original exception (i.e. change the exception thrown to the user). + The overriding exception will typically be a RuntimeException; this is compatible with + any method signature. However, if a throws-advice method throws a checked exception, + it will have to match the declared exceptions of the target method and is hence to some + degree coupled to specific target method signatures. Do not throw an undeclared + checked exception that is incompatible with the target method's signature! + + Throws advice can be used with any pointcut. +
+ +
+ After Returning advice + + An after returning advice in Spring must implement the + org.springframework.aop.AfterReturningAdvice + interface, shown below: + + public interface AfterReturningAdvice extends Advice { + + void afterReturning(Object returnValue, Method m, Object[] args, Object target) + throws Throwable; +} + + An after returning advice has access to the return value (which + it cannot modify), invoked method, methods arguments and + target. + + The following after returning advice counts all successful + method invocations that have not thrown exceptions: + + public class CountingAfterReturningAdvice implements AfterReturningAdvice { + + private int count; + + public void afterReturning(Object returnValue, Method m, Object[] args, Object target) + throws Throwable { + ++count; + } + + public int getCount() { + return count; + } +} + + This advice doesn't change the execution path. If it throws an + exception, this will be thrown up the interceptor chain instead of the + return value. + + After returning advice can be used with any pointcut. +
+ +
+ Introduction advice + Spring treats introduction advice as a special kind of + interception advice. + Introduction requires an IntroductionAdvisor, + and an IntroductionInterceptor, implementing the + following interface: + + public interface IntroductionInterceptor extends MethodInterceptor { + + boolean implementsInterface(Class intf); +} + + The invoke() method inherited from the AOP + Alliance MethodInterceptor interface must implement + the introduction: that is, if the invoked method is on an introduced + interface, the introduction interceptor is responsible for handling + the method call - it cannot invoke proceed(). + + + + Introduction advice cannot be used with any pointcut, as it + applies only at class, rather than method, level. You can only use + introduction advice with the IntroductionAdvisor, + which has the following methods: + + + + public interface IntroductionAdvisor extends Advisor, IntroductionInfo { + + ClassFilter getClassFilter(); + + void validateInterfaces() throws IllegalArgumentException; +} + +public interface IntroductionInfo { + + Class[] getInterfaces(); +} + + + + There is no MethodMatcher, and hence no + Pointcut, associated with introduction advice. Only + class filtering is logical. + + + + The getInterfaces() method returns the + interfaces introduced by this advisor. + + The + + validateInterfaces() + + method is used internally to see whether or not the introduced interfaces can be implemented by the configured + + IntroductionInterceptor + + . + + Let's look at a simple example from the Spring test suite. Let's + suppose we want to introduce the following interface to one or more + objects: + + + + + public interface Lockable { + void lock(); + void unlock(); + boolean locked(); +} + + + + + This illustrates a mixin. We + want to be able to cast advised objects to Lockable, whatever their + type, and call lock and unlock methods. If we call the lock() method, + we want all setter methods to throw a + LockedException. Thus we can add an aspect that + provides the ability to make objects immutable, without them having + any knowledge of it: a good example of AOP. + + + + Firstly, we'll need an + IntroductionInterceptor that does the heavy + lifting. In this case, we extend the + org.springframework.aop.support.DelegatingIntroductionInterceptor + convenience class. We could implement IntroductionInterceptor + directly, but using + DelegatingIntroductionInterceptor is best for most + cases. + + + + The DelegatingIntroductionInterceptor is + designed to delegate an introduction to an actual implementation of + the introduced interface(s), concealing the use of interception to do + so. The delegate can be set to any object using a constructor + argument; the default delegate (when the no-arg constructor is used) + is this. Thus in the example below, the delegate is the + LockMixin subclass of + DelegatingIntroductionInterceptor. Given a delegate + (by default itself), a + DelegatingIntroductionInterceptor instance looks + for all interfaces implemented by the delegate (other than + IntroductionInterceptor), and will support introductions against any + of them. It's possible for subclasses such as + LockMixin to call the + suppressInterface(Class intf) method to suppress + interfaces that should not be exposed. However, no matter how many + interfaces an IntroductionInterceptor is prepared + to support, the IntroductionAdvisor used will + control which interfaces are actually exposed. An introduced interface + will conceal any implementation of the same interface by the + target. + + + + Thus LockMixin subclasses + DelegatingIntroductionInterceptor and implements + Lockable itself. The superclass automatically picks up that Lockable + can be supported for introduction, so we don't need to specify that. + We could introduce any number of interfaces in this way. + + + + Note the use of the locked instance variable. + This effectively adds additional state to that held in the target + object. + + + + + public class LockMixin extends DelegatingIntroductionInterceptor + implements Lockable { + + private boolean locked; + + public void lock() { + this.locked = true; + } + + public void unlock() { + this.locked = false; + } + + public boolean locked() { + return this.locked; + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + if (locked() && invocation.getMethod().getName().indexOf("set") == 0) + throw new LockedException(); + return super.invoke(invocation); + } + +} + + + + + Often it isn't necessary to override the invoke() + method: the + DelegatingIntroductionInterceptor + implementation - which calls the delegate method if the method is + introduced, otherwise proceeds towards the join point - is usually + sufficient. In the present case, we need to add a check: no setter + method can be invoked if in locked mode. + + + + The introduction advisor required is simple. All it needs to do + is hold a distinct LockMixin instance, and specify + the introduced interfaces - in this case, just + Lockable. A more complex example might take a + reference to the introduction interceptor (which would be defined as a + prototype): in this case, there's no configuration relevant for a + LockMixin, so we simply create it using + new. + + + + + public class LockMixinAdvisor extends DefaultIntroductionAdvisor { + + public LockMixinAdvisor() { + super(new LockMixin(), Lockable.class); + } +} + + + + + We can apply this advisor very simply: it requires no + configuration. (However, it is necessary: It's + impossible to use an IntroductionInterceptor + without an IntroductionAdvisor.) As usual with + introductions, the advisor must be per-instance, as it is stateful. We + need a different instance of LockMixinAdvisor, and + hence LockMixin, for each advised object. The + advisor comprises part of the advised object's state. + + + + We can apply this advisor programmatically, using the + Advised.addAdvisor() method, or (the recommended + way) in XML configuration, like any other advisor. All proxy creation + choices discussed below, including "auto proxy creators," correctly + handle introductions and stateful mixins. + + +
+
+
+ +
+ Advisor API in Spring + + In Spring, an Advisor is an aspect that contains just a single advice + object associated with a pointcut expression. + + Apart from the special case of introductions, any advisor can be + used with any advice. + org.springframework.aop.support.DefaultPointcutAdvisor + is the most commonly used advisor class. For example, it can be used with + a MethodInterceptor, BeforeAdvice or + ThrowsAdvice. + + It is possible to mix advisor and advice types in Spring in the same + AOP proxy. For example, you could use a interception around advice, throws + advice and before advice in one proxy configuration: Spring will + automatically create the necessary interceptor chain. +
+ +
+ Using the ProxyFactoryBean to create AOP proxies + + If you're using the Spring IoC container (an ApplicationContext or + BeanFactory) for your business objects - and you should be! - you will want + to use one of Spring's AOP FactoryBeans. (Remember that a factory bean + introduces a layer of indirection, enabling it to create objects of a + different type.) + + + The Spring 2.0 AOP support also uses factory beans under the covers. + + + The basic way to create an AOP proxy in Spring is to use the + org.springframework.aop.framework.ProxyFactoryBean. + This gives complete control over the pointcuts and advice that will apply, + and their ordering. However, there are simpler options that are preferable + if you don't need such control. + +
+ Basics + + The ProxyFactoryBean, like other Spring + FactoryBean implementations, introduces a level of + indirection. If you define a ProxyFactoryBean with + name foo, what objects referencing + foo see is not the + ProxyFactoryBean instance itself, but an object + created by the ProxyFactoryBean's implementation of + the getObject() method. This method will create an + AOP proxy wrapping a target object. + + One of the most important benefits of using a + ProxyFactoryBean or another IoC-aware class to create + AOP proxies, is that it means that advices and pointcuts can also be + managed by IoC. This is a powerful feature, enabling certain approaches + that are hard to achieve with other AOP frameworks. For example, an + advice may itself reference application objects (besides the target, + which should be available in any AOP framework), benefiting from all the + pluggability provided by Dependency Injection. +
+ +
+ JavaBean properties + + In common with most FactoryBean implementations + provided with Spring, the ProxyFactoryBean class is + itself a JavaBean. Its properties are used to: + + + + Specify the target you want to proxy. + + + Specify whether to use CGLIB (see below and also + ). + + + + Some key properties are inherited from + org.springframework.aop.framework.ProxyConfig (the + superclass for all AOP proxy factories in Spring). These key properties include: + + + + + proxyTargetClass: true if the + target class is to be proxied, rather than the target class' interfaces. + If this property value is set to true, then CGLIB proxies + will be created (but see also below ). + + + + + optimize: controls whether or not aggressive + optimizations are applied to proxies created via CGLIB. + One should not blithely use this setting unless one fully understands + how the relevant AOP proxy handles optimization. This is currently used only + for CGLIB proxies; it has no effect with JDK dynamic proxies. + + + + frozen: if a proxy configuration is frozen, + then changes to the configuration are no longer allowed. This is useful both as + a slight optimization and for those cases when you don't want callers to be able + to manipulate the proxy (via the Advised interface) + after the proxy has been created. The default value of this property is + false, so changes such as adding additional advice are allowed. + + + + exposeProxy: determines whether or not the current + proxy should be exposed in a ThreadLocal so that + it can be accessed by the target. If a target needs to obtain + the proxy and the exposeProxy property is set to + true, the target can use the + AopContext.currentProxy() method. + + + + + aopProxyFactory: the implementation of + AopProxyFactory to use. Offers a way of + customizing whether to use dynamic proxies, CGLIB or any other proxy + strategy. The default implementation will choose dynamic proxies or + CGLIB appropriately. There should be no need to use this property; + it is intended to allow the addition of new proxy types in Spring 1.1. + + + + + Other properties specific to ProxyFactoryBean include: + + + + + proxyInterfaces: array of String interface + names. If this isn't supplied, a CGLIB proxy for the target class + will be used (but see also below ). + + + + + interceptorNames: String array of + Advisor, interceptor or other advice + names to apply. Ordering is significant, on a first come-first served + basis. That is to say that the first interceptor in the list + will be the first to be able to intercept the invocation. + + + The names are bean names in the current factory, including + bean names from ancestor factories. You can't mention bean + references here since doing so would result in the + ProxyFactoryBean ignoring the singleton + setting of the advice. + + + You can append an interceptor name with an asterisk + (*). This will result in the application of all + advisor beans with names starting with the part before the asterisk + to be applied. An example of using this feature can be found in + . + + + + + singleton: whether or not the factory should return a single + object, no matter how often the getObject() + method is called. Several FactoryBean + implementations offer such a method. The default value is + true. If you want to use stateful advice - + for example, for stateful mixins - use prototype advices along + with a singleton value of false. + + + +
+ +
+ JDK- and CGLIB-based proxies + + This section serves as the definitive documentation on how the + ProxyFactoryBean chooses to create one of + either a JDK- and CGLIB-based proxy for a particular target object + (that is to be proxied). + + + + The behavior of the ProxyFactoryBean with regard + to creating JDK- or CGLIB-based proxies changed between versions 1.2.x and + 2.0 of Spring. The ProxyFactoryBean now + exhibits similar semantics with regard to auto-detecting interfaces + as those of the TransactionProxyFactoryBean class. + + + + If the class of a target object that is to be proxied (hereafter simply + referred to as the target class) doesn't implement any interfaces, then + a CGLIB-based proxy will be created. This is the easiest scenario, because + JDK proxies are interface based, and no interfaces means JDK proxying + isn't even possible. One simply plugs in the target bean, and specifies the + list of interceptors via the interceptorNames property. + Note that a CGLIB-based proxy will be created even if the + proxyTargetClass property of the + ProxyFactoryBean has been set to false. + (Obviously this makes no sense, and is best removed from the bean + definition because it is at best redundant, and at worst confusing.) + + + If the target class implements one (or more) interfaces, then the type of + proxy that is created depends on the configuration of the + ProxyFactoryBean. + + + If the proxyTargetClass property of the + ProxyFactoryBean has been set to true, + then a CGLIB-based proxy will be created. This makes sense, and is in + keeping with the principle of least surprise. Even if the + proxyInterfaces property of the + ProxyFactoryBean has been set to one or more + fully qualified interface names, the fact that the + proxyTargetClass property is set to + true will cause + CGLIB-based proxying to be in effect. + + + If the proxyInterfaces property of the + ProxyFactoryBean has been set to one or more + fully qualified interface names, then a JDK-based proxy will be created. + The created proxy will implement all of the interfaces that were specified + in the proxyInterfaces property; if the target class + happens to implement a whole lot more interfaces than those specified in + the proxyInterfaces property, that is all well and + good but those additional interfaces will not be implemented by the + returned proxy. + + + If the proxyInterfaces property of the + ProxyFactoryBean has not been + set, but the target class does implement one (or more) + interfaces, then the ProxyFactoryBean will auto-detect + the fact that the target class does actually implement at least one interface, + and a JDK-based proxy will be created. The interfaces that are actually + proxied will be all of the interfaces that the target + class implements; in effect, this is the same as simply supplying a list + of each and every interface that the target class implements to the + proxyInterfaces property. However, it is significantly less + work, and less prone to typos. + + +
+ +
+ Proxying interfaces + + + Let's look at a simple example of ProxyFactoryBean + in action. This example involves: + + + + + A target bean that will be proxied. This + is the "personTarget" bean definition in the example below. + + + + An Advisor and an Interceptor used to provide advice. + + + + An AOP proxy bean definition specifying the target object (the + personTarget bean) and the interfaces to proxy, along with the + advices to apply. + + + + <bean id="personTarget" class="com.mycompany.PersonImpl"> + <property name="name"><value>Tony</value></property> + <property name="age"><value>51</value></property> +</bean> + +<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> + <property name="someProperty"><value>Custom string property value</value></property> +</bean> + +<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> +</bean> + +<bean id="person" + class="org.springframework.aop.framework.ProxyFactoryBean"> + <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> + + <property name="target"><ref local="personTarget"/></property> + <property name="interceptorNames"> + <list> + <value>myAdvisor</value> + <value>debugInterceptor</value> + </list> + </property> +</bean> + + Note that the interceptorNames property takes a + list of String: the bean names of the interceptor or advisors in the + current factory. Advisors, interceptors, before, after returning and + throws advice objects can be used. The ordering of advisors is + significant. + + + You might be wondering why the list doesn't hold bean + references. The reason for this is that if the ProxyFactoryBean's + singleton property is set to false, it must be able to return + independent proxy instances. If any of the advisors is itself a + prototype, an independent instance would need to be returned, so it's + necessary to be able to obtain an instance of the prototype from the + factory; holding a reference isn't sufficient. + + + The "person" bean definition above can be used in place of a + Person implementation, as follows: + + Person person = (Person) factory.getBean("person"); + + Other beans in the same IoC context can express a strongly typed + dependency on it, as with an ordinary Java object: + + <bean id="personUser" class="com.mycompany.PersonUser"> + <property name="person"><ref local="person" /></property> +</bean> + + The PersonUser class in this example would + expose a property of type Person. As far as it's concerned, the AOP + proxy can be used transparently in place of a "real" person + implementation. However, its class would be a dynamic proxy class. It + would be possible to cast it to the Advised interface + (discussed below). + + It's possible to conceal the distinction between target and proxy + using an anonymous inner bean, as follows. Only the + ProxyFactoryBean definition is different; the advice + is included only for completeness: + + <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> + <property name="someProperty"><value>Custom string property value</value></property> +</bean> + +<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> + +<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> + <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> + <!-- Use inner bean, not local reference to target --> + <property name="target"> + <bean class="com.mycompany.PersonImpl"> + <property name="name"><value>Tony</value></property> + <property name="age"><value>51</value></property> + </bean> + </property> + <property name="interceptorNames"> + <list> + <value>myAdvisor</value> + <value>debugInterceptor</value> + </list> + </property> +</bean> + + This has the advantage that there's only one object of type + Person: useful if we want to prevent users of the + application context from obtaining a reference to the un-advised object, or + need to avoid any ambiguity with Spring IoC + autowiring. There's also arguably an advantage in + that the ProxyFactoryBean definition is self-contained. However, there + are times when being able to obtain the un-advised target from the + factory might actually be an advantage: for + example, in certain test scenarios. +
+ +
+ Proxying classes + + What if you need to proxy a class, rather than one or more + interfaces? + + Imagine that in our example above, there was no + Person interface: we needed to advise a class called + Person that didn't implement any business interface. + In this case, you can configure Spring to use CGLIB proxying, rather + than dynamic proxies. Simply set the proxyTargetClass + property on the ProxyFactoryBean above to true. While it's best to + program to interfaces, rather than classes, the ability to advise + classes that don't implement interfaces can be useful when working with + legacy code. (In general, Spring isn't prescriptive. While it makes it + easy to apply good practices, it avoids forcing a particular + approach.) + + If you want to, you can force the use of CGLIB in any case, even if + you do have interfaces. + + CGLIB proxying works by generating a subclass of the target class + at runtime. Spring configures this generated subclass to delegate method + calls to the original target: the subclass is used to implement the + Decorator pattern, weaving in the advice. + + CGLIB proxying should generally be transparent to users. However, + there are some issues to consider: + + + + Final methods can't be advised, as they + can't be overridden. + + + + You'll need the CGLIB 2 binaries on your classpath; dynamic + proxies are available with the JDK. + + + + There's little performance difference between CGLIB proxying and + dynamic proxies. As of Spring 1.0, dynamic proxies are slightly faster. + However, this may change in the future. Performance should not be a + decisive consideration in this case. +
+ +
+ Using 'global' advisors + + By appending an asterisk to an interceptor name, all advisors with + bean names matching the part before the asterisk, will be added to the + advisor chain. This can come in handy if you need to add a standard set + of 'global' advisors: +<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> + <property name="target" ref="service"/> + <property name="interceptorNames"> + <list> + <value>global*</value> + </list> + </property> +</bean> + +<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> +<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/> + +
+
+ +
+ Concise proxy definitions + + Especially when defining transactional proxies, you may end up with + many similar proxy definitions. The use of parent and child bean + definitions, along with inner bean definitions, can result in much cleaner + and more concise proxy definitions. + + First a parent, template, bean definition is + created for the proxy: + + <bean id="txProxyTemplate" abstract="true" + class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> + <property name="transactionManager" ref="transactionManager"/> + <property name="transactionAttributes"> + <props> + <prop key="*">PROPAGATION_REQUIRED</prop> + </props> + </property> +</bean> + + This will never be instantiated itself, so may actually be + incomplete. Then each proxy which needs to be created is just a child bean + definition, which wraps the target of the proxy as an inner bean + definition, since the target will never be used on its own + anyway.<bean id="myService" parent="txProxyTemplate"> + <property name="target"> + <bean class="org.springframework.samples.MyServiceImpl"> + </bean> + </property> +</bean> + + It is of course possible to override properties from the parent + template, such as in this case, the transaction propagation + settings:<bean id="mySpecialService" parent="txProxyTemplate"> + <property name="target"> + <bean class="org.springframework.samples.MySpecialServiceImpl"> + </bean> + </property> + <property name="transactionAttributes"> + <props> + <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> + <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> + <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop> + <prop key="store*">PROPAGATION_REQUIRED</prop> + </props> + </property> +</bean> + + Note that in the example above, we have explicitly marked the parent + bean definition as abstract by using the + abstract attribute, as described previously, so that it may + not actually ever be instantiated. Application contexts (but not simple + bean factories) will by default pre-instantiate all singletons. It is therefore + important (at least for singleton beans) that if you have a (parent) + bean definition which you intend to use only as a template, and this + definition specifies a class, you must make sure to set the + abstract attribute to true, + otherwise the application context will actually try to pre-instantiate + it. +
+ +
+ Creating AOP proxies programmatically with the ProxyFactory + + It's easy to create AOP proxies programmatically using Spring. This + enables you to use Spring AOP without dependency on Spring IoC. + + The following listing shows creation of a proxy for a target object, + with one interceptor and one advisor. The interfaces implemented by the + target object will automatically be proxied: + + ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); +factory.addInterceptor(myMethodInterceptor); +factory.addAdvisor(myAdvisor); +MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy(); + + The first step is to construct an object of type + org.springframework.aop.framework.ProxyFactory. You can + create this with a target object, as in the above example, or specify the + interfaces to be proxied in an alternate constructor. + + You can add interceptors or advisors, and manipulate them for the + life of the ProxyFactory. If you add an + IntroductionInterceptionAroundAdvisor you can cause the proxy to implement + additional interfaces. + + There are also convenience methods on ProxyFactory (inherited from + AdvisedSupport) which allow you to add other advice types + such as before and throws advice. AdvisedSupport is the superclass of both + ProxyFactory and ProxyFactoryBean. + + + Integrating AOP proxy creation with the IoC framework is best + practice in most applications. We recommend that you externalize + configuration from Java code with AOP, as in general. + +
+ +
+ Manipulating advised objects + + However you create AOP proxies, you can manipulate them using the + org.springframework.aop.framework.Advised interface. + Any AOP proxy can be cast to this interface, whichever other interfaces it + implements. This interface includes the following methods: + + Advisor[] getAdvisors(); + +void addAdvice(Advice advice) throws AopConfigException; + +void addAdvice(int pos, Advice advice) + throws AopConfigException; + +void addAdvisor(Advisor advisor) throws AopConfigException; + +void addAdvisor(int pos, Advisor advisor) throws AopConfigException; + +int indexOf(Advisor advisor); + +boolean removeAdvisor(Advisor advisor) throws AopConfigException; + +void removeAdvisor(int index) throws AopConfigException; + +boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; + +boolean isFrozen(); + + The getAdvisors() method will return an Advisor + for every advisor, interceptor or other advice type that has been added to + the factory. If you added an Advisor, the returned advisor at this index + will be the object that you added. If you added an interceptor or other + advice type, Spring will have wrapped this in an advisor with a pointcut + that always returns true. Thus if you added a + MethodInterceptor, the advisor returned for this index + will be an DefaultPointcutAdvisor returning your + MethodInterceptor and a pointcut that matches all + classes and methods. + + The addAdvisor() methods can be used to add any + Advisor. Usually the advisor holding pointcut and advice will be the + generic DefaultPointcutAdvisor, which can be used with + any advice or pointcut (but not for introductions). + + By default, it's possible to add or remove advisors or interceptors + even once a proxy has been created. The only restriction is that it's + impossible to add or remove an introduction advisor, as existing proxies + from the factory will not show the interface change. (You can obtain a new + proxy from the factory to avoid this problem.) + + A simple example of casting an AOP proxy to the + Advised interface and examining and manipulating its + advice: + + Advised advised = (Advised) myObject; +Advisor[] advisors = advised.getAdvisors(); +int oldAdvisorCount = advisors.length; +System.out.println(oldAdvisorCount + " advisors"); + +// Add an advice like an interceptor without a pointcut +// Will match all proxied methods +// Can use for interceptors, before, after returning or throws advice +advised.addAdvice(new DebugInterceptor()); + +// Add selective advice using a pointcut +advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); + +assertEquals("Added two advisors", + oldAdvisorCount + 2, advised.getAdvisors().length); + + + It's questionable whether it's advisable (no pun intended) to + modify advice on a business object in production, although there are no + doubt legitimate usage cases. However, it can be very useful in + development: for example, in tests. I have sometimes found it very useful + to be able to add test code in the form of an interceptor or other advice, + getting inside a method invocation I want to test. (For example, the + advice can get inside a transaction created for that method: for example, + to run SQL to check that a database was correctly updated, before marking + the transaction for roll back.) + + + Depending on how you created the proxy, you can usually set a + frozen flag, in which case the + Advised isFrozen() method will + return true, and any attempts to modify advice through addition or removal + will result in an AopConfigException. The ability to + freeze the state of an advised object is useful in some cases, for + example, to prevent calling code removing a security interceptor. It may + also be used in Spring 1.1 to allow aggressive optimization if runtime + advice modification is known not to be required. +
+ +
+ Using the "autoproxy" facility + + So far we've considered explicit creation of AOP proxies using a + ProxyFactoryBean or similar factory bean. + + Spring also allows us to use "autoproxy" bean definitions, which can + automatically proxy selected bean definitions. This is built on Spring + "bean post processor" infrastructure, which enables modification of any + bean definition as the container loads. + + In this model, you set up some special bean definitions in your XML + bean definition file to configure the auto proxy infrastructure. This + allows you just to declare the targets eligible for autoproxying: you + don't need to use ProxyFactoryBean. + + There are two ways to do this: + + + + Using an autoproxy creator that refers to specific beans in the + current context. + + + + A special case of autoproxy creation that deserves to be + considered separately; autoproxy creation driven by source-level + metadata attributes. + + + +
+ Autoproxy bean definitions + + The org.springframework.aop.framework.autoproxy + package provides the following standard autoproxy creators. + +
+ BeanNameAutoProxyCreator + + The BeanNameAutoProxyCreator class is a + BeanPostProcessor that automatically creates AOP proxies + for beans with names matching literal values or wildcards. + + <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> + <property name="beanNames"><value>jdk*,onlyJdk</value></property> + <property name="interceptorNames"> + <list> + <value>myInterceptor</value> + </list> + </property> +</bean> + + As with ProxyFactoryBean, there is an + interceptorNames property rather than a list of interceptors, to allow + correct behavior for prototype advisors. Named "interceptors" can be + advisors or any advice type. + + As with auto proxying in general, the main point of using + BeanNameAutoProxyCreator is to apply the same + configuration consistently to multiple objects, with minimal + volume of configuration. It is a popular choice for applying + declarative transactions to multiple objects. + + Bean definitions whose names match, such as "jdkMyBean" and + "onlyJdk" in the above example, are plain old bean definitions with + the target class. An AOP proxy will be created automatically by the + BeanNameAutoProxyCreator. The same advice will be + applied to all matching beans. Note that if advisors are used (rather + than the interceptor in the above example), the pointcuts may apply + differently to different beans. +
+ +
+ DefaultAdvisorAutoProxyCreator + + A more general and extremely powerful auto proxy creator is + DefaultAdvisorAutoProxyCreator. This will + automagically apply eligible advisors in the current context, without + the need to include specific bean names in the autoproxy advisor's + bean definition. It offers the same merit of consistent configuration + and avoidance of duplication as + BeanNameAutoProxyCreator. + + Using this mechanism involves: + + + + Specifying a + DefaultAdvisorAutoProxyCreator bean + definition. + + + + Specifying any number of Advisors in the same or related + contexts. Note that these must be Advisors, + not just interceptors or other advices. This is necessary because + there must be a pointcut to evaluate, to check the eligibility of + each advice to candidate bean definitions. + + + + The DefaultAdvisorAutoProxyCreator will + automatically evaluate the pointcut contained in each advisor, to see + what (if any) advice it should apply to each business object (such as + "businessObject1" and "businessObject2" in the example). + + This means that any number of advisors can be applied + automatically to each business object. If no pointcut in any of the + advisors matches any method in a business object, the object will not + be proxied. As bean definitions are added for new business objects, + they will automatically be proxied if necessary. + + Autoproxying in general has the advantage of making it + impossible for callers or dependencies to obtain an un-advised object. + Calling getBean("businessObject1") on this ApplicationContext will + return an AOP proxy, not the target business object. (The "inner bean" + idiom shown earlier also offers this benefit.) + + <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> + +<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> + <property name="transactionInterceptor" ref="transactionInterceptor"/> +</bean> + +<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/> + +<bean id="businessObject1" class="com.mycompany.BusinessObject1"> + <!-- Properties omitted --> +</bean> + +<bean id="businessObject2" class="com.mycompany.BusinessObject2"/> + + + The DefaultAdvisorAutoProxyCreator is very + useful if you want to apply the same advice consistently to many + business objects. Once the infrastructure definitions are in place, + you can simply add new business objects without including specific + proxy configuration. You can also drop in additional aspects very + easily - for example, tracing or performance monitoring aspects - with + minimal change to configuration. + + The DefaultAdvisorAutoProxyCreator offers support for filtering + (using a naming convention so that only certain advisors are + evaluated, allowing use of multiple, differently configured, + AdvisorAutoProxyCreators in the same factory) and ordering. Advisors + can implement the org.springframework.core.Ordered + interface to ensure correct ordering if this is an issue. The + TransactionAttributeSourceAdvisor used in the above example has a + configurable order value; the default setting is unordered. +
+ +
+ AbstractAdvisorAutoProxyCreator + + This is the superclass of DefaultAdvisorAutoProxyCreator. You + can create your own autoproxy creators by subclassing this class, in + the unlikely event that advisor definitions offer insufficient + customization to the behavior of the framework + DefaultAdvisorAutoProxyCreator. +
+
+ +
+ Using metadata-driven auto-proxying + + A particularly important type of autoproxying is driven by + metadata. This produces a similar programming model to .NET + ServicedComponents. Instead of using XML deployment + descriptors as in EJB, configuration for transaction management and + other enterprise services is held in source-level attributes. + + In this case, you use the + DefaultAdvisorAutoProxyCreator, in combination with + Advisors that understand metadata attributes. The metadata specifics are + held in the pointcut part of the candidate advisors, rather than in the + autoproxy creation class itself. + + This is really a special case of the + DefaultAdvisorAutoProxyCreator, but deserves + consideration on its own. (The metadata-aware code is in the pointcuts + contained in the advisors, not the AOP framework itself.) + + The /attributes directory of the JPetStore + sample application shows the use of attribute-driven autoproxying. In + this case, there's no need to use the + TransactionProxyFactoryBean. Simply defining + transactional attributes on business objects is sufficient, because of + the use of metadata-aware pointcuts. The bean definitions include the + following code, in /WEB-INF/declarativeServices.xml. + Note that this is generic, and can be used outside the JPetStore: + + <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> + +<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> + <property name="transactionInterceptor" ref="transactionInterceptor"/> +</bean> + +<bean id="transactionInterceptor" + class="org.springframework.transaction.interceptor.TransactionInterceptor"> + <property name="transactionManager" ref="transactionManager"/> + <property name="transactionAttributeSource"> + <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"> + <property name="attributes" ref="attributes"/> + </bean> + </property> +</bean> + +<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/> + + The DefaultAdvisorAutoProxyCreator bean + definition (the name is not significant, hence it can even be omitted) + will pick up all eligible pointcuts in the current application context. + In this case, the "transactionAdvisor" bean definition, of type + TransactionAttributeSourceAdvisor, will apply to + classes or methods carrying a transaction attribute. The + TransactionAttributeSourceAdvisor depends on a TransactionInterceptor, + via constructor dependency. The example resolves this via autowiring. + The AttributesTransactionAttributeSource depends on + an implementation of the + org.springframework.metadata.Attributes interface. In + this fragment, the "attributes" bean satisfies this, using the Jakarta + Commons Attributes API to obtain attribute information. (The application + code must have been compiled using the Commons Attributes compilation + task.) + + The /annotation directory of the JPetStore + sample application contains an analogous example for auto-proxying + driven by JDK 1.5+ annotations. The following configuration enables + automatic detection of Spring's Transactional + annotation, leading to implicit proxies for beans containing that + annotation: + + <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> + +<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> + <property name="transactionInterceptor" ref="transactionInterceptor"/> +</bean> + +<bean id="transactionInterceptor" + class="org.springframework.transaction.interceptor.TransactionInterceptor"> + <property name="transactionManager" ref="transactionManager"/> + <property name="transactionAttributeSource"> + <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> + </property> +</bean> + + The TransactionInterceptor defined here depends + on a PlatformTransactionManager definition, which is + not included in this generic file (although it could be) because it will + be specific to the application's transaction requirements (typically + JTA, as in this example, or Hibernate, JDO or JDBC): + + <bean id="transactionManager" + class="org.springframework.transaction.jta.JtaTransactionManager"/> + + + If you require only declarative transaction management, using + these generic XML definitions will result in Spring automatically + proxying all classes or methods with transaction attributes. You won't + need to work directly with AOP, and the programming model is similar to + that of .NET ServicedComponents. + + + This mechanism is extensible. It's possible to do autoproxying + based on custom attributes. You need to: + + + + Define your custom attribute. + + + + Specify an Advisor with the necessary advice, including a + pointcut that is triggered by the presence of the custom attribute + on a class or method. You may be able to use an existing advice, + merely implementing a static pointcut that picks up the custom + attribute. + + + + It's possible for such advisors to be unique to each advised class + (for example, mixins): they simply need to be defined as prototype, + rather than singleton, bean definitions. For example, the + LockMixin introduction interceptor from the Spring + test suite, shown above, could be used in conjunction with an + attribute-driven pointcut to target a mixin, as shown here. We use the + generic DefaultPointcutAdvisor, configured using + JavaBean properties: + + <bean id="lockMixin" class="org.springframework.aop.LockMixin" + scope="prototype"/> + +<bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" + scope="prototype"> + <property name="pointcut" ref="myAttributeAwarePointcut"/> + <property name="advice" ref="lockMixin"/> +</bean> + +<bean id="anyBean" class="anyclass" ... + + If the attribute aware pointcut matches any methods in the + anyBean or other bean definitions, the mixin will be + applied. Note that both lockMixin and + lockableAdvisor definitions are prototypes. The + myAttributeAwarePointcut pointcut can be a singleton + definition, as it doesn't hold state for individual advised + objects. +
+
+ +
+ Using TargetSources + + Spring offers the concept of a TargetSource, + expressed in the org.springframework.aop.TargetSource + interface. This interface is responsible for returning the "target object" + implementing the join point. The TargetSource + implementation is asked for a target instance each time the AOP proxy + handles a method invocation. + + Developers using Spring AOP don't normally need to work directly + with TargetSources, but this provides a powerful means of supporting + pooling, hot swappable and other sophisticated targets. For example, a + pooling TargetSource can return a different target instance for each + invocation, using a pool to manage instances. + + If you do not specify a TargetSource, a default implementation is + used that wraps a local object. The same target is returned for each + invocation (as you would expect). + + Let's look at the standard target sources provided with Spring, and + how you can use them. + + + When using a custom target source, your target will usually need + to be a prototype rather than a singleton bean definition. This allows + Spring to create a new target instance when required. + + +
+ Hot swappable target sources + + The + org.springframework.aop.target.HotSwappableTargetSource + exists to allow the target of an AOP proxy to be switched while allowing + callers to keep their references to it. + + Changing the target source's target takes effect immediately. The + HotSwappableTargetSource is threadsafe. + + You can change the target via the swap() method + on HotSwappableTargetSource as follows: + + HotSwappableTargetSource swapper = + (HotSwappableTargetSource) beanFactory.getBean("swapper"); +Object oldTarget = swapper.swap(newTarget); + + The XML definitions required look as follows: + + <bean id="initialTarget" class="mycompany.OldTarget"/> + +<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource"> + <constructor-arg ref="initialTarget"/> +</bean> + +<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean"> + <property name="targetSource" ref="swapper"/> +</bean> + + The above swap() call changes the target of the + swappable bean. Clients who hold a reference to that bean will be + unaware of the change, but will immediately start hitting the new + target. + + Although this example doesn't add any advice - and it's not + necessary to add advice to use a TargetSource - of + course any TargetSource can be used in conjunction + with arbitrary advice. +
+ +
+ Pooling target sources + + Using a pooling target source provides a similar programming model + to stateless session EJBs, in which a pool of identical instances is + maintained, with method invocations going to free objects in the + pool. + + A crucial difference between Spring pooling and SLSB pooling is + that Spring pooling can be applied to any POJO. As with Spring in + general, this service can be applied in a non-invasive way. + + Spring provides out-of-the-box support for Jakarta Commons Pool + 1.3, which provides a fairly efficient pooling implementation. You'll + need the commons-pool Jar on your application's classpath to use this + feature. It's also possible to subclass + org.springframework.aop.target.AbstractPoolingTargetSource + to support any other pooling API. + + Sample configuration is shown below: + + <bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" + scope="prototype"> + ... properties omitted +</bean> + +<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource"> + <property name="targetBeanName" value="businessObjectTarget"/> + <property name="maxSize" value="25"/> +</bean> + +<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"> + <property name="targetSource" ref="poolTargetSource"/> + <property name="interceptorNames" value="myInterceptor"/> +</bean> + + Note that the target object - "businessObjectTarget" in the + example - must be a prototype. This allows the + PoolingTargetSource implementation to create new + instances of the target to grow the pool as necessary. See the havadoc + for AbstractPoolingTargetSource and the concrete + subclass you wish to use for information about its properties: "maxSize" + is the most basic, and always guaranteed to be present. + + In this case, "myInterceptor" is the name of an interceptor that + would need to be defined in the same IoC context. However, it isn't + necessary to specify interceptors to use pooling. If you want only + pooling, and no other advice, don't set the interceptorNames property at + all. + + It's possible to configure Spring so as to be able to cast any + pooled object to the + org.springframework.aop.target.PoolingConfig + interface, which exposes information about the configuration and current + size of the pool through an introduction. You'll need to define an + advisor like this: + + <bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> + <property name="targetObject" ref="poolTargetSource"/> + <property name="targetMethod" value="getPoolingConfigMixin"/> +</bean> + + This advisor is obtained by calling a convenience method on the + AbstractPoolingTargetSource class, hence the use of + MethodInvokingFactoryBean. This advisor's name ("poolConfigAdvisor" + here) must be in the list of interceptors names in the ProxyFactoryBean + exposing the pooled object. + + The cast will look as follows: + + + + + Pooling stateless service objects is not usually necessary. We + don't believe it should be the default choice, as most stateless objects + are naturally thread safe, and instance pooling is problematic if + resources are cached. + + + Simpler pooling is available using autoproxying. It's possible to + set the TargetSources used by any autoproxy creator. +
+ +
+ Prototype target sources + + Setting up a "prototype" target source is similar to a pooling + TargetSource. In this case, a new instance of the target will be created + on every method invocation. Although the cost of creating a new object + isn't high in a modern JVM, the cost of wiring up the new object + (satisfying its IoC dependencies) may be more expensive. Thus you + shouldn't use this approach without very good reason. + + To do this, you could modify the + poolTargetSource definition shown above as follows. + (I've also changed the name, for clarity.) + + + +]]> + + There's only one property: the name of the target bean. + Inheritance is used in the TargetSource implementations to ensure + consistent naming. As with the pooling target source, the target bean + must be a prototype bean definition. +
+ +
+ <classname>ThreadLocal</classname> target sources + + ThreadLocal target sources are useful if you need an object to be + created for each incoming request (per thread that is). The concept of a + ThreadLocal provide a JDK-wide facility to + transparently store resource alongside a thread. Setting up a + ThreadLocalTargetSource is pretty much the same as was explained for the + other types of target source: + + + +]]> + + + ThreadLocals come with serious issues (potentially + resulting in memory leaks) when incorrectly using them in a + multi-threaded and multi-classloader environments. One should always + consider wrapping a threadlocal in some other class and never directly + use the ThreadLocal itself (except of course in the wrapper class). + Also, one should always remember to correctly set and unset (where the + latter simply involved a call to ThreadLocal.set(null)) the resource + local to the thread. Unsetting should be done in any case since not + unsetting it might result in problematic behavior. Spring's ThreadLocal + support does this for you and should always be considered in favor + of using ThreadLocals without other proper handling + code. + +
+
+ +
+ Defining new <interfacename>Advice</interfacename> types + + Spring AOP is designed to be extensible. While the interception + implementation strategy is presently used internally, it is possible to + support arbitrary advice types in addition to the out-of-the-box interception around advice, + before, throws advice and after returning advice. + + The org.springframework.aop.framework.adapter + package is an SPI package allowing support for new custom advice types to + be added without changing the core framework. The only constraint on a + custom Advice type is that it must implement the + org.aopalliance.aop.Advice tag interface. + + Please refer to the + org.springframework.aop.framework.adapter package's + Javadocs for further information. +
+ +
+ Further resources + + Please refer to the Spring sample applications for further examples + of Spring AOP: + + + + The JPetStore's default configuration illustrates the use of the + TransactionProxyFactoryBean for declarative transaction + management. + + + + The /attributes directory of the JPetStore + illustrates the use of attribute-driven declarative transaction management. + + + +
+ +
diff --git a/spring-framework-reference/src/expressions.xml b/spring-framework-reference/src/expressions.xml index e31e9103f5..ce8a489a69 100644 --- a/spring-framework-reference/src/expressions.xml +++ b/spring-framework-reference/src/expressions.xml @@ -1,1016 +1,1016 @@ - - - - Spring Expression Language (SpEL) - -
- Introduction - - The Spring Expression Language (SpEL for short) is a powerful - expression language that supports querying and manipulating an object - graph at runtime. The language syntax is similar to Unified EL but offers - additional features, most notably method invocation and basic string - templating functionality. - - While there are several other Java expression languages available, - OGNL, MVEL, and JBoss EL, to name a few, the Spring Expression Language - was created to provide the Spring community with a single well supported - expression language that can be used across all the products in the Spring - portfolio. Its language features are driven by the requirements of the - projects in the Spring portfolio, including tooling requirements for code - completion support within the eclipse based SpringSource Tool Suite. That - said, SpEL is based on a technology agnostic API allowing other - expression language implementations to be integrated should the need - arise. - - While SpEL serves as the foundation for expression evaluation within - the Spring portfolio, it is not directly tied to Spring and can be used - independently. In order to be self contained, many of the examples in this - chapter use SpEL as if it were an independent expression language. This - requires creating a few bootstrapping infrastructure classes such as the - parser. Most Spring users will not need to deal with this infrastructure - and will instead only author expression strings for evaluation. An example - of this typical use is the integration of SpEL into creating XML or - annotated based bean definitions as shown in the section Expression support for defining bean - definitions. - - This chapter covers the features of the expression language, its - API, and its language syntax. In several places an Inventor and Inventor's - Society class are used as the target objects for expression evaluation. - These class declarations and the data used to populate them are listed at - the end of the chapter. -
- -
- Feature Overview - - The expression language supports the following functionality - - - - Literal expressions - - - - Boolean and relational operators - - - - Regular expressions - - - - Class expressions - - - - Accessing properties, arrays, lists, maps - - - - Method invocation - - - - Relational operators - - - - Assignment - - - - Calling constructors - - - - Bean references - - - - Array construction - - - - Inline lists - - - - Ternary operator - - - - Variables - - - - User defined functions - - - - Collection projection - - - - Collection selection - - - - Templated expressions - - -
- -
- Expression Evaluation using Spring's Expression Interface - - This section introduces the simple use of SpEL interfaces and its - expression language. The complete language reference can be found in the - section Language - Reference. - - The following code introduces the SpEL API to evaluate the literal - string expression 'Hello World'. - - ExpressionParser parser = new SpelExpressionParser(); -Expression exp = parser.parseExpression("'Hello World'"); -String message = (String) exp.getValue();The value of the - message variable is simply 'Hello World'. - - The SpEL classes and interfaces you are most likely to use are - located in the packages org.springframework.expression - and its sub packages and spel.support. - - The interface ExpressionParser is - responsible for parsing an expression string. In this example the - expression string is a string literal denoted by the surrounding single - quotes. The interface Expression is - responsible for evaluating the previously defined expression string. There - are two exceptions that can be thrown, - ParseException and - EvaluationException when calling - 'parser.parseExpression' and - 'exp.getValue' respectively. - - SpEL supports a wide range of features, such as calling methods, - accessing properties, and calling constructors. - - As an example of method invocation, we call the 'concat' method on - the string literal. - - ExpressionParser parser = new SpelExpressionParser(); -Expression exp = parser.parseExpression("'Hello World'.concat('!')"); -String message = (String) exp.getValue(); - - The value of message is now 'Hello World!'. - - As an example of calling a JavaBean property, the String property - 'Bytes' can be called as shown below. - - ExpressionParser parser = new SpelExpressionParser(); - -// invokes 'getBytes()' -Expression exp = parser.parseExpression("'Hello World'.bytes"); - -byte[] bytes = (byte[]) exp.getValue(); - - SpEL also supports nested properties using standard 'dot' notation, - i.e. prop1.prop2.prop3 and the setting of property values - - Public fields may also be accessed. - - ExpressionParser parser = new SpelExpressionParser(); - -// invokes 'getBytes().length' -Expression exp = parser.parseExpression("'Hello World'.bytes.length"); - -int length = (Integer) exp.getValue(); - - The String's constructor can be called instead of using a string - literal. - - ExpressionParser parser = new SpelExpressionParser(); -Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); -String message = exp.getValue(String.class); - - Note the use of the generic method public <T> T - getValue(Class<T> desiredResultType). Using this method - removes the need to cast the value of the expression to the desired result - type. An EvaluationException will be thrown if the - value cannot be cast to the type T or converted using - the registered type converter. - - The more common usage of SpEL is to provide an expression string that - is evaluated against a specific object instance (called the root object). - There are two options here and which to choose depends on whether the object - against which the expression is being evaluated will be changing with each - call to evaluate the expression. In the following example - we retrieve the name property from an instance of the - Inventor class. - - // Create and set a calendar -GregorianCalendar c = new GregorianCalendar(); -c.set(1856, 7, 9); - -// The constructor arguments are name, birthday, and nationality. -Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); - -ExpressionParser parser = new SpelExpressionParser(); -Expression exp = parser.parseExpression("name"); -EvaluationContext context = new StandardEvaluationContext(tesla); - -String name = (String) exp.getValue(context); -In the last - line, the value of the string variable 'name' will be set to "Nikola - Tesla". The class StandardEvaluationContext is where you can specify which - object the "name" property will be evaluated against. This is the mechanism - to use if the root object is unlikely to change, it can simply be set once - in the evaluation context. If the root object is likely to change - repeatedly, it can be supplied on each call to getValue, - as this next example shows: - - / Create and set a calendar -GregorianCalendar c = new GregorianCalendar(); -c.set(1856, 7, 9); - -// The constructor arguments are name, birthday, and nationality. -Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); - -ExpressionParser parser = new SpelExpressionParser(); -Expression exp = parser.parseExpression("name"); - -String name = (String) exp.getValue(tesla); - In this case the inventor tesla has been - supplied directly to getValue and the expression - evaluation infrastructure creates and manages a default evaluation context - internally - it did not require one to be supplied. - - The StandardEvaluationContext is relatively expensive to construct and - during repeated usage it builds up cached state that enables subsequent - expression evaluations to be performed more quickly. For this reason it is - better to cache and reuse them where possible, rather than construct a new - one for each expression evaluation. - - In some cases it can be desirable to use a configured evaluation context and - yet still supply a different root object on each call to getValue. - getValue allows both to be specified on the same call. - In these situations the root object passed on the call is considered to override - any (which maybe null) specified on the evaluation context. - - - - In standalone usage of SpEL there is a need to create the parser, - parse expressions and perhaps provide evaluation contexts and a root - context object. However, more common usage - is to provide only the SpEL expression string as part of a - configuration file, for example for Spring bean or Spring Web Flow - definitions. In this case, the parser, evaluation context, root object - and any predefined variables are all set up implicitly, requiring - the user to specify nothing other than the expressions. - - As a final introductory example, the use of a boolean operator is - shown using the Inventor object in the previous example. - - Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); -boolean result = exp.getValue(context, Boolean.class); // evaluates to true - -
- The EvaluationContext interface - - The interface EvaluationContext is - used when evaluating an expression to resolve properties, methods, - fields, and to help perform type conversion. The out-of-the-box - implementation, StandardEvaluationContext, uses - reflection to manipulate the object, caching - java.lang.reflect's Method, - Field, and Constructor - instances for increased performance. - - The StandardEvaluationContext is where you - may specify the root object to evaluate against via the method - setRootObject() or passing the root object into - the constructor. You can also specify variables and functions that - will be used in the expression using the methods - setVariable() and - registerFunction(). The use of variables and - functions are described in the language reference sections Variables and Functions. The - StandardEvaluationContext is also where you can - register custom ConstructorResolvers, - MethodResolvers, and - PropertyAccessors to extend how SpEL evaluates - expressions. Please refer to the JavaDoc of these classes for more - details. - -
- Type Conversion - - By default SpEL uses the conversion service available in Spring - core - (org.springframework.core.convert.ConversionService). - This conversion service comes with many converters built in for common - conversions but is also fully extensible so custom conversions between - types can be added. Additionally it has the key capability that it is - generics aware. This means that when working with generic types in - expressions, SpEL will attempt conversions to maintain type - correctness for any objects it encounters. - - What does this mean in practice? Suppose assignment, using - setValue(), is being used to set a - List property. The type of the property is actually - List<Boolean>. SpEL will recognize that the - elements of the list need to be converted to - Boolean before being placed in it. A simple - example: - - class Simple { - public List<Boolean> booleanList = new ArrayList<Boolean>(); -} - -Simple simple = new Simple(); - -simple.booleanList.add(true); - -StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); - -// false is passed in here as a string. SpEL and the conversion service will -// correctly recognize that it needs to be a Boolean and convert it -parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); - -// b will be false -Boolean b = simple.booleanList.get(0); - -
-
-
- -
- Expression support for defining bean definitions - - SpEL expressions can be used with XML or annotation based - configuration metadata for defining BeanDefinitions. In both cases the - syntax to define the expression is of the form #{ <expression - string> }. - -
- XML based configuration - - A property or constructor-arg value can be set using expressions - as shown below - - <bean id="numberGuess" class="org.spring.samples.NumberGuess"> - <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> - - <!-- other properties --> -</bean> - - The variable 'systemProperties' is predefined, so you can use it - in your expressions as shown below. Note that you do not have to prefix - the predefined variable with the '#' symbol in this context. - - <bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> - <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> - - <!-- other properties --> -</bean> - - You can also refer to other bean properties by name, for - example. - - <bean id="numberGuess" class="org.spring.samples.NumberGuess"> - <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> - - <!-- other properties --> -</bean> - - -<bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> - <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/> - - <!-- other properties --> -</bean> -
- -
- Annotation-based configuration - - The @Value annotation can be placed on fields, - methods and method/constructor parameters to specify a default - value. - - Here is an example to set the default value of a field - variable. - - public static class FieldValueTestBean - - @Value("#{ systemProperties['user.region'] }") - private String defaultLocale; - - public void setDefaultLocale(String defaultLocale) - { - this.defaultLocale = defaultLocale; - } - - public String getDefaultLocale() - { - return this.defaultLocale; - } - -} - - - - The equivalent but on a property setter method is shown - below. - - public static class PropertyValueTestBean - - private String defaultLocale; - - @Value("#{ systemProperties['user.region'] }") - public void setDefaultLocale(String defaultLocale) - { - this.defaultLocale = defaultLocale; - } - - public String getDefaultLocale() - { - return this.defaultLocale; - } - -} - - Autowired methods and constructors can also use the - @Value annotation. - - public class SimpleMovieLister { - - private MovieFinder movieFinder; - private String defaultLocale; - - @Autowired - public void configure(MovieFinder movieFinder, - @Value("#{ systemProperties['user.region'] }"} String defaultLocale) { - this.movieFinder = movieFinder; - this.defaultLocale = defaultLocale; - } - - // ... -} - - public class MovieRecommender { - - private String defaultLocale; - - private CustomerPreferenceDao customerPreferenceDao; - - @Autowired - public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, - @Value("#{systemProperties['user.country']}"} String defaultLocale) { - this.customerPreferenceDao = customerPreferenceDao; - this.defaultLocale = defaultLocale; - } - - // ... -} -
-
- -
- Language Reference - -
- Literal expressions - - The types of literal expressions supported are strings, dates, - numeric values (int, real, and hex), boolean and null. Strings are - delimited by single quotes. To put a single quote itself in a string use - two single quote characters. The following listing shows simple usage of - literals. Typically they would not be used in isolation like this, but - as part of a more complex expression, for example using a literal on one - side of a logical comparison operator. - - ExpressionParser parser = new SpelExpressionParser(); - -// evals to "Hello World" -String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); - -double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); - -// evals to 2147483647 -int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); - -boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); - -Object nullValue = parser.parseExpression("null").getValue(); - - - Numbers support the use of the negative sign, exponential - notation, and decimal points. By default real numbers are parsed using - Double.parseDouble(). -
- -
- Properties, Arrays, Lists, Maps, Indexers - - Navigating with property references is easy, just use a period to - indicate a nested property value. The instances of Inventor class, pupin - and tesla, were populated with data listed in the section Classes used in the - examples. To navigate "down" and get Tesla's year of birth and - Pupin's city of birth the following expressions are used. - - // evals to 1856 -int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); - - -String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); - - Case insensitivity is allowed for the first letter of property - names. The contents of arrays and lists are obtained using square - bracket notation. - - ExpressionParser parser = new SpelExpressionParser(); - -// Inventions Array -StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla); - -// evaluates to "Induction motor" -String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, - String.class); - - -// Members List -StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee); - -// evaluates to "Nikola Tesla" -String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class); - -// List and Array navigation -// evaluates to "Wireless communication" -String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext, - String.class); - - - The contents of maps are obtained by specifying the literal key - value within the brackets. In this case, because keys for the Officers - map are strings, we can specify string literals. - - // Officer's Dictionary - -Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, - Inventor.class); - -// evaluates to "Idvor" -String city = - parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, - String.class); - -// setting values -parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, - "Croatia"); - - -
-
- Inline lists - - Lists can be expressed directly in an expression using {} notation. - - - -// evaluates to a Java list containing the four numbers -List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); - -List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); - - {} by itself means an empty list. For performance reasons, if the - list is itself entirely composed of fixed literals then a constant list is created - to represent the expression, rather than building a new list on each evaluation. -
- -
- Array construction - - Arrays can be built using the familiar Java syntax, optionally - supplying an initializer to have the array populated at construction time. - - - int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); - -// Array with initializer -int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); - -// Multi dimensional array -int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context); - - It is not currently allowed to supply an initializer when constructing - a multi-dimensional array. -
- -
- Methods - - Methods are invoked using typical Java programming syntax. You may - also invoke methods on literals. Varargs are also supported. - - // string literal, evaluates to "bc" -String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class); - -// evaluates to true -boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, - Boolean.class); -
- -
- Operators - -
- Relational operators - - The relational operators; equal, not equal, less than, less than - or equal, greater than, and greater than or equal are supported using - standard operator notation. - - // evaluates to true -boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); - -// evaluates to false -boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); - -// evaluates to true -boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); - In addition to standard relational operators SpEL supports the - 'instanceof' and regular expression based 'matches' operator. - - // evaluates to false -boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); - -// evaluates to true -boolean trueValue = - parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); - -//evaluates to false -boolean falseValue = - parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); - - - Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids - problems where the symbols used have special meaning for the document type in which - the expression is embedded (eg. an XML document). The textual equivalents are shown - here: lt ('<'), gt ('>'), le ('<='), ge ('>='), - eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!'). - These are case insensitive. -
- -
- Logical operators - - The logical operators that are supported are and, or, and not. - Their use is demonstrated below. - - // -- AND -- - -// evaluates to false -boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); - -// evaluates to true -String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; -boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); - -// -- OR -- - -// evaluates to true -boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); - -// evaluates to true -String expression = "isMember('Nikola Tesla') or isMember('Albert Einstien')"; -boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); - -// -- NOT -- - -// evaluates to false -boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); - - -// -- AND and NOT -- -String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; -boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); -
- -
- Mathematical operators - - The addition operator can be used on numbers, strings and dates. - Subtraction can be used on numbers and dates. Multiplication and - division can be used only on numbers. Other mathematical operators - supported are modulus (%) and exponential power (^). Standard operator - precedence is enforced. These operators are demonstrated below. - - // Addition -int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 - -String testString = - parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' - -// Subtraction -int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 - -double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 - -// Multiplication -int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 - -double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 - -// Division -int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 - -double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 - -// Modulus -int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 - -int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 - -// Operator precedence -int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 - -
-
- -
- Assignment - - Setting of a property is done by using the assignment operator. - This would typically be done within a call to - setValue but can also be done inside a call to - getValue. - - Inventor inventor = new Inventor(); -StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor); - -parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2"); - -// alternatively - -String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, - String.class); - - - -
- -
- Types - - The special 'T' operator can be used to specify an instance of - java.lang.Class (the 'type'). Static methods are invoked using this - operator as well. The StandardEvaluationContext - uses a TypeLocator to find types and the - StandardTypeLocator (which can be replaced) is - built with an understanding of the java.lang package. This means T() - references to types within java.lang do not need to be fully qualified, - but all other type references must be. - - Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); - -Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); - -boolean trueValue = - parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") - .getValue(Boolean.class); - -
- -
- Constructors - - Constructors can be invoked using the new operator. The fully - qualified class name should be used for all but the primitive type and - String (where int, float, etc, can be used). - - Inventor einstein = - p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', - 'German')") - .getValue(Inventor.class); - -//create new inventor instance within add method of List -p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', - 'German'))") - .getValue(societyContext); - -
- -
- Variables - - Variables can be referenced in the expression using the syntax - #variableName. Variables are set using the method setVariable on the - StandardEvaluationContext. - - Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); -StandardEvaluationContext context = new StandardEvaluationContext(tesla); -context.setVariable("newName", "Mike Tesla"); - -parser.parseExpression("Name = #newName").getValue(context); - -System.out.println(tesla.getName()) // "Mike Tesla" - -
- The #this and #root variables - - The variable #this is always defined and refers to the current - evaluation object (against which unqualified references are resolved). - The variable #root is always defined and refers to the root - context object. Although #this may vary as components of an expression - are evaluated, #root always refers to the root. - - // create an array of integers -List<Integer> primes = new ArrayList<Integer>(); -primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); - -// create parser and set variable 'primes' as the array of integers -ExpressionParser parser = new SpelExpressionParser(); -StandardEvaluationContext context = new StandardEvaluationContext(); -context.setVariable("primes",primes); - -// all prime numbers > 10 from the list (using selection ?{...}) -// evaluates to [11, 13, 17] -List<Integer> primesGreaterThanTen = - (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context); - - -
- - -
- -
- Functions - - You can extend SpEL by registering user defined functions that can - be called within the expression string. The function is registered with - the StandardEvaluationContext using the - method. - - public void registerFunction(String name, Method m) - - A reference to a Java Method provides the implementation of the - function. For example, a utility method to reverse a string is shown - below. - - public abstract class StringUtils { - - public static String reverseString(String input) { - StringBuilder backwards = new StringBuilder(); - for (int i = 0; i < input.length(); i++) - backwards.append(input.charAt(input.length() - 1 - i)); - } - return backwards.toString(); - } -} - - This method is then registered with the evaluation context and can - be used within an expression string. - - ExpressionParser parser = new SpelExpressionParser(); -StandardEvaluationContext context = new StandardEvaluationContext(); - -context.registerFunction("reverseString", - StringUtils.class.getDeclaredMethod("reverseString", - new Class[] { String.class })); - -String helloWorldReversed = - parser.parseExpression("#reverseString('hello')").getValue(context, String.class); -
- -
- Bean references - If the evaluation context has been configured with a bean resolver it is possible to - lookup beans from an expression using the (@) symbol. - - ExpressionParser parser = new SpelExpressionParser(); -StandardEvaluationContext context = new StandardEvaluationContext(); -context.setBeanResolver(new MyBeanResolver()); - -// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation -Object bean = parser.parseExpression("@foo").getValue(context); -
- -
- Ternary Operator (If-Then-Else) - - You can use the ternary operator for performing if-then-else - conditional logic inside the expression. A minimal example is: - - String falseString = - parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class); - - In this case, the boolean false results in returning the string - value 'falseExp'. A more realistic example is shown below. - - parser.parseExpression("Name").setValue(societyContext, "IEEE"); -societyContext.setVariable("queryName", "Nikola Tesla"); - -expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + - "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; - -String queryResultString = - parser.parseExpression(expression).getValue(societyContext, String.class); -// queryResultString = "Nikola Tesla is a member of the IEEE Society" - - Also see the next section on the Elvis operator for an even - shorter syntax for the ternary operator. -
- -
- The Elvis Operator - - The Elvis operator is a shortening of the ternary operator syntax - and is used in the Groovy - language. With the ternary operator syntax you usually have to repeat a - variable twice, for example: - - String name = "Elvis Presley"; -String displayName = name != null ? name : "Unknown"; - - Instead you can use the Elvis operator, named for the resemblance - to Elvis' hair style. - - ExpressionParser parser = new SpelExpressionParser(); - -String name = parser.parseExpression("null?:'Unknown'").getValue(String.class); - -System.out.println(name); // 'Unknown' - - - - Here is a more complex example. - - ExpressionParser parser = new SpelExpressionParser(); - -Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); -StandardEvaluationContext context = new StandardEvaluationContext(tesla); - -String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); - -System.out.println(name); // Mike Tesla - -tesla.setName(null); - -name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); - -System.out.println(name); // Elvis Presley -
- -
- Safe Navigation operator - - The Safe Navigation operator is used to avoid a - NullPointerException and comes from the Groovy - language. Typically when you have a reference to an object you might - need to verify that it is not null before accessing methods or - properties of the object. To avoid this, the safe navigation operator - will simply return null instead of throwing an exception. - - ExpressionParser parser = new SpelExpressionParser(); - -Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); -tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); - -StandardEvaluationContext context = new StandardEvaluationContext(tesla); - -String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); -System.out.println(city); // Smiljan - -tesla.setPlaceOfBirth(null); - -city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); - -System.out.println(city); // null - does not throw NullPointerException!!! + + + + Spring Expression Language (SpEL) + +
+ Introduction + + The Spring Expression Language (SpEL for short) is a powerful + expression language that supports querying and manipulating an object + graph at runtime. The language syntax is similar to Unified EL but offers + additional features, most notably method invocation and basic string + templating functionality. + + While there are several other Java expression languages available, + OGNL, MVEL, and JBoss EL, to name a few, the Spring Expression Language + was created to provide the Spring community with a single well supported + expression language that can be used across all the products in the Spring + portfolio. Its language features are driven by the requirements of the + projects in the Spring portfolio, including tooling requirements for code + completion support within the eclipse based SpringSource Tool Suite. That + said, SpEL is based on a technology agnostic API allowing other + expression language implementations to be integrated should the need + arise. + + While SpEL serves as the foundation for expression evaluation within + the Spring portfolio, it is not directly tied to Spring and can be used + independently. In order to be self contained, many of the examples in this + chapter use SpEL as if it were an independent expression language. This + requires creating a few bootstrapping infrastructure classes such as the + parser. Most Spring users will not need to deal with this infrastructure + and will instead only author expression strings for evaluation. An example + of this typical use is the integration of SpEL into creating XML or + annotated based bean definitions as shown in the section Expression support for defining bean + definitions. + + This chapter covers the features of the expression language, its + API, and its language syntax. In several places an Inventor and Inventor's + Society class are used as the target objects for expression evaluation. + These class declarations and the data used to populate them are listed at + the end of the chapter. +
+ +
+ Feature Overview + + The expression language supports the following functionality + + + + Literal expressions + + + + Boolean and relational operators + + + + Regular expressions + + + + Class expressions + + + + Accessing properties, arrays, lists, maps + + + + Method invocation + + + + Relational operators + + + + Assignment + + + + Calling constructors + + + + Bean references + + + + Array construction + + + + Inline lists + + + + Ternary operator + + + + Variables + + + + User defined functions + + + + Collection projection + + + + Collection selection + + + + Templated expressions + + +
+ +
+ Expression Evaluation using Spring's Expression Interface + + This section introduces the simple use of SpEL interfaces and its + expression language. The complete language reference can be found in the + section Language + Reference. + + The following code introduces the SpEL API to evaluate the literal + string expression 'Hello World'. + + ExpressionParser parser = new SpelExpressionParser(); +Expression exp = parser.parseExpression("'Hello World'"); +String message = (String) exp.getValue();The value of the + message variable is simply 'Hello World'. + + The SpEL classes and interfaces you are most likely to use are + located in the packages org.springframework.expression + and its sub packages and spel.support. + + The interface ExpressionParser is + responsible for parsing an expression string. In this example the + expression string is a string literal denoted by the surrounding single + quotes. The interface Expression is + responsible for evaluating the previously defined expression string. There + are two exceptions that can be thrown, + ParseException and + EvaluationException when calling + 'parser.parseExpression' and + 'exp.getValue' respectively. + + SpEL supports a wide range of features, such as calling methods, + accessing properties, and calling constructors. + + As an example of method invocation, we call the 'concat' method on + the string literal. + + ExpressionParser parser = new SpelExpressionParser(); +Expression exp = parser.parseExpression("'Hello World'.concat('!')"); +String message = (String) exp.getValue(); + + The value of message is now 'Hello World!'. + + As an example of calling a JavaBean property, the String property + 'Bytes' can be called as shown below. + + ExpressionParser parser = new SpelExpressionParser(); + +// invokes 'getBytes()' +Expression exp = parser.parseExpression("'Hello World'.bytes"); + +byte[] bytes = (byte[]) exp.getValue(); + + SpEL also supports nested properties using standard 'dot' notation, + i.e. prop1.prop2.prop3 and the setting of property values + + Public fields may also be accessed. + + ExpressionParser parser = new SpelExpressionParser(); + +// invokes 'getBytes().length' +Expression exp = parser.parseExpression("'Hello World'.bytes.length"); + +int length = (Integer) exp.getValue(); + + The String's constructor can be called instead of using a string + literal. + + ExpressionParser parser = new SpelExpressionParser(); +Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); +String message = exp.getValue(String.class); + + Note the use of the generic method public <T> T + getValue(Class<T> desiredResultType). Using this method + removes the need to cast the value of the expression to the desired result + type. An EvaluationException will be thrown if the + value cannot be cast to the type T or converted using + the registered type converter. + + The more common usage of SpEL is to provide an expression string that + is evaluated against a specific object instance (called the root object). + There are two options here and which to choose depends on whether the object + against which the expression is being evaluated will be changing with each + call to evaluate the expression. In the following example + we retrieve the name property from an instance of the + Inventor class. + + // Create and set a calendar +GregorianCalendar c = new GregorianCalendar(); +c.set(1856, 7, 9); + +// The constructor arguments are name, birthday, and nationality. +Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); + +ExpressionParser parser = new SpelExpressionParser(); +Expression exp = parser.parseExpression("name"); +EvaluationContext context = new StandardEvaluationContext(tesla); + +String name = (String) exp.getValue(context); +In the last + line, the value of the string variable 'name' will be set to "Nikola + Tesla". The class StandardEvaluationContext is where you can specify which + object the "name" property will be evaluated against. This is the mechanism + to use if the root object is unlikely to change, it can simply be set once + in the evaluation context. If the root object is likely to change + repeatedly, it can be supplied on each call to getValue, + as this next example shows: + + / Create and set a calendar +GregorianCalendar c = new GregorianCalendar(); +c.set(1856, 7, 9); + +// The constructor arguments are name, birthday, and nationality. +Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian"); + +ExpressionParser parser = new SpelExpressionParser(); +Expression exp = parser.parseExpression("name"); + +String name = (String) exp.getValue(tesla); + In this case the inventor tesla has been + supplied directly to getValue and the expression + evaluation infrastructure creates and manages a default evaluation context + internally - it did not require one to be supplied. + + The StandardEvaluationContext is relatively expensive to construct and + during repeated usage it builds up cached state that enables subsequent + expression evaluations to be performed more quickly. For this reason it is + better to cache and reuse them where possible, rather than construct a new + one for each expression evaluation. + + In some cases it can be desirable to use a configured evaluation context and + yet still supply a different root object on each call to getValue. + getValue allows both to be specified on the same call. + In these situations the root object passed on the call is considered to override + any (which maybe null) specified on the evaluation context. + + + + In standalone usage of SpEL there is a need to create the parser, + parse expressions and perhaps provide evaluation contexts and a root + context object. However, more common usage + is to provide only the SpEL expression string as part of a + configuration file, for example for Spring bean or Spring Web Flow + definitions. In this case, the parser, evaluation context, root object + and any predefined variables are all set up implicitly, requiring + the user to specify nothing other than the expressions. + + As a final introductory example, the use of a boolean operator is + shown using the Inventor object in the previous example. + + Expression exp = parser.parseExpression("name == 'Nikola Tesla'"); +boolean result = exp.getValue(context, Boolean.class); // evaluates to true + +
+ The EvaluationContext interface + + The interface EvaluationContext is + used when evaluating an expression to resolve properties, methods, + fields, and to help perform type conversion. The out-of-the-box + implementation, StandardEvaluationContext, uses + reflection to manipulate the object, caching + java.lang.reflect's Method, + Field, and Constructor + instances for increased performance. + + The StandardEvaluationContext is where you + may specify the root object to evaluate against via the method + setRootObject() or passing the root object into + the constructor. You can also specify variables and functions that + will be used in the expression using the methods + setVariable() and + registerFunction(). The use of variables and + functions are described in the language reference sections Variables and Functions. The + StandardEvaluationContext is also where you can + register custom ConstructorResolvers, + MethodResolvers, and + PropertyAccessors to extend how SpEL evaluates + expressions. Please refer to the JavaDoc of these classes for more + details. + +
+ Type Conversion + + By default SpEL uses the conversion service available in Spring + core + (org.springframework.core.convert.ConversionService). + This conversion service comes with many converters built in for common + conversions but is also fully extensible so custom conversions between + types can be added. Additionally it has the key capability that it is + generics aware. This means that when working with generic types in + expressions, SpEL will attempt conversions to maintain type + correctness for any objects it encounters. + + What does this mean in practice? Suppose assignment, using + setValue(), is being used to set a + List property. The type of the property is actually + List<Boolean>. SpEL will recognize that the + elements of the list need to be converted to + Boolean before being placed in it. A simple + example: + + class Simple { + public List<Boolean> booleanList = new ArrayList<Boolean>(); +} + +Simple simple = new Simple(); + +simple.booleanList.add(true); + +StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple); + +// false is passed in here as a string. SpEL and the conversion service will +// correctly recognize that it needs to be a Boolean and convert it +parser.parseExpression("booleanList[0]").setValue(simpleContext, "false"); + +// b will be false +Boolean b = simple.booleanList.get(0); + +
+
+
+ +
+ Expression support for defining bean definitions + + SpEL expressions can be used with XML or annotation based + configuration metadata for defining BeanDefinitions. In both cases the + syntax to define the expression is of the form #{ <expression + string> }. + +
+ XML based configuration + + A property or constructor-arg value can be set using expressions + as shown below + + <bean id="numberGuess" class="org.spring.samples.NumberGuess"> + <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> + + <!-- other properties --> +</bean> + + The variable 'systemProperties' is predefined, so you can use it + in your expressions as shown below. Note that you do not have to prefix + the predefined variable with the '#' symbol in this context. + + <bean id="taxCalculator" class="org.spring.samples.TaxCalculator"> + <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/> + + <!-- other properties --> +</bean> + + You can also refer to other bean properties by name, for + example. + + <bean id="numberGuess" class="org.spring.samples.NumberGuess"> + <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/> + + <!-- other properties --> +</bean> + + +<bean id="shapeGuess" class="org.spring.samples.ShapeGuess"> + <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/> + + <!-- other properties --> +</bean> +
+ +
+ Annotation-based configuration + + The @Value annotation can be placed on fields, + methods and method/constructor parameters to specify a default + value. + + Here is an example to set the default value of a field + variable. + + public static class FieldValueTestBean + + @Value("#{ systemProperties['user.region'] }") + private String defaultLocale; + + public void setDefaultLocale(String defaultLocale) + { + this.defaultLocale = defaultLocale; + } + + public String getDefaultLocale() + { + return this.defaultLocale; + } + +} + + + + The equivalent but on a property setter method is shown + below. + + public static class PropertyValueTestBean + + private String defaultLocale; + + @Value("#{ systemProperties['user.region'] }") + public void setDefaultLocale(String defaultLocale) + { + this.defaultLocale = defaultLocale; + } + + public String getDefaultLocale() + { + return this.defaultLocale; + } + +} + + Autowired methods and constructors can also use the + @Value annotation. + + public class SimpleMovieLister { + + private MovieFinder movieFinder; + private String defaultLocale; + + @Autowired + public void configure(MovieFinder movieFinder, + @Value("#{ systemProperties['user.region'] }"} String defaultLocale) { + this.movieFinder = movieFinder; + this.defaultLocale = defaultLocale; + } + + // ... +} + + public class MovieRecommender { + + private String defaultLocale; + + private CustomerPreferenceDao customerPreferenceDao; + + @Autowired + public MovieRecommender(CustomerPreferenceDao customerPreferenceDao, + @Value("#{systemProperties['user.country']}"} String defaultLocale) { + this.customerPreferenceDao = customerPreferenceDao; + this.defaultLocale = defaultLocale; + } + + // ... +} +
+
+ +
+ Language Reference + +
+ Literal expressions + + The types of literal expressions supported are strings, dates, + numeric values (int, real, and hex), boolean and null. Strings are + delimited by single quotes. To put a single quote itself in a string use + two single quote characters. The following listing shows simple usage of + literals. Typically they would not be used in isolation like this, but + as part of a more complex expression, for example using a literal on one + side of a logical comparison operator. + + ExpressionParser parser = new SpelExpressionParser(); + +// evals to "Hello World" +String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); + +double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue(); + +// evals to 2147483647 +int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue(); + +boolean trueValue = (Boolean) parser.parseExpression("true").getValue(); + +Object nullValue = parser.parseExpression("null").getValue(); + + + Numbers support the use of the negative sign, exponential + notation, and decimal points. By default real numbers are parsed using + Double.parseDouble(). +
+ +
+ Properties, Arrays, Lists, Maps, Indexers + + Navigating with property references is easy, just use a period to + indicate a nested property value. The instances of Inventor class, pupin + and tesla, were populated with data listed in the section Classes used in the + examples. To navigate "down" and get Tesla's year of birth and + Pupin's city of birth the following expressions are used. + + // evals to 1856 +int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); + + +String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); + + Case insensitivity is allowed for the first letter of property + names. The contents of arrays and lists are obtained using square + bracket notation. + + ExpressionParser parser = new SpelExpressionParser(); + +// Inventions Array +StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla); + +// evaluates to "Induction motor" +String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, + String.class); + + +// Members List +StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee); + +// evaluates to "Nikola Tesla" +String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class); + +// List and Array navigation +// evaluates to "Wireless communication" +String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(societyContext, + String.class); + + + The contents of maps are obtained by specifying the literal key + value within the brackets. In this case, because keys for the Officers + map are strings, we can specify string literals. + + // Officer's Dictionary + +Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, + Inventor.class); + +// evaluates to "Idvor" +String city = + parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, + String.class); + +// setting values +parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, + "Croatia"); + + +
+
+ Inline lists + + Lists can be expressed directly in an expression using {} notation. + + + +// evaluates to a Java list containing the four numbers +List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); + +List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); + + {} by itself means an empty list. For performance reasons, if the + list is itself entirely composed of fixed literals then a constant list is created + to represent the expression, rather than building a new list on each evaluation. +
+ +
+ Array construction + + Arrays can be built using the familiar Java syntax, optionally + supplying an initializer to have the array populated at construction time. + + + int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); + +// Array with initializer +int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); + +// Multi dimensional array +int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context); + + It is not currently allowed to supply an initializer when constructing + a multi-dimensional array. +
+ +
+ Methods + + Methods are invoked using typical Java programming syntax. You may + also invoke methods on literals. Varargs are also supported. + + // string literal, evaluates to "bc" +String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class); + +// evaluates to true +boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, + Boolean.class); +
+ +
+ Operators + +
+ Relational operators + + The relational operators; equal, not equal, less than, less than + or equal, greater than, and greater than or equal are supported using + standard operator notation. + + // evaluates to true +boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class); + +// evaluates to false +boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class); + +// evaluates to true +boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); + In addition to standard relational operators SpEL supports the + 'instanceof' and regular expression based 'matches' operator. + + // evaluates to false +boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class); + +// evaluates to true +boolean trueValue = + parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + +//evaluates to false +boolean falseValue = + parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); + + + Each symbolic operator can also be specified as a purely alphabetic equivalent. This avoids + problems where the symbols used have special meaning for the document type in which + the expression is embedded (eg. an XML document). The textual equivalents are shown + here: lt ('<'), gt ('>'), le ('<='), ge ('>='), + eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!'). + These are case insensitive. +
+ +
+ Logical operators + + The logical operators that are supported are and, or, and not. + Their use is demonstrated below. + + // -- AND -- + +// evaluates to false +boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class); + +// evaluates to true +String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"; +boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); + +// -- OR -- + +// evaluates to true +boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class); + +// evaluates to true +String expression = "isMember('Nikola Tesla') or isMember('Albert Einstien')"; +boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); + +// -- NOT -- + +// evaluates to false +boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class); + + +// -- AND and NOT -- +String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"; +boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class); +
+ +
+ Mathematical operators + + The addition operator can be used on numbers, strings and dates. + Subtraction can be used on numbers and dates. Multiplication and + division can be used only on numbers. Other mathematical operators + supported are modulus (%) and exponential power (^). Standard operator + precedence is enforced. These operators are demonstrated below. + + // Addition +int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 + +String testString = + parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class); // 'test string' + +// Subtraction +int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 + +double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 + +// Multiplication +int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 + +double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 + +// Division +int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 + +double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 + +// Modulus +int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 + +int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 + +// Operator precedence +int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21 + +
+
+ +
+ Assignment + + Setting of a property is done by using the assignment operator. + This would typically be done within a call to + setValue but can also be done inside a call to + getValue. + + Inventor inventor = new Inventor(); +StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor); + +parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2"); + +// alternatively + +String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, + String.class); + + + +
+ +
+ Types + + The special 'T' operator can be used to specify an instance of + java.lang.Class (the 'type'). Static methods are invoked using this + operator as well. The StandardEvaluationContext + uses a TypeLocator to find types and the + StandardTypeLocator (which can be replaced) is + built with an understanding of the java.lang package. This means T() + references to types within java.lang do not need to be fully qualified, + but all other type references must be. + + Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class); + +Class stringClass = parser.parseExpression("T(String)").getValue(Class.class); + +boolean trueValue = + parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR") + .getValue(Boolean.class); + +
+ +
+ Constructors + + Constructors can be invoked using the new operator. The fully + qualified class name should be used for all but the primitive type and + String (where int, float, etc, can be used). + + Inventor einstein = + p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', + 'German')") + .getValue(Inventor.class); + +//create new inventor instance within add method of List +p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', + 'German'))") + .getValue(societyContext); + +
+ +
+ Variables + + Variables can be referenced in the expression using the syntax + #variableName. Variables are set using the method setVariable on the + StandardEvaluationContext. + + Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); +StandardEvaluationContext context = new StandardEvaluationContext(tesla); +context.setVariable("newName", "Mike Tesla"); + +parser.parseExpression("Name = #newName").getValue(context); + +System.out.println(tesla.getName()) // "Mike Tesla" + +
+ The #this and #root variables + + The variable #this is always defined and refers to the current + evaluation object (against which unqualified references are resolved). + The variable #root is always defined and refers to the root + context object. Although #this may vary as components of an expression + are evaluated, #root always refers to the root. + + // create an array of integers +List<Integer> primes = new ArrayList<Integer>(); +primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); + +// create parser and set variable 'primes' as the array of integers +ExpressionParser parser = new SpelExpressionParser(); +StandardEvaluationContext context = new StandardEvaluationContext(); +context.setVariable("primes",primes); + +// all prime numbers > 10 from the list (using selection ?{...}) +// evaluates to [11, 13, 17] +List<Integer> primesGreaterThanTen = + (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context); + + +
+ + +
+ +
+ Functions + + You can extend SpEL by registering user defined functions that can + be called within the expression string. The function is registered with + the StandardEvaluationContext using the + method. + + public void registerFunction(String name, Method m) + + A reference to a Java Method provides the implementation of the + function. For example, a utility method to reverse a string is shown + below. + + public abstract class StringUtils { + + public static String reverseString(String input) { + StringBuilder backwards = new StringBuilder(); + for (int i = 0; i < input.length(); i++) + backwards.append(input.charAt(input.length() - 1 - i)); + } + return backwards.toString(); + } +} + + This method is then registered with the evaluation context and can + be used within an expression string. + + ExpressionParser parser = new SpelExpressionParser(); +StandardEvaluationContext context = new StandardEvaluationContext(); + +context.registerFunction("reverseString", + StringUtils.class.getDeclaredMethod("reverseString", + new Class[] { String.class })); + +String helloWorldReversed = + parser.parseExpression("#reverseString('hello')").getValue(context, String.class); +
+ +
+ Bean references + If the evaluation context has been configured with a bean resolver it is possible to + lookup beans from an expression using the (@) symbol. + + ExpressionParser parser = new SpelExpressionParser(); +StandardEvaluationContext context = new StandardEvaluationContext(); +context.setBeanResolver(new MyBeanResolver()); + +// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation +Object bean = parser.parseExpression("@foo").getValue(context); +
+ +
+ Ternary Operator (If-Then-Else) + + You can use the ternary operator for performing if-then-else + conditional logic inside the expression. A minimal example is: + + String falseString = + parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class); + + In this case, the boolean false results in returning the string + value 'falseExp'. A more realistic example is shown below. + + parser.parseExpression("Name").setValue(societyContext, "IEEE"); +societyContext.setVariable("queryName", "Nikola Tesla"); + +expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"; + +String queryResultString = + parser.parseExpression(expression).getValue(societyContext, String.class); +// queryResultString = "Nikola Tesla is a member of the IEEE Society" + + Also see the next section on the Elvis operator for an even + shorter syntax for the ternary operator. +
+ +
+ The Elvis Operator + + The Elvis operator is a shortening of the ternary operator syntax + and is used in the Groovy + language. With the ternary operator syntax you usually have to repeat a + variable twice, for example: + + String name = "Elvis Presley"; +String displayName = name != null ? name : "Unknown"; + + Instead you can use the Elvis operator, named for the resemblance + to Elvis' hair style. + + ExpressionParser parser = new SpelExpressionParser(); + +String name = parser.parseExpression("null?:'Unknown'").getValue(String.class); + +System.out.println(name); // 'Unknown' + + + + Here is a more complex example. + + ExpressionParser parser = new SpelExpressionParser(); + +Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); +StandardEvaluationContext context = new StandardEvaluationContext(tesla); + +String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); + +System.out.println(name); // Mike Tesla + +tesla.setName(null); + +name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class); + +System.out.println(name); // Elvis Presley +
+ +
+ Safe Navigation operator + + The Safe Navigation operator is used to avoid a + NullPointerException and comes from the Groovy + language. Typically when you have a reference to an object you might + need to verify that it is not null before accessing methods or + properties of the object. To avoid this, the safe navigation operator + will simply return null instead of throwing an exception. + + ExpressionParser parser = new SpelExpressionParser(); + +Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); +tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); + +StandardEvaluationContext context = new StandardEvaluationContext(tesla); + +String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); +System.out.println(city); // Smiljan + +tesla.setPlaceOfBirth(null); + +city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class); + +System.out.println(city); // null - does not throw NullPointerException!!! The Elvis operator can be used to apply default values in expressions, e.g. in an @Value expression: @@ -1019,258 +1019,258 @@ System.out.println(city); // null - does not throw NullPointerException!!!This will inject a system property pop3.port if it is defined or 25 if not. - -
- -
- Collection Selection - - Selection is a powerful expression language feature that allows you - to transform some source collection into another by selecting from its - entries. - - Selection uses the syntax - ?[selectionExpression]. This will filter the - collection and return a new collection containing a subset of the - original elements. For example, selection would allow us to easily get a - list of Serbian inventors: - - List<Inventor> list = (List<Inventor>) - parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext); - - Selection is possible upon both lists and maps. In the former case - the selection criteria is evaluated against each individual list element - whilst against a map the selection criteria is evaluated against each - map entry (objects of the Java type Map.Entry). Map - entries have their key and value accessible as properties for use in the - selection. - - This expression will return a new map consisting of those elements - of the original map where the entry value is less than 27. - - Map newMap = parser.parseExpression("map.?[value<27]").getValue(); - - In addition to returning all the selected elements, it is possible - to retrieve just the first or the last value. To obtain the first entry - matching the selection the syntax is ^[...] whilst to - obtain the last matching selection the syntax is - $[...]. -
- -
- Collection Projection - - Projection allows a collection to drive the evaluation of a - sub-expression and the result is a new collection. The syntax for - projection is ![projectionExpression]. Most easily - understood by example, suppose we have a list of inventors but want the - list of cities where they were born. Effectively we want to evaluate - 'placeOfBirth.city' for every entry in the inventor list. Using - projection: - - // returns [ 'Smiljan', 'Idvor' ] -List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]"); - - A map can also be used to drive projection and in this case the - projection expression is evaluated against each entry in the map - (represented as a Java Map.Entry). The result of a - projection across a map is a list consisting of the evaluation of the - projection expression against each map entry. -
- -
- Expression templating - - Expression templates allow a mixing of literal text with one or - more evaluation blocks. Each evaluation block is delimited with prefix - and suffix characters that you can define, a common choice is to use - #{ } as the delimiters. For example, - - String randomPhrase = - parser.parseExpression("random number is #{T(java.lang.Math).random()}", - new TemplateParserContext()).getValue(String.class); - -// evaluates to "random number is 0.7038186818312008" - - The string is evaluated by concatenating the literal text 'random - number is ' with the result of evaluating the expression inside the #{ } - delimiter, in this case the result of calling that random() method. The - second argument to the method parseExpression() is of - the type ParserContext. The - ParserContext interface is used to - influence how the expression is parsed in order to support the - expression templating functionality. The definition of - TemplateParserContext is shown below. - - public class TemplateParserContext implements ParserContext { - - public String getExpressionPrefix() { - return "#{"; - } - - public String getExpressionSuffix() { - return "}"; - } - - public boolean isTemplate() { - return true; - } -} -
-
- -
- Classes used in the examples - - Inventor.java - - package org.spring.samples.spel.inventor; - -import java.util.Date; -import java.util.GregorianCalendar; - -public class Inventor { - - private String name; - private String nationality; - private String[] inventions; - private Date birthdate; - private PlaceOfBirth placeOfBirth; - - - public Inventor(String name, String nationality) - { - GregorianCalendar c= new GregorianCalendar(); - this.name = name; - this.nationality = nationality; - this.birthdate = c.getTime(); - } - public Inventor(String name, Date birthdate, String nationality) { - this.name = name; - this.nationality = nationality; - this.birthdate = birthdate; - } - - public Inventor() { - } - - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } - public String getNationality() { - return nationality; - } - public void setNationality(String nationality) { - this.nationality = nationality; - } - public Date getBirthdate() { - return birthdate; - } - public void setBirthdate(Date birthdate) { - this.birthdate = birthdate; - } - public PlaceOfBirth getPlaceOfBirth() { - return placeOfBirth; - } - public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) { - this.placeOfBirth = placeOfBirth; - } - public void setInventions(String[] inventions) { - this.inventions = inventions; - } - public String[] getInventions() { - return inventions; - } -} - - - PlaceOfBirth.java - - package org.spring.samples.spel.inventor; - -public class PlaceOfBirth { - - private String city; - private String country; - - public PlaceOfBirth(String city) { - this.city=city; - } - public PlaceOfBirth(String city, String country) - { - this(city); - this.country = country; - } - - - public String getCity() { - return city; - } - public void setCity(String s) { - this.city = s; - } - public String getCountry() { - return country; - } - public void setCountry(String country) { - this.country = country; - } - - - -} - - - Society.java - - package org.spring.samples.spel.inventor; - -import java.util.*; - -public class Society { - - private String name; - - public static String Advisors = "advisors"; - public static String President = "president"; - - private List<Inventor> members = new ArrayList<Inventor>(); - private Map officers = new HashMap(); - - public List getMembers() { - return members; - } - - public Map getOfficers() { - return officers; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean isMember(String name) - { - boolean found = false; - for (Inventor inventor : members) { - if (inventor.getName().equals(name)) - { - found = true; - break; - } - } - return found; - } - - -} - -
-
+ +
+ +
+ Collection Selection + + Selection is a powerful expression language feature that allows you + to transform some source collection into another by selecting from its + entries. + + Selection uses the syntax + ?[selectionExpression]. This will filter the + collection and return a new collection containing a subset of the + original elements. For example, selection would allow us to easily get a + list of Serbian inventors: + + List<Inventor> list = (List<Inventor>) + parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext); + + Selection is possible upon both lists and maps. In the former case + the selection criteria is evaluated against each individual list element + whilst against a map the selection criteria is evaluated against each + map entry (objects of the Java type Map.Entry). Map + entries have their key and value accessible as properties for use in the + selection. + + This expression will return a new map consisting of those elements + of the original map where the entry value is less than 27. + + Map newMap = parser.parseExpression("map.?[value<27]").getValue(); + + In addition to returning all the selected elements, it is possible + to retrieve just the first or the last value. To obtain the first entry + matching the selection the syntax is ^[...] whilst to + obtain the last matching selection the syntax is + $[...]. +
+ +
+ Collection Projection + + Projection allows a collection to drive the evaluation of a + sub-expression and the result is a new collection. The syntax for + projection is ![projectionExpression]. Most easily + understood by example, suppose we have a list of inventors but want the + list of cities where they were born. Effectively we want to evaluate + 'placeOfBirth.city' for every entry in the inventor list. Using + projection: + + // returns [ 'Smiljan', 'Idvor' ] +List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]"); + + A map can also be used to drive projection and in this case the + projection expression is evaluated against each entry in the map + (represented as a Java Map.Entry). The result of a + projection across a map is a list consisting of the evaluation of the + projection expression against each map entry. +
+ +
+ Expression templating + + Expression templates allow a mixing of literal text with one or + more evaluation blocks. Each evaluation block is delimited with prefix + and suffix characters that you can define, a common choice is to use + #{ } as the delimiters. For example, + + String randomPhrase = + parser.parseExpression("random number is #{T(java.lang.Math).random()}", + new TemplateParserContext()).getValue(String.class); + +// evaluates to "random number is 0.7038186818312008" + + The string is evaluated by concatenating the literal text 'random + number is ' with the result of evaluating the expression inside the #{ } + delimiter, in this case the result of calling that random() method. The + second argument to the method parseExpression() is of + the type ParserContext. The + ParserContext interface is used to + influence how the expression is parsed in order to support the + expression templating functionality. The definition of + TemplateParserContext is shown below. + + public class TemplateParserContext implements ParserContext { + + public String getExpressionPrefix() { + return "#{"; + } + + public String getExpressionSuffix() { + return "}"; + } + + public boolean isTemplate() { + return true; + } +} +
+
+ +
+ Classes used in the examples + + Inventor.java + + package org.spring.samples.spel.inventor; + +import java.util.Date; +import java.util.GregorianCalendar; + +public class Inventor { + + private String name; + private String nationality; + private String[] inventions; + private Date birthdate; + private PlaceOfBirth placeOfBirth; + + + public Inventor(String name, String nationality) + { + GregorianCalendar c= new GregorianCalendar(); + this.name = name; + this.nationality = nationality; + this.birthdate = c.getTime(); + } + public Inventor(String name, Date birthdate, String nationality) { + this.name = name; + this.nationality = nationality; + this.birthdate = birthdate; + } + + public Inventor() { + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public String getNationality() { + return nationality; + } + public void setNationality(String nationality) { + this.nationality = nationality; + } + public Date getBirthdate() { + return birthdate; + } + public void setBirthdate(Date birthdate) { + this.birthdate = birthdate; + } + public PlaceOfBirth getPlaceOfBirth() { + return placeOfBirth; + } + public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) { + this.placeOfBirth = placeOfBirth; + } + public void setInventions(String[] inventions) { + this.inventions = inventions; + } + public String[] getInventions() { + return inventions; + } +} + + + PlaceOfBirth.java + + package org.spring.samples.spel.inventor; + +public class PlaceOfBirth { + + private String city; + private String country; + + public PlaceOfBirth(String city) { + this.city=city; + } + public PlaceOfBirth(String city, String country) + { + this(city); + this.country = country; + } + + + public String getCity() { + return city; + } + public void setCity(String s) { + this.city = s; + } + public String getCountry() { + return country; + } + public void setCountry(String country) { + this.country = country; + } + + + +} + + + Society.java + + package org.spring.samples.spel.inventor; + +import java.util.*; + +public class Society { + + private String name; + + public static String Advisors = "advisors"; + public static String President = "president"; + + private List<Inventor> members = new ArrayList<Inventor>(); + private Map officers = new HashMap(); + + public List getMembers() { + return members; + } + + public Map getOfficers() { + return officers; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isMember(String name) + { + boolean found = false; + for (Inventor inventor : members) { + if (inventor.getName().equals(name)) + { + found = true; + break; + } + } + return found; + } + + +} + +
+