diff --git a/org.springframework.aop/ivy.xml b/org.springframework.aop/ivy.xml index d6418d085a..3252b1bd7c 100644 --- a/org.springframework.aop/ivy.xml +++ b/org.springframework.aop/ivy.xml @@ -21,7 +21,14 @@ - + + + + + + + + diff --git a/org.springframework.aop/pom.xml b/org.springframework.aop/pom.xml index 1de1d0d139..51506dc3ca 100644 --- a/org.springframework.aop/pom.xml +++ b/org.springframework.aop/pom.xml @@ -6,11 +6,41 @@ jar Spring Core Abstractions and Utilities 3.0.0.M1 + + + com.springsource.repository.bundles.external + SpringSource Enterprise Bundle Repository - External Bundle Releases + http://repository.springsource.com/maven/bundles/external + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + commons-logging commons-logging 1.1.1 + + org.aopalliance + com.springsource.org.aopalliance + 1.0.0 + + + org.apache.commons + com.springsource.org.apache.commons.pool + 1.4.0 + true + \ No newline at end of file diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/Advisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/Advisor.java new file mode 100644 index 0000000000..21c9c8301b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/Advisor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2007 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.aop; + +import org.aopalliance.aop.Advice; + +/** + * Base interface holding AOP advice (action to take at a joinpoint) + * and a filter determining the applicability of the advice (such as + * a pointcut). This interface is not for use by Spring users, but to + * allow for commonality in support for different types of advice. + * + *

Spring AOP is based around around advice delivered via method + * interception, compliant with the AOP Alliance interception API. + * The Advisor interface allows support for different types of advice, + * such as before and after advice, which need not be + * implemented using interception. + * + * @author Rod Johnson + */ +public interface Advisor { + + /** + * Return the advice part of this aspect. An advice may be an + * interceptor, a before advice, a throws advice, etc. + * @return the advice that should apply if the pointcut matches + * @see org.aopalliance.intercept.MethodInterceptor + * @see BeforeAdvice + * @see ThrowsAdvice + * @see AfterReturningAdvice + */ + Advice getAdvice(); + + /** + * Return whether this advice is associated with a particular instance + * (for example, creating a mixin) or shared with all instances of + * the advised class obtained from the same Spring bean factory. + *

Note that this method is not currently used by the framework. + * Typical Advisor implementations always return true. + * Use singleton/prototype bean definitions or appropriate programmatic + * proxy creation to ensure that Advisors have the correct lifecycle model. + * @return whether this advice is associated with a particular target instance + */ + boolean isPerInstance(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/AfterAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/AfterAdvice.java new file mode 100644 index 0000000000..e807cbdfed --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/AfterAdvice.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 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.aop; + +import org.aopalliance.aop.Advice; + +/** + * Common marker interface for after advice, + * such as {@link AfterReturningAdvice} and {@link ThrowsAdvice}. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see BeforeAdvice + */ +public interface AfterAdvice extends Advice { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java new file mode 100644 index 0000000000..ac4b9ba227 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/AfterReturningAdvice.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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.aop; + +import java.lang.reflect.Method; + +/** + * After returning advice is invoked only on normal method return, not if an + * exception is thrown. Such advice can see the return value, but cannot change it. + * + * @author Rod Johnson + * @see MethodBeforeAdvice + * @see ThrowsAdvice + */ +public interface AfterReturningAdvice extends AfterAdvice { + + /** + * Callback after a given method successfully returned. + * @param returnValue the value returned by the method, if any + * @param method method being invoked + * @param args arguments to the method + * @param target target of the method invocation. May be null. + * @throws Throwable if this object wishes to abort the call. + * Any exception thrown will be returned to the caller if it's + * allowed by the method signature. Otherwise the exception + * will be wrapped as a runtime exception. + */ + void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/AopInvocationException.java b/org.springframework.aop/src/main/java/org/springframework/aop/AopInvocationException.java new file mode 100644 index 0000000000..fa23500793 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/AopInvocationException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2006 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.aop; + +import org.springframework.core.NestedRuntimeException; + +/** + * Exception that gets thrown when an AOP invocation failed + * because of misconfiguration or unexpected runtime issues. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public class AopInvocationException extends NestedRuntimeException { + + /** + * Constructor for AopInvocationException. + * @param msg the detail message + */ + public AopInvocationException(String msg) { + super(msg); + } + + /** + * Constructor for AopInvocationException. + * @param msg the detail message + * @param cause the root cause + */ + public AopInvocationException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/BeforeAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/BeforeAdvice.java new file mode 100644 index 0000000000..6d8251e68a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/BeforeAdvice.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2007 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.aop; + +import org.aopalliance.aop.Advice; + +/** + * Common marker interface for before advice, such as {@link MethodBeforeAdvice}. + * + *

Spring supports only method before advice. Although this is unlikely to change, + * this API is designed to allow field advice in future if desired. + * + * @author Rod Johnson + * @see AfterAdvice + */ +public interface BeforeAdvice extends Advice { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/ClassFilter.java b/org.springframework.aop/src/main/java/org/springframework/aop/ClassFilter.java new file mode 100644 index 0000000000..bc3051fce4 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/ClassFilter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Filter that restricts matching of a pointcut or introduction to + * a given set of target classes. + * + *

Can be used as part of a {@link Pointcut} or for the entire + * targeting of an {@link IntroductionAdvisor}. + * + * @author Rod Johnson + * @see Pointcut + * @see MethodMatcher + */ +public interface ClassFilter { + + /** + * Should the pointcut apply to the given interface or target class? + * @param clazz the candidate target class + * @return whether the advice should apply to the given target class + */ + boolean matches(Class clazz); + + + /** + * Canonical instance of a ClassFilter that matches all classes. + */ + ClassFilter TRUE = TrueClassFilter.INSTANCE; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java new file mode 100644 index 0000000000..0a33639388 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/DynamicIntroductionAdvice.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2007 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.aop; + +import org.aopalliance.aop.Advice; + +/** + * Subinterface of AOP Alliance Advice that allows additional interfaces + * to be implemented by an Advice, and available via a proxy using that + * interceptor. This is a fundamental AOP concept called introduction. + * + *

Introductions are often mixins, enabling the building of composite + * objects that can achieve many of the goals of multiple inheritance in Java. + * + *

Compared to {qlink IntroductionInfo}, this interface allows an advice to + * implement a range of interfaces that is not necessarily known in advance. + * Thus an {@link IntroductionAdvisor} can be used to specify which interfaces + * will be exposed in an advised object. + * + * @author Rod Johnson + * @since 1.1.1 + * @see IntroductionInfo + * @see IntroductionAdvisor + */ +public interface DynamicIntroductionAdvice extends Advice { + + /** + * Does this introduction advice implement the given interface? + * @param intf the interface to check + * @return whether the advice implements the specified interface + */ + boolean implementsInterface(Class intf); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionAdvisor.java new file mode 100644 index 0000000000..9e71253ef0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionAdvisor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Superinterface for advisors that perform one or more AOP introductions. + * + *

This interface cannot be implemented directly; subinterfaces must + * provide the advice type implementing the introduction. + * + *

Introduction is the implementation of additional interfaces + * (not implemented by a target) via AOP advice. + * + * @author Rod Johnson + * @since 04.04.2003 + * @see IntroductionInterceptor + */ +public interface IntroductionAdvisor extends Advisor, IntroductionInfo { + + /** + * Return the filter determining which target classes this introduction + * should apply to. + *

This represents the class part of a pointcut. Note that method + * matching doesn't make sense to introductions. + * @return the class filter + */ + ClassFilter getClassFilter(); + + /** + * Can the advised interfaces be implemented by the introduction advice? + * Invoked before adding an IntroductionAdvisor. + * @throws IllegalArgumentException if the advised interfaces can't be + * implemented by the introduction advice + */ + void validateInterfaces() throws IllegalArgumentException; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java new file mode 100644 index 0000000000..1363960890 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionAwareMethodMatcher.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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.aop; + +import java.lang.reflect.Method; + +/** + * A specialized type of MethodMatcher that takes into account introductions when + * matching methods. If there are no introductions on the target class, a method + * matcher may be able to optimize matching more effectively for example. + * + * @author Adrian Colyer + * @since 2.0 + */ +public interface IntroductionAwareMethodMatcher extends MethodMatcher { + + /** + * Perform static checking whether the given method matches. This may be invoked + * instead of the 2-arg {@link #matches(java.lang.reflect.Method, Class)} method + * if the caller supports the extended IntroductionAwareMethodMatcher interface. + * @param method the candidate method + * @param targetClass the target class (may be null, in which case + * the candidate class must be taken to be the method's declaring class) + * @param hasIntroductions true if the object on whose behalf we are + * asking is the subject on one or more introductions; false otherwise + * @return whether or not this method matches statically + */ + boolean matches(Method method, Class targetClass, boolean hasIntroductions); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionInfo.java b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionInfo.java new file mode 100644 index 0000000000..c10e63c81d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionInfo.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Interface supplying the information necessary to describe an introduction. + * + *

{@link IntroductionAdvisor IntroductionAdvisors} must implement this + * interface. If an {@link org.aopalliance.aop.Advice} implements this, + * it may be used as an introduction without an {@link IntroductionAdvisor}. + * In this case, the advice is self-describing, providing not only the + * necessary behavior, but describing the interfaces it introduces. + * + * @author Rod Johnson + * @since 1.1.1 + */ +public interface IntroductionInfo { + + /** + * Return the additional interfaces introduced by this Advisor or Advice. + * @return the introduced interfaces + */ + Class[] getInterfaces(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionInterceptor.java new file mode 100644 index 0000000000..94fbcb1558 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/IntroductionInterceptor.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2007 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.aop; + +import org.aopalliance.intercept.MethodInterceptor; + +/** + * Subinterface of AOP Alliance MethodInterceptor that allows additional interfaces + * to be implemented by the interceptor, and available via a proxy using that + * interceptor. This is a fundamental AOP concept called introduction. + * + *

Introductions are often mixins, enabling the building of composite + * objects that can achieve many of the goals of multiple inheritance in Java. + * + * @author Rod Johnson + * @see DynamicIntroductionAdvice + */ +public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java new file mode 100644 index 0000000000..9383af683c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/MethodBeforeAdvice.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2005 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.aop; + +import java.lang.reflect.Method; + +/** + * Advice invoked before a method is invoked. Such advices cannot + * prevent the method call proceeding, unless they throw a Throwable. + * + * @see AfterReturningAdvice + * @see ThrowsAdvice + * + * @author Rod Johnson + */ +public interface MethodBeforeAdvice extends BeforeAdvice { + + /** + * Callback before a given method is invoked. + * @param method method being invoked + * @param args arguments to the method + * @param target target of the method invocation. May be null. + * @throws Throwable if this object wishes to abort the call. + * Any exception thrown will be returned to the caller if it's + * allowed by the method signature. Otherwise the exception + * will be wrapped as a runtime exception. + */ + void before(Method method, Object[] args, Object target) throws Throwable; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/MethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/MethodMatcher.java new file mode 100644 index 0000000000..3a260249b0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/MethodMatcher.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2007 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.aop; + +import java.lang.reflect.Method; + +/** + * Part of a {@link Pointcut}: Checks whether the target method is eligible for advice. + * + *

A MethodMatcher may be evaluated statically or at runtime (dynamically). + * Static matching involves method and (possibly) method attributes. Dynamic matching + * also makes arguments for a particular call available, and any effects of running + * previous advice applying to the joinpoint. + * + *

If an implementation returns false from its {@link #isRuntime()} + * method, evaluation can be performed statically, and the result will be the same + * for all invocations of this method, whatever their arguments. This means that + * if the {@link #isRuntime()} method returns false, the 3-arg + * {@link #matches(java.lang.reflect.Method, Class, Object[])} method will never be invoked. + * + *

If an implementation returns true from its 2-arg + * {@link #matches(java.lang.reflect.Method, Class)} method and its {@link #isRuntime()} method + * returns true, the 3-arg {@link #matches(java.lang.reflect.Method, Class, Object[])} + * method will be invoked immediately before each potential execution of the related advice, + * to decide whether the advice should run. All previous advice, such as earlier interceptors + * in an interceptor chain, will have run, so any state changes they have produced in + * parameters or ThreadLocal state will be available at the time of evaluation. + * + * @author Rod Johnson + * @since 11.11.2003 + * @see Pointcut + * @see ClassFilter + */ +public interface MethodMatcher { + + /** + * Perform static checking whether the given method matches. If this + * returns false or if the {@link #isRuntime()} method + * returns false, no runtime check (i.e. no. + * {@link #matches(java.lang.reflect.Method, Class, Object[])} call) will be made. + * @param method the candidate method + * @param targetClass the target class (may be null, in which case + * the candidate class must be taken to be the method's declaring class) + * @return whether or not this method matches statically + */ + boolean matches(Method method, Class targetClass); + + /** + * Is this MethodMatcher dynamic, that is, must a final call be made on the + * {@link #matches(java.lang.reflect.Method, Class, Object[])} method at + * runtime even if the 2-arg matches method returns true? + *

Can be invoked when an AOP proxy is created, and need not be invoked + * again before each method invocation, + * @return whether or not a runtime match via the 3-arg + * {@link #matches(java.lang.reflect.Method, Class, Object[])} method + * is required if static matching passed + */ + boolean isRuntime(); + + /** + * Check whether there a runtime (dynamic) match for this method, + * which must have matched statically. + *

This method is invoked only if the 2-arg matches method returns + * true for the given method and target class, and if the + * {@link #isRuntime()} method returns true. Invoked + * immediately before potential running of the advice, after any + * advice earlier in the advice chain has run. + * @param method the candidate method + * @param targetClass the target class (may be null, in which case + * the candidate class must be taken to be the method's declaring class) + * @param args arguments to the method + * @return whether there's a runtime match + * @see MethodMatcher#matches(Method, Class) + */ + boolean matches(Method method, Class targetClass, Object[] args); + + + /** + * Canonical instance that matches all methods. + */ + MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/Pointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/Pointcut.java new file mode 100644 index 0000000000..cebbfa023c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/Pointcut.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Core Spring pointcut abstraction. + * + *

A pointcut is composed of a {@link ClassFilter} and a {@link MethodMatcher}. + * Both these basic terms and a Pointcut itself can be combined to build up combinations + * (e.g. through {@link org.springframework.aop.support.ComposablePointcut}). + * + * @author Rod Johnson + * @see ClassFilter + * @see MethodMatcher + * @see org.springframework.aop.support.Pointcuts + * @see org.springframework.aop.support.ClassFilters + * @see org.springframework.aop.support.MethodMatchers + */ +public interface Pointcut { + + /** + * Return the ClassFilter for this pointcut. + * @return the ClassFilter (never null) + */ + ClassFilter getClassFilter(); + + /** + * Return the MethodMatcher for this pointcut. + * @return the MethodMatcher (never null) + */ + MethodMatcher getMethodMatcher(); + + + /** + * Canonical Pointcut instance that always matches. + */ + Pointcut TRUE = TruePointcut.INSTANCE; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/PointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/PointcutAdvisor.java new file mode 100644 index 0000000000..02818d12e7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/PointcutAdvisor.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2005 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.aop; + +/** + * Superinterface for all Advisors that are driven by a pointcut. + * This covers nearly all advisors except introduction advisors, + * for which method-level matching doesn't apply. + * + * @author Rod Johnson + */ +public interface PointcutAdvisor extends Advisor { + + /** + * Get the Pointcut that drives this advisor. + */ + Pointcut getPointcut(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/ProxyMethodInvocation.java b/org.springframework.aop/src/main/java/org/springframework/aop/ProxyMethodInvocation.java new file mode 100644 index 0000000000..00015a5d9e --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/ProxyMethodInvocation.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2007 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.aop; + +import org.aopalliance.intercept.MethodInvocation; + +/** + * Extension of the AOP Alliance {@link org.aopalliance.intercept.MethodInvocation} + * interface, allowing access to the proxy that the method invocation was made through. + * + *

Useful to be able to substitute return values with the proxy, + * if necessary, for example if the invocation target returned itself. + * + * @author Juergen Hoeller + * @author Adrian Colyer + * @since 1.1.3 + * @see org.springframework.aop.framework.ReflectiveMethodInvocation + * @see org.springframework.aop.support.DelegatingIntroductionInterceptor + */ +public interface ProxyMethodInvocation extends MethodInvocation { + + /** + * Return the proxy that this method invocation was made through. + * @return the original proxy object + */ + Object getProxy(); + + /** + * Create a clone of this object. If cloning is done before proceed() + * is invoked on this object, proceed() can be invoked once per clone + * to invoke the joinpoint (and the rest of the advice chain) more than once. + * @return an invocable clone of this invocation. + * proceed() can be called once per clone. + */ + MethodInvocation invocableClone(); + + /** + * Create a clone of this object. If cloning is done before proceed() + * is invoked on this object, proceed() can be invoked once per clone + * to invoke the joinpoint (and the rest of the advice chain) more than once. + * @param arguments the arguments that the cloned invocation is supposed to use, + * overriding the original arguments + * @return an invocable clone of this invocation. + * proceed() can be called once per clone. + */ + MethodInvocation invocableClone(Object[] arguments); + + /** + * Set the arguments to be used on subsequent invocations in the any advice + * in this chain. + * @param arguments the argument array + */ + void setArguments(Object[] arguments); + + /** + * Add the specified user attribute with the given value to this invocation. + *

Such attributes are not used within the AOP framework itself. They are + * just kept as part of the invocation object, for use in special interceptors. + * @param key the name of the attribute + * @param value the value of the attribute, or null to reset it + */ + void setUserAttribute(String key, Object value); + + /** + * Return the value of the specified user attribute. + * @param key the name of the attribute + * @return the value of the attribute, or null if not set + * @see #setUserAttribute + */ + Object getUserAttribute(String key); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/RawTargetAccess.java b/org.springframework.aop/src/main/java/org/springframework/aop/RawTargetAccess.java new file mode 100644 index 0000000000..4e3c6b787a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/RawTargetAccess.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Marker for AOP proxy interfaces (in particular: introduction interfaces) + * that explicitly intend to return the raw target object (which would normally + * get replaced with the proxy object when returned from a method invocation). + * + *

Note that this is a marker interface in the style of {@link java.io.Serializable}, + * semantically applying to a declared interface rather than to the full class + * of a concrete object. In other words, this marker applies to a particular + * interface only (typically an introduction interface that does not serve + * as the primary interface of an AOP proxy), and hence does not affect + * other interfaces that a concrete AOP proxy may implement. + * + * @author Juergen Hoeller + * @since 2.0.5 + * @see org.springframework.aop.scope.ScopedObject + */ +public interface RawTargetAccess { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/SpringProxy.java b/org.springframework.aop/src/main/java/org/springframework/aop/SpringProxy.java new file mode 100644 index 0000000000..29568ed268 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/SpringProxy.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Marker interface implemented by all AOP proxies. Used to detect + * whether or not objects are Spring-generated proxies. + * + * @author Rob Harrop + * @since 2.0.1 + * @see org.springframework.aop.support.AopUtils#isAopProxy(Object) + */ +public interface SpringProxy { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/TargetClassAware.java b/org.springframework.aop/src/main/java/org/springframework/aop/TargetClassAware.java new file mode 100644 index 0000000000..994e5c43b5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/TargetClassAware.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2007 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.aop; + +/** + * Minimal interface for exposing the target class behind a proxy. + * + *

Implemented by AOP proxy objects and proxy factories + * (via {@link org.springframework.aop.framework.Advised}} + * as well as by {@link TargetSource TargetSources}. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see org.springframework.aop.support.AopUtils#getTargetClass(Object) + */ +public interface TargetClassAware { + + /** + * Return the target class behind the implementing object + * (typically a proxy configuration or an actual proxy). + * @return the target Class, or null if not known + */ + Class getTargetClass(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/TargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/TargetSource.java new file mode 100644 index 0000000000..7d2604c29d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/TargetSource.java @@ -0,0 +1,70 @@ +/*< + * Copyright 2002-2007 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.aop; + +/** + * A TargetSource is used to obtain the current "target" of + * an AOP invocation, which will be invoked via reflection if no around + * advice chooses to end the interceptor chain itself. + * + *

If a TargetSource is "static", it will always return + * the same target, allowing optimizations in the AOP framework. Dynamic + * target sources can support pooling, hot swapping, etc. + * + *

Application developers don't usually need to work with + * TargetSources directly: this is an AOP framework interface. + * + * @author Rod Johnson + */ +public interface TargetSource extends TargetClassAware { + + /** + * Return the type of targets returned by this {@link TargetSource}. + *

Can return null, although certain usages of a + * TargetSource might just work with a predetermined + * target class. + * @return the type of targets returned by this {@link TargetSource} + */ + Class getTargetClass(); + + /** + * Will all calls to {@link #getTarget()} return the same object? + *

In that case, there will be no need to invoke + * {@link #releaseTarget(Object)}, and the AOP framework can cache + * the return value of {@link #getTarget()}. + * @return true if the target is immutable + * @see #getTarget + */ + boolean isStatic(); + + /** + * Return a target instance. Invoked immediately before the + * AOP framework calls the "target" of an AOP method invocation. + * @return the target object, which contains the joinpoint + * @throws Exception if the target object can't be resolved + */ + Object getTarget() throws Exception; + + /** + * Release the given target object obtained from the + * {@link #getTarget()} method. + * @param target object obtained from a call to {@link #getTarget()} + * @throws Exception if the object can't be released + */ + void releaseTarget(Object target) throws Exception; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/ThrowsAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/ThrowsAdvice.java new file mode 100644 index 0000000000..63d4260c33 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/ThrowsAdvice.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2008 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.aop; + +/** + * Tag interface for throws advice. + * + *

There are not any methods on this interface, as methods are invoked by + * reflection. Implementing classes must implement methods of the form: + * + *

void afterThrowing([Method, args, target], ThrowableSubclass);
+ * + *

Some examples of valid methods would be: + * + *

public void afterThrowing(Exception ex)
+ *
public void afterThrowing(RemoteException)
+ *
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
+ *
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)
+ * + * The first three arguments are optional, and only useful if we want further + * information about the joinpoint, as in AspectJ after-throwing advice. + * + *

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! + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see AfterReturningAdvice + * @see MethodBeforeAdvice + */ +public interface ThrowsAdvice extends AfterAdvice { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/TrueClassFilter.java b/org.springframework.aop/src/main/java/org/springframework/aop/TrueClassFilter.java new file mode 100644 index 0000000000..5aade6cfea --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/TrueClassFilter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2006 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.aop; + +import java.io.Serializable; + +/** + * Canonical ClassFilter instance that matches all classes. + * + * @author Rod Johnson + */ +class TrueClassFilter implements ClassFilter, Serializable { + + public static final TrueClassFilter INSTANCE = new TrueClassFilter(); + + /** + * Enforce Singleton pattern. + */ + private TrueClassFilter() { + } + + public boolean matches(Class clazz) { + return true; + } + + /** + * Required to support serialization. Replaces with canonical + * instance on deserialization, protecting Singleton pattern. + * Alternative to overriding equals(). + */ + private Object readResolve() { + return INSTANCE; + } + + public String toString() { + return "ClassFilter.TRUE"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java new file mode 100644 index 0000000000..4f9602cf91 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/TrueMethodMatcher.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2006 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.aop; + +import java.io.Serializable; +import java.lang.reflect.Method; + +/** + * Canonical MethodMatcher instance that matches all methods. + * + * @author Rod Johnson + */ +class TrueMethodMatcher implements MethodMatcher, Serializable { + + public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher(); + + /** + * Enforce Singleton pattern. + */ + private TrueMethodMatcher() { + } + + public boolean isRuntime() { + return false; + } + + public boolean matches(Method method, Class targetClass) { + return true; + } + + public boolean matches(Method method, Class targetClass, Object[] args) { + // Should never be invoked as isRuntime returns false. + throw new UnsupportedOperationException(); + } + + /** + * Required to support serialization. Replaces with canonical + * instance on deserialization, protecting Singleton pattern. + * Alternative to overriding equals(). + */ + private Object readResolve() { + return INSTANCE; + } + + public String toString() { + return "MethodMatcher.TRUE"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/TruePointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/TruePointcut.java new file mode 100644 index 0000000000..d7d5900a07 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/TruePointcut.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2006 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.aop; + +import java.io.Serializable; + +/** + * Canonical Pointcut instance that always matches. + * + * @author Rod Johnson + */ +class TruePointcut implements Pointcut, Serializable { + + public static final TruePointcut INSTANCE = new TruePointcut(); + + /** + * Enforce Singleton pattern. + */ + private TruePointcut() { + } + + public ClassFilter getClassFilter() { + return ClassFilter.TRUE; + } + + public MethodMatcher getMethodMatcher() { + return MethodMatcher.TRUE; + } + + /** + * Required to support serialization. Replaces with canonical + * instance on deserialization, protecting Singleton pattern. + * Alternative to overriding equals(). + */ + private Object readResolve() { + return INSTANCE; + } + + public String toString() { + return "Pointcut.TRUE"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java new file mode 100644 index 0000000000..3c40df60cd --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AbstractAspectJAdvice.java @@ -0,0 +1,691 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInvocation; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.weaver.tools.JoinPointMatch; +import org.aspectj.weaver.tools.PointcutParameter; + +import org.springframework.aop.AopInvocationException; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.aop.support.ComposablePointcut; +import org.springframework.aop.support.MethodMatchers; +import org.springframework.aop.support.StaticMethodMatcher; +import org.springframework.core.JdkVersion; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.PrioritizedParameterNameDiscoverer; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Base class for AOP Alliance {@link org.aopalliance.aop.Advice} classes + * wrapping an AspectJ aspect or an AspectJ-annotated advice method. + * + * @author Rod Johnson + * @author Adrian Colyer + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.0 + */ +public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation { + + /** + * Key used in ReflectiveMethodInvocation userAtributes map for the current joinpoint. + */ + protected static final String JOIN_POINT_KEY = JoinPoint.class.getName(); + + + /** + * Lazily instantiate joinpoint for the current invocation. + * Requires MethodInvocation to be bound with ExposeInvocationInterceptor. + *

Do not use if access is available to the current ReflectiveMethodInvocation + * (in an around advice). + * @return current AspectJ joinpoint, or through an exception if we're not in a + * Spring AOP invocation. + */ + public static JoinPoint currentJoinPoint() { + MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; + JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY); + if (jp == null) { + jp = new MethodInvocationProceedingJoinPoint(pmi); + pmi.setUserAttribute(JOIN_POINT_KEY, jp); + } + return jp; + } + + + protected final Method aspectJAdviceMethod; + + /** The total number of arguments we have to populate on advice dispatch */ + private final int adviceInvocationArgumentCount; + + private final AspectJExpressionPointcut pointcut; + + private final AspectInstanceFactory aspectInstanceFactory; + + /** + * The name of the aspect (ref bean) in which this advice was defined (used + * when determining advice precedence so that we can determine + * whether two pieces of advice come from the same aspect). + */ + private String aspectName; + + /** + * The order of declaration of this advice within the aspect. + */ + private int declarationOrder; + + /** + * This will be non-null if the creator of this advice object knows the argument names + * and sets them explicitly + */ + private String[] argumentNames = null; + + /** Non-null if after throwing advice binds the thrown value */ + private String throwingName = null; + + /** Non-null if after returning advice binds the return value */ + private String returningName = null; + + private Class discoveredReturningType = Object.class; + + private Class discoveredThrowingType = Object.class; + + /** + * Index for thisJoinPoint argument (currently only + * supported at index 0 if present at all) + */ + private int joinPointArgumentIndex = -1; + + /** + * Index for thisJoinPointStaticPart argument (currently only + * supported at index 0 if present at all) + */ + private int joinPointStaticPartArgumentIndex = -1; + + private Map argumentBindings = null; + + private boolean argumentsIntrospected = false; + + // The actual type is java.lang.reflect.Type, + // but for JDK 1.4 compatibility we use Object as the static type. + private Object discoveredReturningGenericType; + // Note: Unlike return type, no such generic information is needed for the throwing type, + // since Java doesn't allow exception types to be parameterized. + + + /** + * Create a new AbstractAspectJAdvice for the given advice method. + * @param aspectJAdviceMethod the AspectJ-style advice method + * @param pointcut the AspectJ expression pointcut + * @param aspectInstanceFactory the factory for aspect instances + */ + public AbstractAspectJAdvice( + Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) { + + Assert.notNull(aspectJAdviceMethod, "Advice method must not be null"); + this.aspectJAdviceMethod = aspectJAdviceMethod; + this.adviceInvocationArgumentCount = this.aspectJAdviceMethod.getParameterTypes().length; + this.pointcut = pointcut; + this.aspectInstanceFactory = aspectInstanceFactory; + } + + + /** + * Return the AspectJ-style advice method. + */ + public final Method getAspectJAdviceMethod() { + return this.aspectJAdviceMethod; + } + + /** + * Return the AspectJ expression pointcut. + */ + public final AspectJExpressionPointcut getPointcut() { + calculateArgumentBindings(); + return this.pointcut; + } + + /** + * Build a 'safe' pointcut that excludes the AspectJ advice method itself. + * @return a composable pointcut that builds on the original AspectJ expression pointcut + * @see #getPointcut() + */ + public final Pointcut buildSafePointcut() { + Pointcut pc = getPointcut(); + MethodMatcher safeMethodMatcher = MethodMatchers.intersection( + new AdviceExcludingMethodMatcher(this.aspectJAdviceMethod), pc.getMethodMatcher()); + return new ComposablePointcut(pc.getClassFilter(), safeMethodMatcher); + } + + /** + * Return the factory for aspect instances. + */ + public final AspectInstanceFactory getAspectInstanceFactory() { + return this.aspectInstanceFactory; + } + + /** + * Return the ClassLoader for aspect instances. + */ + public final ClassLoader getAspectClassLoader() { + return this.aspectInstanceFactory.getAspectClassLoader(); + } + + public int getOrder() { + return this.aspectInstanceFactory.getOrder(); + } + + + public void setAspectName(String name) { + this.aspectName = name; + } + + public String getAspectName() { + return this.aspectName; + } + + /** + * Sets the declaration order of this advice within the aspect + */ + public void setDeclarationOrder(int order) { + this.declarationOrder = order; + } + + public int getDeclarationOrder() { + return this.declarationOrder; + } + + /** + * Set by creator of this advice object if the argument names are known. + *

This could be for example because they have been explicitly specified in XML, + * or in an advice annotation. + * @param argNames comma delimited list of arg names + */ + public void setArgumentNames(String argNames) { + String[] tokens = StringUtils.commaDelimitedListToStringArray(argNames); + setArgumentNamesFromStringArray(tokens); + } + + public void setArgumentNamesFromStringArray(String[] args) { + this.argumentNames = new String[args.length]; + for (int i = 0; i < args.length; i++) { + this.argumentNames[i] = StringUtils.trimWhitespace(args[i]); + if (!isVariableName(this.argumentNames[i])) { + throw new IllegalArgumentException( + "'argumentNames' property of AbstractAspectJAdvice contains an argument name '" + + this.argumentNames[i] + "' that is not a valid Java identifier"); + } + } + if (argumentNames != null) { + if (aspectJAdviceMethod.getParameterTypes().length == argumentNames.length + 1) { + // May need to add implicit join point arg name... + Class firstArgType = aspectJAdviceMethod.getParameterTypes()[0]; + if (firstArgType == JoinPoint.class || + firstArgType == ProceedingJoinPoint.class || + firstArgType == JoinPoint.StaticPart.class) { + String[] oldNames = argumentNames; + argumentNames = new String[oldNames.length + 1]; + argumentNames[0] = "THIS_JOIN_POINT"; + System.arraycopy(oldNames, 0, argumentNames, 1, oldNames.length); + } + } + } + } + + public void setReturningName(String name) { + throw new UnsupportedOperationException("Only afterReturning advice can be used to bind a return value"); + } + + /** + * We need to hold the returning name at this level for argument binding calculations, + * this method allows the afterReturning advice subclass to set the name. + */ + protected void setReturningNameNoCheck(String name) { + // name could be a variable or a type... + if (isVariableName(name)) { + this.returningName = name; + } + else { + // assume a type + try { + this.discoveredReturningType = ClassUtils.forName(name, getAspectClassLoader()); + } + catch (Throwable ex) { + throw new IllegalArgumentException("Returning name '" + name + + "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " + + "Root cause: " + ex); + } + } + } + + protected Class getDiscoveredReturningType() { + return this.discoveredReturningType; + } + + protected Object getDiscoveredReturningGenericType() { + return this.discoveredReturningGenericType; + } + + public void setThrowingName(String name) { + throw new UnsupportedOperationException("Only afterThrowing advice can be used to bind a thrown exception"); + } + + /** + * We need to hold the throwing name at this level for argument binding calculations, + * this method allows the afterThrowing advice subclass to set the name. + */ + protected void setThrowingNameNoCheck(String name) { + // name could be a variable or a type... + if (isVariableName(name)) { + this.throwingName = name; + } + else { + // assume a type + try { + this.discoveredThrowingType = ClassUtils.forName(name, getAspectClassLoader()); + } + catch (Throwable ex) { + throw new IllegalArgumentException("Throwing name '" + name + + "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " + + "Root cause: " + ex); + } + } + } + + protected Class getDiscoveredThrowingType() { + return this.discoveredThrowingType; + } + + private boolean isVariableName(String name) { + char[] chars = name.toCharArray(); + if (!Character.isJavaIdentifierStart(chars[0])) { + return false; + } + for (int i = 1; i < chars.length; i++) { + if (!Character.isJavaIdentifierPart(chars[i])) { + return false; + } + } + return true; + } + + + /** + * Do as much work as we can as part of the set-up so that argument binding + * on subsequent advice invocations can be as fast as possible. + *

If the first argument is of type JoinPoint or ProceedingJoinPoint then we + * pass a JoinPoint in that position (ProceedingJoinPoint for around advice). + *

If the first argument is of type JoinPoint.StaticPart + * then we pass a JoinPoint.StaticPart in that position. + *

Remaining arguments have to be bound by pointcut evaluation at + * a given join point. We will get back a map from argument name to + * value. We need to calculate which advice parameter needs to be bound + * to which argument name. There are multiple strategies for determining + * this binding, which are arranged in a ChainOfResponsibility. + */ + public synchronized final void calculateArgumentBindings() { + // The simple case... nothing to bind. + if (this.argumentsIntrospected || this.adviceInvocationArgumentCount == 0) { + return; + } + + int numUnboundArgs = this.adviceInvocationArgumentCount; + Class[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes(); + if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) { + numUnboundArgs--; + } + else if (maybeBindJoinPointStaticPart(parameterTypes[0])) { + numUnboundArgs--; + } + + if (numUnboundArgs > 0) { + // need to bind arguments by name as returned from the pointcut match + bindArgumentsByName(numUnboundArgs); + } + + this.argumentsIntrospected = true; + } + + private boolean maybeBindJoinPoint(Class candidateParameterType) { + if (candidateParameterType.equals(JoinPoint.class)) { + this.joinPointArgumentIndex = 0; + return true; + } + else { + return false; + } + } + + private boolean maybeBindProceedingJoinPoint(Class candidateParameterType) { + if (candidateParameterType.equals(ProceedingJoinPoint.class)) { + if (!supportsProceedingJoinPoint()) { + throw new IllegalArgumentException("ProceedingJoinPoint is only supported for around advice"); + } + this.joinPointArgumentIndex = 0; + return true; + } + else { + return false; + } + } + + protected boolean supportsProceedingJoinPoint() { + return false; + } + + private boolean maybeBindJoinPointStaticPart(Class candidateParameterType) { + if (candidateParameterType.equals(JoinPoint.StaticPart.class)) { + this.joinPointStaticPartArgumentIndex = 0; + return true; + } + else { + return false; + } + } + + private void bindArgumentsByName(int numArgumentsExpectingToBind) { + if (this.argumentNames == null) { + this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod); + } + if (this.argumentNames != null) { + // We have been able to determine the arg names. + bindExplicitArguments(numArgumentsExpectingToBind); + } + else { + throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " + + "requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " + + "the argument names were not specified and could not be discovered."); + } + } + + /** + * Create a ParameterNameDiscoverer to be used for argument binding. + *

The default implementation creates a {@link PrioritizedParameterNameDiscoverer} + * containing a {@link LocalVariableTableParameterNameDiscoverer} and an + * {@link AspectJAdviceParameterNameDiscoverer}. + */ + protected ParameterNameDiscoverer createParameterNameDiscoverer() { + // We need to discover them, or if that fails, guess, + // and if we can't guess with 100% accuracy, fail. + PrioritizedParameterNameDiscoverer discoverer = new PrioritizedParameterNameDiscoverer(); + discoverer.addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); + AspectJAdviceParameterNameDiscoverer adviceParameterNameDiscoverer = + new AspectJAdviceParameterNameDiscoverer(this.pointcut.getExpression()); + adviceParameterNameDiscoverer.setReturningName(this.returningName); + adviceParameterNameDiscoverer.setThrowingName(this.throwingName); + // Last in chain, so if we're called and we fail, that's bad... + adviceParameterNameDiscoverer.setRaiseExceptions(true); + discoverer.addDiscoverer(adviceParameterNameDiscoverer); + return discoverer; + } + + private void bindExplicitArguments(int numArgumentsLeftToBind) { + this.argumentBindings = new HashMap(); + + int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterTypes().length; + if (this.argumentNames.length != numExpectedArgumentNames) { + throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames + + " arguments to bind by name in advice, but actually found " + + this.argumentNames.length + " arguments."); + } + + // So we match in number... + int argumentIndexOffset = this.adviceInvocationArgumentCount - numArgumentsLeftToBind; + for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) { + this.argumentBindings.put(this.argumentNames[i],new Integer(i)); + } + + // Check that returning and throwing were in the argument names list if + // specified, and find the discovered argument types. + if (this.returningName != null) { + if (!this.argumentBindings.containsKey(this.returningName)) { + throw new IllegalStateException("Returning argument name '" + + this.returningName + "' was not bound in advice arguments"); + } + else { + Integer index = (Integer) this.argumentBindings.get(this.returningName); + this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index.intValue()]; + if (JdkVersion.isAtLeastJava15()) { + this.discoveredReturningGenericType = + this.aspectJAdviceMethod.getGenericParameterTypes()[index.intValue()]; + } + } + } + if (this.throwingName != null) { + if (!this.argumentBindings.containsKey(this.throwingName)) { + throw new IllegalStateException("Throwing argument name '" + + this.throwingName + "' was not bound in advice arguments"); + } + else { + Integer index = (Integer) this.argumentBindings.get(this.throwingName); + this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index.intValue()]; + } + } + + // configure the pointcut expression accordingly. + configurePointcutParameters(argumentIndexOffset); + } + + /** + * All parameters from argumentIndexOffset onwards are candidates for + * pointcut parameters - but returning and throwing vars are handled differently + * and must be removed from the list if present. + */ + private void configurePointcutParameters(int argumentIndexOffset) { + int numParametersToRemove = argumentIndexOffset; + if (this.returningName != null) { + numParametersToRemove++; + } + if (this.throwingName != null) { + numParametersToRemove++; + } + String[] pointcutParameterNames = new String[this.argumentNames.length - numParametersToRemove]; + Class[] pointcutParameterTypes = new Class[pointcutParameterNames.length]; + Class[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes(); + + int index = 0; + for (int i = 0; i < this.argumentNames.length; i++) { + if (i < argumentIndexOffset) { + continue; + } + if (this.argumentNames[i].equals(this.returningName) || + this.argumentNames[i].equals(this.throwingName)) { + continue; + } + pointcutParameterNames[index] = this.argumentNames[i]; + pointcutParameterTypes[index] = methodParameterTypes[i]; + index++; + } + + this.pointcut.setParameterNames(pointcutParameterNames); + this.pointcut.setParameterTypes(pointcutParameterTypes); + } + + /** + * Take the arguments at the method execution join point and output a set of arguments + * to the advice method + * @param jp the current JoinPoint + * @param jpMatch the join point match that matched this execution join point + * @param returnValue the return value from the method execution (may be null) + * @param ex the exception thrown by the method execution (may be null) + * @return the empty array if there are no arguments + */ + protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) { + calculateArgumentBindings(); + + // AMC start + Object[] adviceInvocationArgs = new Object[this.adviceInvocationArgumentCount]; + int numBound = 0; + + if (this.joinPointArgumentIndex != -1) { + adviceInvocationArgs[this.joinPointArgumentIndex] = jp; + numBound++; + } + else if (this.joinPointStaticPartArgumentIndex != -1) { + adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart(); + numBound++; + } + + if (!CollectionUtils.isEmpty(this.argumentBindings)) { + // binding from pointcut match + if (jpMatch != null) { + PointcutParameter[] parameterBindings = jpMatch.getParameterBindings(); + for (int i = 0; i < parameterBindings.length; i++) { + PointcutParameter parameter = parameterBindings[i]; + String name = parameter.getName(); + Integer index = (Integer) this.argumentBindings.get(name); + adviceInvocationArgs[index.intValue()] = parameter.getBinding(); + numBound++; + } + } + // binding from returning clause + if (this.returningName != null) { + Integer index = (Integer) this.argumentBindings.get(this.returningName); + adviceInvocationArgs[index.intValue()] = returnValue; + numBound++; + } + // binding from thrown exception + if (this.throwingName != null) { + Integer index = (Integer) this.argumentBindings.get(this.throwingName); + adviceInvocationArgs[index.intValue()] = ex; + numBound++; + } + } + + if (numBound != this.adviceInvocationArgumentCount) { + throw new IllegalStateException("Required to bind " + this.adviceInvocationArgumentCount + + " arguments, but only bound " + numBound + " (JoinPointMatch " + + (jpMatch == null ? "was NOT" : "WAS") + + " bound in invocation)"); + } + + return adviceInvocationArgs; + } + + + /** + * Invoke the advice method. + * @param jpMatch the JoinPointMatch that matched this execution join point + * @param returnValue the return value from the method execution (may be null) + * @param ex the exception thrown by the method execution (may be null) + * @return the invocation result + * @throws Throwable in case of invocation failure + */ + protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable { + return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex)); + } + + // As above, but in this case we are given the join point. + protected Object invokeAdviceMethod(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable t) + throws Throwable { + + return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t)); + } + + protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable { + Object[] actualArgs = args; + if (this.aspectJAdviceMethod.getParameterTypes().length == 0) { + actualArgs = null; + } + try { + ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); + // TODO AopUtils.invokeJoinpointUsingReflection + return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); + } + catch (IllegalArgumentException ex) { + throw new AopInvocationException("Mismatch on arguments to advice method [" + + this.aspectJAdviceMethod + "]; pointcut expression [" + + this.pointcut.getPointcutExpression() + "]", ex); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + + /** + * Overridden in around advice to return proceeding join point. + */ + protected JoinPoint getJoinPoint() { + return currentJoinPoint(); + } + + /** + * Get the current join point match at the join point we are being dispatched on. + */ + protected JoinPointMatch getJoinPointMatch() { + MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + return getJoinPointMatch((ProxyMethodInvocation) mi); + } + + // Note: We can't use JoinPointMatch.getClass().getName() as the key, since + // Spring AOP does all the matching at a join point, and then all the invocations. + // Under this scenario, if we just use JoinPointMatch as the key, then + // 'last man wins' which is not what we want at all. + // Using the expression is guaranteed to be safe, since 2 identical expressions + // are guaranteed to bind in exactly the same way. + protected JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) { + return (JoinPointMatch) pmi.getUserAttribute(this.pointcut.getExpression()); + } + + + public String toString() { + return getClass().getName() + ": advice method [" + this.aspectJAdviceMethod + "]; " + + "aspect name '" + this.aspectName + "'"; + } + + + /** + * MethodMatcher that excludes the specified advice method. + * @see AbstractAspectJAdvice#buildSafePointcut() + */ + private static class AdviceExcludingMethodMatcher extends StaticMethodMatcher { + + private final Method adviceMethod; + + public AdviceExcludingMethodMatcher(Method adviceMethod) { + this.adviceMethod = adviceMethod; + } + + public boolean matches(Method method, Class targetClass) { + return !this.adviceMethod.equals(method); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectInstanceFactory.java new file mode 100644 index 0000000000..0794a3fe1b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectInstanceFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.springframework.core.Ordered; + +/** + * Interface implemented to provide an instance of an AspectJ aspect. + * Decouples from Spring's bean factory. + * + *

Extends the {@link org.springframework.core.Ordered} interface + * to express an order value for the underlying aspect in a chain. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory#getBean + */ +public interface AspectInstanceFactory extends Ordered { + + /** + * Create an instance of this factory's aspect. + * @return the aspect instance (never null) + */ + Object getAspectInstance(); + + /** + * Expose the aspect class loader that this factory uses. + * @return the aspect class loader (never null) + */ + ClassLoader getAspectClassLoader(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java new file mode 100644 index 0000000000..f983be0110 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAdviceParameterNameDiscoverer.java @@ -0,0 +1,826 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.weaver.tools.PointcutParser; +import org.aspectj.weaver.tools.PointcutPrimitive; + +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * {@link ParameterNameDiscoverer} implementation that tries to deduce parameter names + * for an advice method from the pointcut expression, returning, and throwing clauses. + * If an unambiguous interpretation is not available, it returns null. + * + *

This class interprets arguments in the following way: + *

    + *
  1. If the first parameter of the method is of type {@link JoinPoint} + * or {@link ProceedingJoinPoint}, it is assumed to be for passing + * thisJoinPoint to the advice, and the parameter name will + * be assigned the value "thisJoinPoint".
  2. + *
  3. If the first parameter of the method is of type + * JoinPoint.StaticPart, it is assumed to be for passing + * "thisJoinPointStaticPart" to the advice, and the parameter name + * will be assigned the value "thisJoinPointStaticPart".
  4. + *
  5. If a {@link #setThrowingName(String) throwingName} has been set, and + * there are no unbound arguments of type Throwable+, then an + * {@link IllegalArgumentException} is raised. If there is more than one + * unbound argument of type Throwable+, then an + * {@link AmbiguousBindingException} is raised. If there is exactly one + * unbound argument of type Throwable+, then the corresponding + * parameter name is assigned the value <throwingName>.
  6. + *
  7. If there remain unbound arguments, then the pointcut expression is + * examined. Let a be the number of annotation-based pointcut + * expressions (@annotation, @this, @target, @args, + * @within, @withincode) that are used in binding form. Usage in + * binding form has itself to be deduced: if the expression inside the + * pointcut is a single string literal that meets Java variable name + * conventions it is assumed to be a variable name. If a is + * zero we proceed to the next stage. If a > 1 then an + * AmbiguousBindingException is raised. If a == 1, + * and there are no unbound arguments of type Annotation+, + * then an IllegalArgumentException is raised. if there is + * exactly one such argument, then the corresponding parameter name is + * assigned the value from the pointcut expression.
  8. + *
  9. If a returningName has been set, and there are no unbound arguments + * then an IllegalArgumentException is raised. If there is + * more than one unbound argument then an + * AmbiguousBindingException is raised. If there is exactly + * one unbound argument then the corresponding parameter name is assigned + * the value <returningName>.
  10. + *
  11. If there remain unbound arguments, then the pointcut expression is + * examined once more for this, target, and + * args pointcut expressions used in the binding form (binding + * forms are deduced as described for the annotation based pointcuts). If + * there remains more than one unbound argument of a primitive type (which + * can only be bound in args) then an + * AmbiguousBindingException is raised. If there is exactly + * one argument of a primitive type, then if exactly one args + * bound variable was found, we assign the corresponding parameter name + * the variable name. If there were no args bound variables + * found an IllegalStateException is raised. If there are + * multiple args bound variables, an + * AmbiguousBindingException is raised. At this point, if + * there remains more than one unbound argument we raise an + * AmbiguousBindingException. If there are no unbound arguments + * remaining, we are done. If there is exactly one unbound argument + * remaining, and only one candidate variable name unbound from + * this, target, or args, it is + * assigned as the corresponding parameter name. If there are multiple + * possibilities, an AmbiguousBindingException is raised.
  12. + *
+ * + *

The behavior on raising an IllegalArgumentException or + * AmbiguousBindingException is configurable to allow this discoverer + * to be used as part of a chain-of-responsibility. By default the condition will + * be logged and the getParameterNames(..) method will simply return + * null. If the {@link #setRaiseExceptions(boolean) raiseExceptions} + * property is set to true, the conditions will be thrown as + * IllegalArgumentException and AmbiguousBindingException, + * respectively. + * + *

Was that perfectly clear? ;) + * + *

Short version: If an unambiguous binding can be deduced, then it is. + * If the advice requirements cannot possibly be satisfied, then null + * is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions} + * property to true, descriptive exceptions will be thrown instead of + * returning null in the case that the parameter names cannot be discovered. + * + * @author Adrian Colyer + * @since 2.0 + */ +public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscoverer { + + private static final String ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation"; + + private static final String THIS_JOIN_POINT = "thisJoinPoint"; + private static final String THIS_JOIN_POINT_STATIC_PART = "thisJoinPointStaticPart"; + + // Steps in the binding algorithm... + private static final int STEP_JOIN_POINT_BINDING = 1; + private static final int STEP_THROWING_BINDING = 2; + private static final int STEP_ANNOTATION_BINDING = 3; + private static final int STEP_RETURNING_BINDING = 4; + private static final int STEP_PRIMITIVE_ARGS_BINDING = 5; + private static final int STEP_THIS_TARGET_ARGS_BINDING = 6; + private static final int STEP_REFERENCE_PCUT_BINDING = 7; + private static final int STEP_FINISHED = 8; + + private static final Set singleValuedAnnotationPcds = new HashSet(); + private static final Set nonReferencePointcutTokens = new HashSet(); + + private static Class annotationClass; + + + static { + singleValuedAnnotationPcds.add("@this"); + singleValuedAnnotationPcds.add("@target"); + singleValuedAnnotationPcds.add("@within"); + singleValuedAnnotationPcds.add("@withincode"); + singleValuedAnnotationPcds.add("@annotation"); + + Set pointcutPrimitives = PointcutParser.getAllSupportedPointcutPrimitives(); + for (Iterator iterator = pointcutPrimitives.iterator(); iterator.hasNext();) { + PointcutPrimitive primitive = (PointcutPrimitive) iterator.next(); + nonReferencePointcutTokens.add(primitive.getName()); + } + nonReferencePointcutTokens.add("&&"); + nonReferencePointcutTokens.add("!"); + nonReferencePointcutTokens.add("||"); + nonReferencePointcutTokens.add("and"); + nonReferencePointcutTokens.add("or"); + nonReferencePointcutTokens.add("not"); + + try { + annotationClass = ClassUtils.forName(ANNOTATION_CLASS_NAME, + AspectJAdviceParameterNameDiscoverer.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + // Running on < JDK 1.5, this is OK... + annotationClass = null; + } + } + + + private boolean raiseExceptions; + + /** + * If the advice is afterReturning, and binds the return value, this is the parameter name used. + */ + private String returningName; + + /** + * If the advice is afterThrowing, and binds the thrown value, this is the parameter name used. + */ + private String throwingName; + + /** + * The pointcut expression associated with the advice, as a simple String. + */ + private String pointcutExpression; + + private Class[] argumentTypes; + + private String[] parameterNameBindings; + + private int numberOfRemainingUnboundArguments; + + private int algorithmicStep = STEP_JOIN_POINT_BINDING; + + + /** + * Create a new discoverer that attempts to discover parameter names + * from the given pointcut expression. + */ + public AspectJAdviceParameterNameDiscoverer(String pointcutExpression) { + this.pointcutExpression = pointcutExpression; + } + + /** + * Indicate whether {@link IllegalArgumentException} and {@link AmbiguousBindingException} + * must be thrown as appropriate in the case of failing to deduce advice parameter names. + * @param raiseExceptions true if exceptions are to be thrown + */ + public void setRaiseExceptions(boolean raiseExceptions) { + this.raiseExceptions = raiseExceptions; + } + + /** + * If afterReturning advice binds the return value, the + * returning variable name must be specified. + * @param returningName the name of the returning variable + */ + public void setReturningName(String returningName) { + this.returningName = returningName; + } + + /** + * If afterThrowing advice binds the thrown value, the + * throwing variable name must be specified. + * @param throwingName the name of the throwing variable + */ + public void setThrowingName(String throwingName) { + this.throwingName = throwingName; + } + + /** + * Deduce the parameter names for an advice method. + *

See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc} + * for this class for details of the algorithm used. + * @param method the target {@link Method} + * @return the parameter names + */ + public String[] getParameterNames(Method method) { + this.argumentTypes = method.getParameterTypes(); + this.numberOfRemainingUnboundArguments = this.argumentTypes.length; + this.parameterNameBindings = new String[this.numberOfRemainingUnboundArguments]; + this.algorithmicStep = STEP_JOIN_POINT_BINDING; + + int minimumNumberUnboundArgs = 0; + if (this.returningName != null) { + minimumNumberUnboundArgs++; + } + if (this.throwingName != null) { + minimumNumberUnboundArgs++; + } + if (this.numberOfRemainingUnboundArguments < minimumNumberUnboundArgs) { + throw new IllegalStateException( + "Not enough arguments in method to satisfy binding of returning and throwing variables"); + } + + try { + while ((this.numberOfRemainingUnboundArguments > 0) && (this.algorithmicStep < STEP_FINISHED)) { + switch (this.algorithmicStep++) { + case STEP_JOIN_POINT_BINDING: + if (!maybeBindThisJoinPoint()) { + maybeBindThisJoinPointStaticPart(); + } + break; + case STEP_THROWING_BINDING: + maybeBindThrowingVariable(); + break; + case STEP_ANNOTATION_BINDING: + maybeBindAnnotationsFromPointcutExpression(); + break; + case STEP_RETURNING_BINDING: + maybeBindReturningVariable(); + break; + case STEP_PRIMITIVE_ARGS_BINDING: + maybeBindPrimitiveArgsFromPointcutExpression(); + break; + case STEP_THIS_TARGET_ARGS_BINDING: + maybeBindThisOrTargetOrArgsFromPointcutExpression(); + break; + case STEP_REFERENCE_PCUT_BINDING: + maybeBindReferencePointcutParameter(); + break; + default: + throw new IllegalStateException("Unknown algorithmic step: " + (this.algorithmicStep - 1)); + } + } + } + catch (AmbiguousBindingException ambigEx) { + if (this.raiseExceptions) { + throw ambigEx; + } + else { + return null; + } + } + catch (IllegalArgumentException ex) { + if (this.raiseExceptions) { + throw ex; + } + else { + return null; + } + } + + if (this.numberOfRemainingUnboundArguments == 0) { + return this.parameterNameBindings; + } + else { + if (this.raiseExceptions) { + throw new IllegalStateException("Failed to bind all argument names: " + + this.numberOfRemainingUnboundArguments + " argument(s) could not be bound"); + } + else { + // convention for failing is to return null, allowing participation in a chain of responsibility + return null; + } + } + } + + /** + * An advice method can never be a constructor in Spring. + * @return null + * @throws UnsupportedOperationException if + * {@link #setRaiseExceptions(boolean) raiseExceptions} has been set to true + */ + public String[] getParameterNames(Constructor ctor) { + if (this.raiseExceptions) { + throw new UnsupportedOperationException("An advice method can never be a constructor"); + } + else { + // we return null rather than throw an exception so that we behave well + // in a chain-of-responsibility. + return null; + } + } + + + private void bindParameterName(int index, String name) { + this.parameterNameBindings[index] = name; + this.numberOfRemainingUnboundArguments--; + } + + /** + * If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as + * parameter name and return true, else return false. + */ + private boolean maybeBindThisJoinPoint() { + if ((this.argumentTypes[0] == JoinPoint.class) || (this.argumentTypes[0] == ProceedingJoinPoint.class)) { + bindParameterName(0, THIS_JOIN_POINT); + return true; + } + else { + return false; + } + } + + private void maybeBindThisJoinPointStaticPart() { + if (this.argumentTypes[0] == JoinPoint.StaticPart.class) { + bindParameterName(0, THIS_JOIN_POINT_STATIC_PART); + } + } + + /** + * If a throwing name was specified and there is exactly one choice remaining + * (argument that is a subtype of Throwable) then bind it. + */ + private void maybeBindThrowingVariable() { + if (this.throwingName == null) { + return; + } + + // So there is binding work to do... + int throwableIndex = -1; + for (int i = 0; i < this.argumentTypes.length; i++) { + if (isUnbound(i) && isSubtypeOf(Throwable.class, i)) { + if (throwableIndex == -1) { + throwableIndex = i; + } + else { + // Second candidate we've found - ambiguous binding + throw new AmbiguousBindingException("Binding of throwing parameter '" + + this.throwingName + "' is ambiguous: could be bound to argument " + + throwableIndex + " or argument " + i); + } + } + } + + if (throwableIndex == -1) { + throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName + + "' could not be completed as no available arguments are a subtype of Throwable"); + } + else { + bindParameterName(throwableIndex, this.throwingName); + } + } + + /** + * If a returning variable was specified and there is only one choice remaining, bind it. + */ + private void maybeBindReturningVariable() { + if (this.numberOfRemainingUnboundArguments == 0) { + throw new IllegalStateException( + "Algorithm assumes that there must be at least one unbound parameter on entry to this method"); + } + + if (this.returningName != null) { + if (this.numberOfRemainingUnboundArguments > 1) { + throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName + + "' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates."); + } + + // We're all set... find the unbound parameter, and bind it. + for (int i = 0; i < this.parameterNameBindings.length; i++) { + if (this.parameterNameBindings[i] == null) { + bindParameterName(i, this.returningName); + break; + } + } + } + } + + + /** + * Parse the string pointcut expression looking for: + * @this, @target, @args, @within, @withincode, @annotation. + * If we find one of these pointcut expressions, try and extract a candidate variable + * name (or variable names, in the case of args). + *

Some more support from AspectJ in doing this exercise would be nice... :) + */ + private void maybeBindAnnotationsFromPointcutExpression() { + List varNames = new ArrayList(); + String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); + for (int i = 0; i < tokens.length; i++) { + String toMatch = tokens[i]; + int firstParenIndex = toMatch.indexOf("("); + if (firstParenIndex != -1) { + toMatch = toMatch.substring(0, firstParenIndex); + } + if (singleValuedAnnotationPcds.contains(toMatch)) { + PointcutBody body = getPointcutBody(tokens, i); + i += body.numTokensConsumed; + String varName = maybeExtractVariableName(body.text); + if (varName != null) { + varNames.add(varName); + } + } + else if (tokens[i].startsWith("@args(") || tokens[i].equals("@args")) { + PointcutBody body = getPointcutBody(tokens, i); + i += body.numTokensConsumed; + maybeExtractVariableNamesFromArgs(body.text, varNames); + } + } + + bindAnnotationsFromVarNames(varNames); + } + + /** + * Match the given list of extracted variable names to argument slots. + */ + private void bindAnnotationsFromVarNames(List varNames) { + if (!varNames.isEmpty()) { + // we have work to do... + int numAnnotationSlots = countNumberOfUnboundAnnotationArguments(); + if (numAnnotationSlots > 1) { + throw new AmbiguousBindingException("Found " + varNames.size() + + " potential annotation variable(s), and " + + numAnnotationSlots + " potential argument slots"); + } + else if (numAnnotationSlots == 1) { + if (varNames.size() == 1) { + // it's a match + findAndBind(annotationClass, (String) varNames.get(0)); + } + else { + // multiple candidate vars, but only one slot + throw new IllegalArgumentException("Found " + varNames.size() + + " candidate annotation binding variables" + + " but only one potential argument binding slot"); + } + } + else { + // no slots so presume those candidate vars were actually type names + } + } + } + + /* + * If the token starts meets Java identifier conventions, it's in. + */ + private String maybeExtractVariableName(String candidateToken) { + if (candidateToken == null || candidateToken.equals("")) { + return null; + } + if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) && + Character.isLowerCase(candidateToken.charAt(0))) { + char[] tokenChars = candidateToken.toCharArray(); + for (int i = 0; i < tokenChars.length; i++) { + if (!Character.isJavaIdentifierPart(tokenChars[i])) { + return null; + } + } + return candidateToken; + } + else { + return null; + } + } + + /** + * Given an args pointcut body (could be args or at_args), + * add any candidate variable names to the given list. + */ + private void maybeExtractVariableNamesFromArgs(String argsSpec, List varNames) { + if (argsSpec == null) { + return; + } + + String[] tokens = StringUtils.tokenizeToStringArray(argsSpec, ","); + for (int i = 0; i < tokens.length; i++) { + tokens[i] = StringUtils.trimWhitespace(tokens[i]); + String varName = maybeExtractVariableName(tokens[i]); + if (varName != null) { + varNames.add(varName); + } + } + } + + /** + * Parse the string pointcut expression looking for this(), target() and args() expressions. + * If we find one, try and extract a candidate variable name and bind it. + */ + private void maybeBindThisOrTargetOrArgsFromPointcutExpression() { + if (this.numberOfRemainingUnboundArguments > 1) { + throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments + + " unbound args at this(),target(),args() binding stage, with no way to determine between them"); + } + + List varNames = new ArrayList(); + String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); + for (int i = 0; i < tokens.length; i++) { + if (tokens[i].equals("this") || + tokens[i].startsWith("this(") || + tokens[i].equals("target") || + tokens[i].startsWith("target(")) { + PointcutBody body = getPointcutBody(tokens, i); + i += body.numTokensConsumed; + String varName = maybeExtractVariableName(body.text); + if (varName != null) { + varNames.add(varName); + } + } + else if (tokens[i].equals("args") || tokens[i].startsWith("args(")) { + PointcutBody body = getPointcutBody(tokens, i); + i += body.numTokensConsumed; + List candidateVarNames = new ArrayList(); + maybeExtractVariableNamesFromArgs(body.text, candidateVarNames); + // we may have found some var names that were bound in previous primitive args binding step, + // filter them out... + for (Iterator iter = candidateVarNames.iterator(); iter.hasNext();) { + String varName = (String) iter.next(); + if (!alreadyBound(varName)) { + varNames.add(varName); + } + } + } + } + + + if (varNames.size() > 1) { + throw new AmbiguousBindingException("Found " + varNames.size() + + " candidate this(), target() or args() variables but only one unbound argument slot"); + } + else if (varNames.size() == 1) { + for (int j = 0; j < this.parameterNameBindings.length; j++) { + if (isUnbound(j)) { + bindParameterName(j, (String) varNames.get(0)); + break; + } + } + } + // else varNames.size must be 0 and we have nothing to bind. + } + + private void maybeBindReferencePointcutParameter() { + if (this.numberOfRemainingUnboundArguments > 1) { + throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments + + " unbound args at reference pointcut binding stage, with no way to determine between them"); + } + + List varNames = new ArrayList(); + String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); + for (int i = 0; i < tokens.length; i++) { + String toMatch = tokens[i]; + if (toMatch.startsWith("!")) { + toMatch = toMatch.substring(1); + } + int firstParenIndex = toMatch.indexOf("("); + if (firstParenIndex != -1) { + toMatch = toMatch.substring(0, firstParenIndex); + } + else { + if (tokens.length < i + 2) { + // no "(" and nothing following + continue; + } + else { + String nextToken = tokens[i + 1]; + if (nextToken.charAt(0) != '(') { + // next token is not "(" either, can't be a pc... + continue; + } + } + + } + + // eat the body + PointcutBody body = getPointcutBody(tokens, i); + i += body.numTokensConsumed; + + if (!nonReferencePointcutTokens.contains(toMatch)) { + // then it could be a reference pointcut + String varName = maybeExtractVariableName(body.text); + if (varName != null) { + varNames.add(varName); + } + } + } + + if (varNames.size() > 1) { + throw new AmbiguousBindingException("Found " + varNames.size() + + " candidate reference pointcut variables but only one unbound argument slot"); + } + else if (varNames.size() == 1) { + for (int j = 0; j < this.parameterNameBindings.length; j++) { + if (isUnbound(j)) { + bindParameterName(j, (String) varNames.get(0)); + break; + } + } + } + // else varNames.size must be 0 and we have nothing to bind. + } + + /* + * We've found the start of a binding pointcut at the given index into the + * token array. Now we need to extract the pointcut body and return it. + */ + private PointcutBody getPointcutBody(String[] tokens, int startIndex) { + int numTokensConsumed = 0; + String currentToken = tokens[startIndex]; + int bodyStart = currentToken.indexOf('('); + if (currentToken.charAt(currentToken.length() - 1) == ')') { + // It's an all in one... get the text between the first (and the last) + return new PointcutBody(0, currentToken.substring(bodyStart + 1, currentToken.length() - 1)); + } + else { + StringBuffer sb = new StringBuffer(); + if (bodyStart >= 0 && bodyStart != (currentToken.length() - 1)) { + sb.append(currentToken.substring(bodyStart + 1)); + sb.append(" "); + } + numTokensConsumed++; + int currentIndex = startIndex + numTokensConsumed; + while (currentIndex < tokens.length) { + if (tokens[currentIndex].equals("(")) { + currentIndex++; + continue; + } + + if (tokens[currentIndex].endsWith(")")) { + sb.append(tokens[currentIndex].substring(0, tokens[currentIndex].length() - 1)); + return new PointcutBody(numTokensConsumed, sb.toString().trim()); + } + + String toAppend = tokens[currentIndex]; + if (toAppend.startsWith("(")) { + toAppend = toAppend.substring(1); + } + sb.append(toAppend); + sb.append(" "); + currentIndex++; + numTokensConsumed++; + } + + } + + // We looked and failed... + return new PointcutBody(numTokensConsumed, null); + } + + /** + * Match up args against unbound arguments of primitive types + */ + private void maybeBindPrimitiveArgsFromPointcutExpression() { + int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments(); + if (numUnboundPrimitives > 1) { + throw new AmbiguousBindingException("Found '" + numUnboundPrimitives + + "' unbound primitive arguments with no way to distinguish between them."); + } + if (numUnboundPrimitives == 1) { + // Look for arg variable and bind it if we find exactly one... + List varNames = new ArrayList(); + String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); + for (int i = 0; i < tokens.length; i++) { + if (tokens[i].equals("args") || tokens[i].startsWith("args(")) { + PointcutBody body = getPointcutBody(tokens, i); + i += body.numTokensConsumed; + maybeExtractVariableNamesFromArgs(body.text, varNames); + } + } + if (varNames.size() > 1) { + throw new AmbiguousBindingException("Found " + varNames.size() + + " candidate variable names but only one candidate binding slot when matching primitive args"); + } + else if (varNames.size() == 1) { + // 1 primitive arg, and one candidate... + for (int i = 0; i < this.argumentTypes.length; i++) { + if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) { + bindParameterName(i, (String) varNames.get(0)); + break; + } + } + } + } + } + + /* + * Return true if the parameter name binding for the given parameter + * index has not yet been assigned. + */ + private boolean isUnbound(int i) { + return this.parameterNameBindings[i] == null; + } + + private boolean alreadyBound(String varName) { + for (int i = 0; i < this.parameterNameBindings.length; i++) { + if (!isUnbound(i) && varName.equals(this.parameterNameBindings[i])) { + return true; + } + } + return false; + } + + /* + * Return true if the given argument type is a subclass + * of the given supertype. + */ + private boolean isSubtypeOf(Class supertype, int argumentNumber) { + return supertype.isAssignableFrom(this.argumentTypes[argumentNumber]); + } + + private int countNumberOfUnboundAnnotationArguments() { + if (annotationClass == null) { + // We're running on a JDK < 1.5 + return 0; + } + + int count = 0; + for (int i = 0; i < this.argumentTypes.length; i++) { + if (isUnbound(i) && isSubtypeOf(annotationClass, i)) { + count++; + } + } + return count; + } + + private int countNumberOfUnboundPrimitiveArguments() { + int count = 0; + for (int i = 0; i < this.argumentTypes.length; i++) { + if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) { + count++; + } + } + return count; + } + + /* + * Find the argument index with the given type, and bind the given + * varName in that position. + */ + private void findAndBind(Class argumentType, String varName) { + for (int i = 0; i < this.argumentTypes.length; i++) { + if (isUnbound(i) && isSubtypeOf(argumentType, i)) { + bindParameterName(i, varName); + return; + } + } + throw new IllegalStateException("Expected to find an unbound argument of type '" + + argumentType.getName() + "'"); + } + + + /** + * Simple struct to hold the extracted text from a pointcut body, together + * with the number of tokens consumed in extracting it. + */ + private static class PointcutBody { + + private int numTokensConsumed; + + private String text; + + public PointcutBody(int tokens, String text) { + this.numTokensConsumed = tokens; + this.text = text; + } + } + + + /** + * Thrown in response to an ambiguous binding being detected when + * trying to resolve a method's parameter names. + */ + public static class AmbiguousBindingException extends RuntimeException { + + /** + * Construct a new AmbiguousBindingException with the specified message. + * @param msg the detail message + */ + public AmbiguousBindingException(String msg) { + super(msg); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java new file mode 100644 index 0000000000..148f71ce5d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterAdvice.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.AfterAdvice; + +/** + * Spring AOP advice wrapping an AspectJ after advice method. + * + * @author Rod Johnson + * @since 2.0 + */ +public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice { + + public AspectJAfterAdvice( + Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { + + super(aspectJBeforeAdviceMethod, pointcut, aif); + } + + public Object invoke(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + finally { + invokeAdviceMethod(getJoinPointMatch(), null, null); + } + } + + public boolean isBeforeAdvice() { + return false; + } + + public boolean isAfterAdvice() { + return true; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java new file mode 100644 index 0000000000..f4bda7a6da --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterReturningAdvice.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import org.springframework.aop.AfterAdvice; +import org.springframework.aop.AfterReturningAdvice; +import org.springframework.util.ClassUtils; +import org.springframework.util.TypeUtils; + +/** + * Spring AOP advice wrapping an AspectJ after-returning advice method. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.0 + */ +public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice implements AfterReturningAdvice, AfterAdvice { + + public AspectJAfterReturningAdvice( + Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { + + super(aspectJBeforeAdviceMethod, pointcut, aif); + } + + public boolean isBeforeAdvice() { + return false; + } + + public boolean isAfterAdvice() { + return true; + } + + public void setReturningName(String name) { + setReturningNameNoCheck(name); + } + + public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { + if (shouldInvokeOnReturnValueOf(method, returnValue)) { + invokeAdviceMethod(getJoinPointMatch(), returnValue, null); + } + } + + /** + * Following AspectJ semantics, if a returning clause was specified, then the + * advice is only invoked if the returned value is an instance of the given + * returning type and generic type parameters, if any, match the assignment + * rules. If the returning type is Object, the advice is *always* invoked. + * @param returnValue the return value of the target method + * @return whether to invoke the advice method for the given return value + */ + private boolean shouldInvokeOnReturnValueOf(Method method, Object returnValue) { + Class type = getDiscoveredReturningType(); + Object genericType = getDiscoveredReturningGenericType(); + // If we aren't dealing with a raw type, check if generic parameters are assignable. + return (ClassUtils.isAssignableValue(type, returnValue) && + (genericType == null || genericType == type || GenericTypeMatcher.isAssignable(genericType, method))); + } + + + /** + * Inner class to avoid a static JDK 1.5 dependency for generic type matching. + */ + private static class GenericTypeMatcher { + + public static boolean isAssignable(Object genericType, Method method) { + return TypeUtils.isAssignable((Type) genericType, method.getGenericReturnType()); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java new file mode 100644 index 0000000000..1ed4f54e7d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAfterThrowingAdvice.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.AfterAdvice; + +/** + * Spring AOP advice wrapping an AspectJ after-throwing advice method. + * + * @author Rod Johnson + * @since 2.0 + */ +public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice { + + public AspectJAfterThrowingAdvice( + Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { + + super(aspectJBeforeAdviceMethod, pointcut, aif); + } + + public boolean isBeforeAdvice() { + return false; + } + + public boolean isAfterAdvice() { + return true; + } + + public void setThrowingName(String name) { + setThrowingNameNoCheck(name); + } + + public Object invoke(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + catch (Throwable t) { + if (shouldInvokeOnThrowing(t)) { + invokeAdviceMethod(getJoinPointMatch(), null, t); + } + throw t; + } + } + + /** + * In AspectJ semantics, after throwing advice that specifies a throwing clause + * is only invoked if the thrown exception is a subtype of the given throwing type. + */ + private boolean shouldInvokeOnThrowing(Throwable t) { + Class throwingType = getDiscoveredThrowingType(); + return throwingType.isAssignableFrom(t.getClass()); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java new file mode 100644 index 0000000000..d8fe47d3e6 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAopUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Advisor; +import org.springframework.aop.AfterAdvice; +import org.springframework.aop.BeforeAdvice; + +/** + * Utility methods for dealing with AspectJ advisors. + * + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AspectJAopUtils { + + /** + * Return true if the advisor is a form of before advice. + */ + public static boolean isBeforeAdvice(Advisor anAdvisor) { + AspectJPrecedenceInformation precedenceInfo = getAspectJPrecedenceInformationFor(anAdvisor); + if (precedenceInfo != null) { + return precedenceInfo.isBeforeAdvice(); + } + return (anAdvisor.getAdvice() instanceof BeforeAdvice); + } + + /** + * Return true if the advisor is a form of after advice. + */ + public static boolean isAfterAdvice(Advisor anAdvisor) { + AspectJPrecedenceInformation precedenceInfo = getAspectJPrecedenceInformationFor(anAdvisor); + if (precedenceInfo != null) { + return precedenceInfo.isAfterAdvice(); + } + return (anAdvisor.getAdvice() instanceof AfterAdvice); + } + + /** + * Return the AspectJPrecedenceInformation provided by this advisor or its advice. + * If neither the advisor nor the advice have precedence information, this method + * will return null. + */ + public static AspectJPrecedenceInformation getAspectJPrecedenceInformationFor(Advisor anAdvisor) { + if (anAdvisor instanceof AspectJPrecedenceInformation) { + return (AspectJPrecedenceInformation) anAdvisor; + } + Advice advice = anAdvisor.getAdvice(); + if (advice instanceof AspectJPrecedenceInformation) { + return (AspectJPrecedenceInformation) advice; + } + return null; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java new file mode 100644 index 0000000000..9af7962a4c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJAroundAdvice.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.weaver.tools.JoinPointMatch; + +import org.springframework.aop.ProxyMethodInvocation; + +/** + * Spring AOP around advice (MethodInterceptor) that wraps + * an AspectJ advice method. Exposes ProceedingJoinPoint. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor { + + public AspectJAroundAdvice( + Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { + + super(aspectJAroundAdviceMethod, pointcut, aif); + } + + public boolean isBeforeAdvice() { + return false; + } + + public boolean isAfterAdvice() { + return false; + } + + protected boolean supportsProceedingJoinPoint() { + return true; + } + + + public Object invoke(MethodInvocation mi) throws Throwable { + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; + ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi); + JoinPointMatch jpm = getJoinPointMatch(pmi); + return invokeAdviceMethod(pjp, jpm, null, null); + } + + /** + * Return the ProceedingJoinPoint for the current invocation, + * instantiating it lazily if it hasn't been bound to the thread already. + * @param rmi the current Spring AOP ReflectiveMethodInvocation, + * which we'll use for attribute binding + * @return the ProceedingJoinPoint to make available to advice methods + */ + protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) { + return new MethodInvocationProceedingJoinPoint(rmi); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java new file mode 100644 index 0000000000..da189d0817 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcut.java @@ -0,0 +1,530 @@ +/* + * Copyright 2002-2008 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.aop.aspectj; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.weaver.BCException; +import org.aspectj.weaver.patterns.NamePattern; +import org.aspectj.weaver.reflect.ReflectionWorld; +import org.aspectj.weaver.tools.ContextBasedMatcher; +import org.aspectj.weaver.tools.FuzzyBoolean; +import org.aspectj.weaver.tools.JoinPointMatch; +import org.aspectj.weaver.tools.MatchingContext; +import org.aspectj.weaver.tools.PointcutDesignatorHandler; +import org.aspectj.weaver.tools.PointcutExpression; +import org.aspectj.weaver.tools.PointcutParameter; +import org.aspectj.weaver.tools.PointcutParser; +import org.aspectj.weaver.tools.PointcutPrimitive; +import org.aspectj.weaver.tools.ShadowMatch; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.IntroductionAwareMethodMatcher; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.aop.framework.autoproxy.ProxyCreationContext; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.aop.support.AbstractExpressionPointcut; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Spring {@link org.springframework.aop.Pointcut} implementation + * that uses the AspectJ weaver to evaluate a pointcut expression. + * + *

The pointcut expression value is an AspectJ expression. This can + * reference other pointcuts and use composition and other operations. + * + *

Naturally, as this is to be processed by Spring AOP's proxy-based model, + * only method execution pointcuts are supported. + * + * @author Rob Harrop + * @author Adrian Colyer + * @author Rod Johnson + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.0 + */ +public class AspectJExpressionPointcut extends AbstractExpressionPointcut + implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware { + + private static final Set DEFAULT_SUPPORTED_PRIMITIVES = new HashSet(); + + static { + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS); + DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET); + } + + + private static final Log logger = LogFactory.getLog(AspectJExpressionPointcut.class); + + private final Map shadowMapCache = new HashMap(); + + private PointcutParser pointcutParser; + + private Class pointcutDeclarationScope; + + private String[] pointcutParameterNames = new String[0]; + + private Class[] pointcutParameterTypes = new Class[0]; + + private BeanFactory beanFactory; + + private PointcutExpression pointcutExpression; + + + /** + * Create a new default AspectJExpressionPointcut. + */ + public AspectJExpressionPointcut() { + this(DEFAULT_SUPPORTED_PRIMITIVES); + } + + /** + * Create a new AspectJExpressionPointcut with the given supported primitives. + * @param supportedPrimitives Set of {@link org.aspectj.weaver.tools.PointcutPrimitive} + * instances + */ + public AspectJExpressionPointcut(Set supportedPrimitives) { + this.pointcutParser = + PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution( + supportedPrimitives); + this.pointcutParser.registerPointcutDesignatorHandler(new BeanNamePointcutDesignatorHandler()); + } + + /** + * Create a new AspectJExpressionPointcut with the given settings. + * @param declarationScope the declaration scope for the pointcut + * @param paramNames the parameter names for the pointcut + * @param paramTypes the parameter types for the pointcut + */ + public AspectJExpressionPointcut(Class declarationScope, String[] paramNames, Class[] paramTypes) { + this(DEFAULT_SUPPORTED_PRIMITIVES); + this.pointcutDeclarationScope = declarationScope; + if (paramNames.length != paramTypes.length) { + throw new IllegalStateException( + "Number of pointcut parameter names must match number of pointcut parameter types"); + } + this.pointcutParameterNames = paramNames; + this.pointcutParameterTypes = paramTypes; + } + + + /** + * Set the declaration scope for the pointcut. + */ + public void setPointcutDeclarationScope(Class pointcutDeclarationScope) { + this.pointcutDeclarationScope = pointcutDeclarationScope; + } + + /** + * Set the parameter names for the pointcut. + */ + public void setParameterNames(String[] names) { + this.pointcutParameterNames = names; + } + + /** + * Set the parameter types for the pointcut. + */ + public void setParameterTypes(Class[] types) { + this.pointcutParameterTypes = types; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + + public ClassFilter getClassFilter() { + checkReadyToMatch(); + return this; + } + + public MethodMatcher getMethodMatcher() { + checkReadyToMatch(); + return this; + } + + + /** + * Check whether this pointcut is ready to match, + * lazily building the underlying AspectJ pointcut expression. + */ + private void checkReadyToMatch() { + if (getExpression() == null) { + throw new IllegalStateException("Must set property 'expression' before attempting to match"); + } + if (this.pointcutExpression == null) { + this.pointcutExpression = buildPointcutExpression(); + } + } + + /** + * Build the underlying AspectJ pointcut expression. + */ + private PointcutExpression buildPointcutExpression() { + PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length]; + for (int i = 0; i < pointcutParameters.length; i++) { + pointcutParameters[i] = this.pointcutParser.createPointcutParameter( + this.pointcutParameterNames[i], this.pointcutParameterTypes[i]); + } + return this.pointcutParser.parsePointcutExpression( + replaceBooleanOperators(getExpression()), this.pointcutDeclarationScope, pointcutParameters); + } + + /** + * If a pointcut expression has been specified in XML, the user cannot + * write and as "&&" (though && will work). + * We also allow and between two pointcut sub-expressions. + *

This method converts back to && for the AspectJ pointcut parser. + */ + private String replaceBooleanOperators(String pcExpr) { + pcExpr = StringUtils.replace(pcExpr," and "," && "); + pcExpr = StringUtils.replace(pcExpr, " or ", " || "); + pcExpr = StringUtils.replace(pcExpr, " not ", " ! "); + return pcExpr; + } + + /** + * Return the underlying AspectJ pointcut expression. + */ + public PointcutExpression getPointcutExpression() { + checkReadyToMatch(); + return this.pointcutExpression; + } + + + public boolean matches(Class targetClass) { + checkReadyToMatch(); + try { + return this.pointcutExpression.couldMatchJoinPointsInType(targetClass); + } + catch (BCException ex) { + logger.debug("PointcutExpression matching rejected target class", ex); + return false; + } + } + + public boolean matches(Method method, Class targetClass, boolean beanHasIntroductions) { + checkReadyToMatch(); + Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass); + ShadowMatch shadowMatch = null; + try { + shadowMatch = getShadowMatch(targetMethod, method); + } + catch (ReflectionWorld.ReflectionWorldException ex) { + // Could neither introspect the target class nor the proxy class -> + // let's simply consider this method as non-matching. + return false; + } + + // Special handling for this, target, @this, @target, @annotation + // in Spring - we can optimize since we know we have exactly this class, + // and there will never be matching subclass at runtime. + if (shadowMatch.alwaysMatches()) { + return true; + } + else if (shadowMatch.neverMatches()) { + return false; + } + else { + // the maybe case + return (beanHasIntroductions || matchesIgnoringSubtypes(shadowMatch) || matchesTarget(shadowMatch, targetClass)); + } + } + + public boolean matches(Method method, Class targetClass) { + return matches(method, targetClass, false); + } + + public boolean isRuntime() { + checkReadyToMatch(); + return this.pointcutExpression.mayNeedDynamicTest(); + } + + public boolean matches(Method method, Class targetClass, Object[] args) { + checkReadyToMatch(); + ShadowMatch shadowMatch = null; + ShadowMatch originalShadowMatch = null; + try { + shadowMatch = getShadowMatch(AopUtils.getMostSpecificMethod(method, targetClass), method); + originalShadowMatch = getShadowMatch(method, method); + } + catch (ReflectionWorld.ReflectionWorldException ex) { + // Could neither introspect the target class nor the proxy class -> + // let's simply consider this method as non-matching. + return false; + } + + // Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target, + // consistent with return of MethodInvocationProceedingJoinPoint + ProxyMethodInvocation pmi = null; + Object targetObject = null; + Object thisObject = null; + try { + MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); + targetObject = mi.getThis(); + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + pmi = (ProxyMethodInvocation) mi; + thisObject = pmi.getProxy(); + } + catch (IllegalStateException ex) { + // No current invocation... + // TODO: Should we really proceed here? + logger.debug("Couldn't access current invocation - matching with limited context: " + ex); + } + + JoinPointMatch joinPointMatch = shadowMatch.matchesJoinPoint(thisObject, targetObject, args); + + /* + * Do a final check to see if any this(TYPE) kind of residue match. For + * this purpose, we use the original method's (proxy method's) shadow to + * ensure that 'this' is correctly checked against. Without this check, + * we get incorrect match on this(TYPE) where TYPE matches the target + * type but not 'this' (as would be the case of JDK dynamic proxies). + *

See SPR-2979 for the original bug. + */ + if (pmi != null) { // there is a current invocation + RuntimeTestWalker originalMethodResidueTest = new RuntimeTestWalker(originalShadowMatch); + if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) { + return false; + } + } + if (joinPointMatch.matches() && pmi != null) { + bindParameters(pmi, joinPointMatch); + } + return joinPointMatch.matches(); + } + + + protected String getCurrentProxiedBeanName() { + return ProxyCreationContext.getCurrentProxiedBeanName(); + } + + + /** + * A match test returned maybe - if there are any subtype sensitive variables + * involved in the test (this, target, at_this, at_target, at_annotation) then + * we say this is not a match as in Spring there will never be a different + * runtime subtype. + */ + private boolean matchesIgnoringSubtypes(ShadowMatch shadowMatch) { + return !(new RuntimeTestWalker(shadowMatch).testsSubtypeSensitiveVars()); + } + + private boolean matchesTarget(ShadowMatch shadowMatch, Class targetClass) { + return new RuntimeTestWalker(shadowMatch).testTargetInstanceOfResidue(targetClass); + } + + private void bindParameters(ProxyMethodInvocation invocation, JoinPointMatch jpm) { + // Note: Can't use JoinPointMatch.getClass().getName() as the key, since + // Spring AOP does all the matching at a join point, and then all the invocations + // under this scenario, if we just use JoinPointMatch as the key, then + // 'last man wins' which is not what we want at all. + // Using the expression is guaranteed to be safe, since 2 identical expressions + // are guaranteed to bind in exactly the same way. + invocation.setUserAttribute(getExpression(), jpm); + } + + private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) { + synchronized (this.shadowMapCache) { + ShadowMatch shadowMatch = (ShadowMatch) this.shadowMapCache.get(targetMethod); + if (shadowMatch == null) { + try { + shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod); + } + catch (ReflectionWorld.ReflectionWorldException ex) { + // Failed to introspect target method, probably because it has been loaded + // in a special ClassLoader. Let's try the original method instead... + if (targetMethod == originalMethod) { + throw ex; + } + shadowMatch = this.pointcutExpression.matchesMethodExecution(originalMethod); + } + this.shadowMapCache.put(targetMethod, shadowMatch); + } + return shadowMatch; + } + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AspectJExpressionPointcut)) { + return false; + } + AspectJExpressionPointcut otherPc = (AspectJExpressionPointcut) other; + return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) && + ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) && + ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) && + ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes); + } + + public int hashCode() { + int hashCode = ObjectUtils.nullSafeHashCode(this.getExpression()); + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutDeclarationScope); + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterNames); + hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterTypes); + return hashCode; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("AspectJExpressionPointcut: "); + if (this.pointcutParameterNames != null && this.pointcutParameterTypes != null) { + sb.append("("); + for (int i = 0; i < this.pointcutParameterTypes.length; i++) { + sb.append(this.pointcutParameterTypes[i].getName()); + sb.append(" "); + sb.append(this.pointcutParameterNames[i]); + if ((i+1) < this.pointcutParameterTypes.length) { + sb.append(", "); + } + } + sb.append(")"); + } + sb.append(" "); + if (getExpression() != null) { + sb.append(getExpression()); + } + else { + sb.append(""); + } + return sb.toString(); + } + + + /** + * Handler for the Spring-specific bean() pointcut designator + * extension to AspectJ. + *

This handler must be added to each pointcut object that needs to + * handle the bean() PCD. Matching context is obtained + * automatically by examining a thread local variable and therefore a matching + * context need not be set on the pointcut. + */ + private class BeanNamePointcutDesignatorHandler implements PointcutDesignatorHandler { + + private static final String BEAN_DESIGNATOR_NAME = "bean"; + + public String getDesignatorName() { + return BEAN_DESIGNATOR_NAME; + } + + public ContextBasedMatcher parse(String expression) { + return new BeanNameContextMatcher(expression); + } + } + + + /** + * Matcher class for the BeanNamePointcutDesignatorHandler. + * + *

Dynamic match tests for this matcher always return true, + * since the matching decision is made at the proxy creation time. + * For static match tests, this matcher abstains to allow the overall + * pointcut to match even when negation is used with the bean() poitncut. + */ + private class BeanNameContextMatcher implements ContextBasedMatcher { + + private final NamePattern expressionPattern; + + public BeanNameContextMatcher(String expression) { + this.expressionPattern = new NamePattern(expression); + } + + public boolean couldMatchJoinPointsInType(Class someClass) { + return (contextMatch(someClass) == FuzzyBoolean.YES); + } + + public boolean couldMatchJoinPointsInType(Class someClass, MatchingContext context) { + return (contextMatch(someClass) == FuzzyBoolean.YES); + } + + public boolean matchesDynamically(MatchingContext context) { + return true; + } + + public FuzzyBoolean matchesStatically(MatchingContext context) { + return contextMatch(null); + } + + public boolean mayNeedDynamicTest() { + return false; + } + + private FuzzyBoolean contextMatch(Class targetType) { + String advisedBeanName = getCurrentProxiedBeanName(); + if (advisedBeanName == null) { // no proxy creation in progress + // abstain; can't return YES, since that will make pointcut with negation fail + return FuzzyBoolean.MAYBE; + } + if (BeanFactoryUtils.isGeneratedBeanName(advisedBeanName)) { + return FuzzyBoolean.NO; + } + if (targetType != null) { + boolean isFactory = FactoryBean.class.isAssignableFrom(targetType); + return FuzzyBoolean.fromBoolean( + matchesBeanName(isFactory ? BeanFactory.FACTORY_BEAN_PREFIX + advisedBeanName : advisedBeanName)); + } + else { + return FuzzyBoolean.fromBoolean(matchesBeanName(advisedBeanName) || + matchesBeanName(BeanFactory.FACTORY_BEAN_PREFIX + advisedBeanName)); + } + } + + private boolean matchesBeanName(String advisedBeanName) { + if (this.expressionPattern.matches(advisedBeanName)) { + return true; + } + if (beanFactory != null) { + String[] aliases = beanFactory.getAliases(advisedBeanName); + for (int i = 0; i < aliases.length; i++) { + if (this.expressionPattern.matches(aliases[i])) { + return true; + } + } + } + return false; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java new file mode 100644 index 0000000000..001b161fe4 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJExpressionPointcutAdvisor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2006 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.aop.aspectj; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractGenericPointcutAdvisor; + +/** + * Spring AOP Advisor that can be used for any AspectJ pointcut expression. + * + * @author Rob Harrop + * @since 2.0 + */ +public class AspectJExpressionPointcutAdvisor extends AbstractGenericPointcutAdvisor { + + private final AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); + + + public Pointcut getPointcut() { + return this.pointcut; + } + + public void setExpression(String expression) { + this.pointcut.setExpression(expression); + } + + public void setLocation(String location) { + this.pointcut.setLocation(location); + } + + public void setParameterTypes(Class[] types) { + this.pointcut.setParameterTypes(types); + } + + public void setParameterNames(String[] names) { + this.pointcut.setParameterNames(names); + } + + public String getLocation() { + return this.pointcut.getLocation(); + } + + public String getExpression() { + return this.pointcut.getExpression(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java new file mode 100644 index 0000000000..503119714f --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJMethodBeforeAdvice.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2006 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.aop.aspectj; + +import java.lang.reflect.Method; + +import org.springframework.aop.MethodBeforeAdvice; + +/** + * Spring AOP advice that wraps an AspectJ before method. + * + * @author Rod Johnson + * @author Adrian Colyer + * @since 2.0 + */ +public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice { + + public AspectJMethodBeforeAdvice( + Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) { + + super(aspectJBeforeAdviceMethod, pointcut, aif); + } + + public void before(Method method, Object[] args, Object target) throws Throwable { + invokeAdviceMethod(getJoinPointMatch(), null, null); + } + + public boolean isBeforeAdvice() { + return true; + } + + public boolean isAfterAdvice() { + return false; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java new file mode 100644 index 0000000000..3c09f0d31d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJPointcutAdvisor.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * AspectJPointcutAdvisor that adapts an {@link AbstractAspectJAdvice} + * to the {@link org.springframework.aop.PointcutAdvisor} interface. + * + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + */ +public class AspectJPointcutAdvisor implements PointcutAdvisor, Ordered { + + private final AbstractAspectJAdvice advice; + + private final Pointcut pointcut; + + private Integer order; + + + /** + * Create a new AspectJPointcutAdvisor for the given advice + * @param advice the AbstractAspectJAdvice to wrap + */ + public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + this.pointcut = advice.buildSafePointcut(); + } + + public void setOrder(int order) { + this.order = new Integer(order); + } + + + public boolean isPerInstance() { + return true; + } + + public Advice getAdvice() { + return this.advice; + } + + public Pointcut getPointcut() { + return this.pointcut; + } + + public int getOrder() { + if (this.order != null) { + return this.order.intValue(); + } + else { + return this.advice.getOrder(); + } + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AspectJPointcutAdvisor)) { + return false; + } + AspectJPointcutAdvisor otherAdvisor = (AspectJPointcutAdvisor) other; + return (ObjectUtils.nullSafeEquals(this.advice, otherAdvisor.advice)); + } + + public int hashCode() { + return AspectJPointcutAdvisor.class.hashCode(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJPrecedenceInformation.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJPrecedenceInformation.java new file mode 100644 index 0000000000..cd90ba6d19 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJPrecedenceInformation.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2006 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.aop.aspectj; + +import org.springframework.core.Ordered; + +/** + * Interface to be implemented by types that can supply the information + * needed to sort advice/advisors by AspectJ's precedence rules. + * + * @author Adrian Colyer + * @since 2.0 + * @see org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator + */ +public interface AspectJPrecedenceInformation extends Ordered { + + // Implementation note: + // We need the level of indirection this interface provides as otherwise the + // AspectJPrecedenceComparator must ask an Advisor for its Advice in all cases + // in order to sort advisors. This causes problems with the + // InstantiationModelAwarePointcutAdvisor which needs to delay creating + // its advice for aspects with non-singleton instantiation models. + + /** + * The name of the aspect (bean) in which the advice was declared. + */ + String getAspectName(); + + /** + * The declaration order of the advice member within the aspect. + */ + int getDeclarationOrder(); + + /** + * Return whether this is a before advice. + */ + boolean isBeforeAdvice(); + + /** + * Return whether this is an after advice. + */ + boolean isAfterAdvice(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java new file mode 100644 index 0000000000..8b7128863f --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJProxyUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.util.Iterator; +import java.util.List; + +import org.springframework.aop.Advisor; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; + +/** + * Utility methods for working with AspectJ proxies. + * + * @author Rod Johnson + * @author Ramnivas Laddad + * @since 2.0 + */ +public abstract class AspectJProxyUtils { + + /** + * Add special advisors if necessary to work with a proxy chain that contains AspectJ advisors. + * This will expose the current Spring AOP invocation (necessary for some AspectJ pointcut matching) + * and make available the current AspectJ JoinPoint. The call will have no effect if there are no + * AspectJ advisors in the advisor chain. + * @param advisors Advisors available + * @return true if any special {@link Advisor Advisors} were added, otherwise false. + */ + public static boolean makeAdvisorChainAspectJCapableIfNecessary(List advisors) { + // Don't add advisors to an empty list; may indicate that proxying is just not required + if (!advisors.isEmpty()) { + boolean foundAspectJAdvice = false; + for (Iterator it = advisors.iterator(); it.hasNext() && !foundAspectJAdvice; ) { + Advisor advisor = (Advisor) it.next(); + // Be careful not to get the Advice without a guard, as + // this might eagerly instantiate a non-singleton AspectJ aspect + if (isAspectJAdvice(advisor)) { + foundAspectJAdvice = true; + } + } + if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) { + advisors.add(0, ExposeInvocationInterceptor.ADVISOR); + return true; + } + } + return false; + } + + /** + * Determine whether the given Advisor contains an AspectJ advice. + * @param advisor the Advisor to check + */ + private static boolean isAspectJAdvice(Advisor advisor) { + return (advisor instanceof InstantiationModelAwarePointcutAdvisor || + advisor.getAdvice() instanceof AbstractAspectJAdvice || + (advisor instanceof PointcutAdvisor && + ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java new file mode 100644 index 0000000000..8932259dc3 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/AspectJWeaverMessageHandler.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.bridge.AbortException; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.IMessage.Kind; +import org.aspectj.bridge.IMessageHandler; + +/** + * Implementation of AspectJ's {@link IMessageHandler} interface that + * routes AspectJ weaving messages through the same logging system as the + * regular Spring messages. + * + *

Pass the option... + * + *

-XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler + * + *

to the weaver; for example, specifying the following in a + * "META-INF/aop.xml file: + * + *

<weaver options="..."/> + * + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + */ +public class AspectJWeaverMessageHandler implements IMessageHandler { + + private static final String AJ_ID = "[AspectJ] "; + + private static final Log LOGGER = LogFactory.getLog("AspectJ Weaver"); + + + public boolean handleMessage(IMessage message) throws AbortException { + Kind messageKind = message.getKind(); + + if (LOGGER.isDebugEnabled() || LOGGER.isTraceEnabled()) { + if (messageKind == IMessage.DEBUG) { + LOGGER.debug(makeMessageFor(message)); + return true; + } + } + + if (LOGGER.isInfoEnabled()) { + if ((messageKind == IMessage.INFO) || (messageKind == IMessage.WEAVEINFO)) { + LOGGER.info(makeMessageFor(message)); + return true; + } + } + + if (LOGGER.isWarnEnabled()) { + if (messageKind == IMessage.WARNING) { + LOGGER.warn(makeMessageFor(message)); + return true; + } + } + + if (LOGGER.isErrorEnabled()) { + if (messageKind == IMessage.ERROR) { + LOGGER.error(makeMessageFor(message)); + return true; + } + } + + if (LOGGER.isFatalEnabled()) { + if (messageKind == IMessage.ABORT) { + LOGGER.fatal(makeMessageFor(message)); + return true; + } + } + + return false; + } + + private String makeMessageFor(IMessage aMessage) { + return AJ_ID + aMessage.getMessage(); + } + + public boolean isIgnoring(Kind messageKind) { + // We want to see everything, and allow configuration of log levels dynamically. + return false; + } + + public void dontIgnore(Kind messageKind) { + // We weren't ignoring anything anyway... + } + + public void ignore(Kind kind) { + // We weren't ignoring anything anyway... + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java new file mode 100644 index 0000000000..a60227a5df --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/DeclareParentsAdvisor.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.support.ClassFilters; +import org.springframework.aop.support.DelegatePerTargetObjectIntroductionInterceptor; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; + +/** + * Introduction advisor delegating to the given object. + * Implements AspectJ annotation-style behavior for the DeclareParents annotation. + * + * @author Rod Johnson + * @author Ramnivas Laddad + * @since 2.0 + */ +public class DeclareParentsAdvisor implements IntroductionAdvisor { + + private final Class introducedInterface; + + private final ClassFilter typePatternClassFilter; + + private final Advice advice; + + + /** + * Create a new advisor for this DeclareParents field. + * @param interfaceType static field defining the introduction + * @param typePattern type pattern the introduction is restricted to + * @param defaultImpl the default implementation class + */ + public DeclareParentsAdvisor(Class interfaceType, String typePattern, Class defaultImpl) { + this(interfaceType, typePattern, defaultImpl, + new DelegatePerTargetObjectIntroductionInterceptor(defaultImpl, interfaceType)); + } + + /** + * Create a new advisor for this DeclareParents field. + * @param interfaceType static field defining the introduction + * @param typePattern type pattern the introduction is restricted to + * @param delegateRef the delegate implementation object + */ + public DeclareParentsAdvisor(Class interfaceType, String typePattern, Object delegateRef) { + this(interfaceType, typePattern, delegateRef.getClass(), + new DelegatingIntroductionInterceptor(delegateRef)); + } + + /** + * Private constructor to share common code between impl-based delegate and reference-based delegate + * (cannot use method such as init() to share common code, due the the use of final fields) + * @param interfaceType static field defining the introduction + * @param typePattern type pattern the introduction is restricted to + * @param implementationClass implementation class + * @param advice delegation advice + */ + private DeclareParentsAdvisor(Class interfaceType, String typePattern, Class implementationClass, Advice advice) { + this.introducedInterface = interfaceType; + ClassFilter typePatternFilter = new TypePatternClassFilter(typePattern); + + // Excludes methods implemented. + ClassFilter exclusion = new ClassFilter() { + public boolean matches(Class clazz) { + return !(introducedInterface.isAssignableFrom(clazz)); + } + }; + + this.typePatternClassFilter = ClassFilters.intersection(typePatternFilter, exclusion); + this.advice = advice; + } + + + public ClassFilter getClassFilter() { + return this.typePatternClassFilter; + } + + public void validateInterfaces() throws IllegalArgumentException { + // Do nothing + } + + public boolean isPerInstance() { + return true; + } + + public Advice getAdvice() { + return this.advice; + } + + public Class[] getInterfaces() { + return new Class[] {this.introducedInterface}; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/InstantiationModelAwarePointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/InstantiationModelAwarePointcutAdvisor.java new file mode 100644 index 0000000000..325b04e24a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/InstantiationModelAwarePointcutAdvisor.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2006 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.aop.aspectj; + +import org.springframework.aop.PointcutAdvisor; + +/** + * Interface to be implemented by Spring AOP Advisors wrapping AspectJ + * aspects that may have a lazy initialization strategy. For example, + * a perThis instantiation model would mean lazy initialization of the advice. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public interface InstantiationModelAwarePointcutAdvisor extends PointcutAdvisor { + + /** + * Return whether this advisor is lazily initializing its underlying advice. + */ + boolean isLazy(); + + /** + * Return whether this advisor has already instantiated its advice. + */ + boolean isAdviceInstantiated(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java new file mode 100644 index 0000000000..da28648ddc --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/MethodInvocationProceedingJoinPoint.java @@ -0,0 +1,229 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import java.lang.reflect.Method; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.reflect.MethodSignature; +import org.aspectj.lang.reflect.SourceLocation; +import org.aspectj.runtime.internal.AroundClosure; + +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.util.Assert; + +/** + * Implementation of AspectJ ProceedingJoinPoint interface + * wrapping an AOP Alliance MethodInvocation. + * + *

Note: the getThis() method returns the current Spring AOP proxy. + * The getTarget() method returns the current Spring AOP target (which may be + * null if there is no target), and is a plain POJO without any advice. + * If you want to call the object and have the advice take effect, use + * getThis(). A common example is casting the object to an + * introduced interface in the implementation of an introduction. + * + *

Of course there is no such distinction between target and proxy in AspectJ. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Adrian Colyer + * @since 2.0 + */ +public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint, JoinPoint.StaticPart { + + private final ProxyMethodInvocation methodInvocation; + + private Object[] defensiveCopyOfArgs; + + /** Lazily initialized signature object */ + private Signature signature; + + /** Lazily initialized source location object */ + private SourceLocation sourceLocation; + + + /** + * Create a new MethodInvocationProceedingJoinPoint, wrapping the given + * Spring ProxyMethodInvocation object. + * @param methodInvocation the Spring ProxyMethodInvocation object + */ + public MethodInvocationProceedingJoinPoint(ProxyMethodInvocation methodInvocation) { + Assert.notNull(methodInvocation, "MethodInvocation must not be null"); + this.methodInvocation = methodInvocation; + } + + public void set$AroundClosure(AroundClosure aroundClosure) { + throw new UnsupportedOperationException(); + } + + public Object proceed() throws Throwable { + return this.methodInvocation.invocableClone().proceed(); + } + + public Object proceed(Object[] arguments) throws Throwable { + Assert.notNull(arguments, "Argument array passed to proceed cannot be null"); + if (arguments.length != this.methodInvocation.getArguments().length) { + throw new IllegalArgumentException("Expecting " + + this.methodInvocation.getArguments().length + " arguments to proceed, " + + "but was passed " + arguments.length + " arguments"); + } + this.methodInvocation.setArguments(arguments); + return this.methodInvocation.invocableClone(arguments).proceed(); + } + + /** + * Returns the Spring AOP proxy. Cannot be null. + */ + public Object getThis() { + return this.methodInvocation.getProxy(); + } + + /** + * Returns the Spring AOP target. May be null if there is no target. + */ + public Object getTarget() { + return this.methodInvocation.getThis(); + } + + public Object[] getArgs() { + if (this.defensiveCopyOfArgs == null) { + Object[] argsSource = this.methodInvocation.getArguments(); + this.defensiveCopyOfArgs = new Object[argsSource.length]; + System.arraycopy(argsSource, 0, this.defensiveCopyOfArgs, 0, argsSource.length); + } + return this.defensiveCopyOfArgs; + } + + public Signature getSignature() { + if (this.signature == null) { + this.signature = new MethodSignatureImpl(); + } + return signature; + } + + public SourceLocation getSourceLocation() { + if (this.sourceLocation == null) { + this.sourceLocation = new SourceLocationImpl(); + } + return this.sourceLocation; + } + + public String getKind() { + return ProceedingJoinPoint.METHOD_EXECUTION; + } + + public JoinPoint.StaticPart getStaticPart() { + return this; + } + + + public String toShortString() { + return "execution(" + this.methodInvocation.getMethod().getName() + ")"; + } + + public String toLongString() { + return getClass().getName() + ": execution: [" + this.methodInvocation + "]"; + } + + public String toString() { + return getClass().getName() + ": " + toShortString(); + } + + + /** + * Lazily initialized MethodSignature. + */ + private class MethodSignatureImpl implements Signature, MethodSignature { + + public String toShortString() { + return methodInvocation.getMethod().getName(); + } + + public String toLongString() { + return methodInvocation.getMethod().toString(); + } + + public String getName() { + return methodInvocation.getMethod().getName(); + } + + public int getModifiers() { + return methodInvocation.getMethod().getModifiers(); + } + + public Class getDeclaringType() { + return methodInvocation.getMethod().getDeclaringClass(); + } + + public String getDeclaringTypeName() { + return methodInvocation.getMethod().getDeclaringClass().getName(); + } + + public Class getReturnType() { + return methodInvocation.getMethod().getReturnType(); + } + + public Method getMethod() { + return methodInvocation.getMethod(); + } + + public Class[] getParameterTypes() { + return methodInvocation.getMethod().getParameterTypes(); + } + + public String[] getParameterNames() { + // TODO consider allowing use of ParameterNameDiscoverer, or tying into + // parameter names exposed for argument binding... + throw new UnsupportedOperationException( + "Parameter names cannot be determined unless compiled by AspectJ compiler"); + } + + public Class[] getExceptionTypes() { + return methodInvocation.getMethod().getExceptionTypes(); + } + } + + + /** + * Lazily initialized SourceLocation. + */ + private class SourceLocationImpl implements SourceLocation { + + public Class getWithinType() { + if (methodInvocation.getThis() == null) { + throw new UnsupportedOperationException("No source location joinpoint available: target is null"); + } + return methodInvocation.getThis().getClass(); + } + + public String getFileName() { + throw new UnsupportedOperationException(); + } + + public int getLine() { + throw new UnsupportedOperationException(); + } + + public int getColumn() { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java new file mode 100644 index 0000000000..974838675a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/RuntimeTestWalker.java @@ -0,0 +1,255 @@ +/* + * Copyright 2002-2008 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.aop.aspectj; + +import java.lang.reflect.Field; + +import org.aspectj.weaver.ResolvedType; +import org.aspectj.weaver.ast.And; +import org.aspectj.weaver.ast.Call; +import org.aspectj.weaver.ast.FieldGetCall; +import org.aspectj.weaver.ast.HasAnnotation; +import org.aspectj.weaver.ast.ITestVisitor; +import org.aspectj.weaver.ast.Instanceof; +import org.aspectj.weaver.ast.Literal; +import org.aspectj.weaver.ast.Not; +import org.aspectj.weaver.ast.Or; +import org.aspectj.weaver.ast.Test; +import org.aspectj.weaver.internal.tools.MatchingContextBasedTest; +import org.aspectj.weaver.reflect.ReflectionVar; +import org.aspectj.weaver.reflect.ShadowMatchImpl; +import org.aspectj.weaver.tools.ShadowMatch; + +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * This class encapsulates some AspectJ internal knowledge that should be + * pushed back into the AspectJ project in a future release. + * + *

It relies on implementation specific knowledge in AspectJ to break + * encapsulation and do something AspectJ was not designed to do: query + * the types of runtime tests that will be performed. The code here should + * migrate to ShadowMatch.getVariablesInvolvedInRuntimeTest() + * or some similar operation. + * + *

See . + * + * @author Adrian Colyer + * @author Ramnivas Laddad + * @since 2.0 + */ +class RuntimeTestWalker { + + private final Test runtimeTest; + + + public RuntimeTestWalker(ShadowMatch shadowMatch) { + ShadowMatchImpl shadowMatchImplementation = (ShadowMatchImpl) shadowMatch; + try { + Field testField = shadowMatchImplementation.getClass().getDeclaredField("residualTest"); + ReflectionUtils.makeAccessible(testField); + this.runtimeTest = (Test) testField.get(shadowMatch); + } + catch (NoSuchFieldException noSuchFieldEx) { + throw new IllegalStateException("The version of aspectjtools.jar / aspectjweaver.jar " + + "on the classpath is incompatible with this version of Spring: Expected field " + + "'runtimeTest' is not present on ShadowMatchImpl class."); + } + catch (IllegalAccessException illegalAccessEx) { + // Famous last words... but I don't see how this can happen given the + // makeAccessible call above + throw new IllegalStateException("Unable to access ShadowMatchImpl.runtimeTest field."); + } + } + + + /** + * If the test uses any of the this, target, at_this, at_target, and at_annotation vars, + * then it tests subtype sensitive vars. + */ + public boolean testsSubtypeSensitiveVars() { + return new SubtypeSensitiveVarTypeTestVisitor().testsSubtypeSensitiveVars(this.runtimeTest); + } + + public boolean testThisInstanceOfResidue(Class thisClass) { + return new ThisInstanceOfResidueTestVisitor(thisClass).thisInstanceOfMatches(this.runtimeTest); + } + + public boolean testTargetInstanceOfResidue(Class targetClass) { + return new TargetInstanceOfResidueTestVisitor(targetClass).targetInstanceOfMatches(this.runtimeTest); + } + + + private static class TestVisitorAdapter implements ITestVisitor { + + protected static final int THIS_VAR = 0; + protected static final int TARGET_VAR = 1; + protected static final int AT_THIS_VAR = 3; + protected static final int AT_TARGET_VAR = 4; + protected static final int AT_ANNOTATION_VAR = 8; + + public void visit(And e) { + e.getLeft().accept(this); + e.getRight().accept(this); + } + + public void visit(Or e) { + e.getLeft().accept(this); + e.getRight().accept(this); + } + + public void visit(Not e) { + e.getBody().accept(this); + } + + public void visit(Instanceof i) { + } + + public void visit(Literal literal) { + } + + public void visit(Call call) { + } + + public void visit(FieldGetCall fieldGetCall) { + } + + public void visit(HasAnnotation hasAnnotation) { + } + + public void visit(MatchingContextBasedTest matchingContextTest) { + } + + protected int getVarType(ReflectionVar v) { + try { + Field varTypeField = ReflectionVar.class.getDeclaredField("varType"); + ReflectionUtils.makeAccessible(varTypeField); + Integer varTypeValue = (Integer) varTypeField.get(v); + return varTypeValue.intValue(); + } + catch (NoSuchFieldException noSuchFieldEx) { + throw new IllegalStateException("the version of aspectjtools.jar / aspectjweaver.jar " + + "on the classpath is incompatible with this version of Spring:- expected field " + + "'varType' is not present on ReflectionVar class"); + } + catch (IllegalAccessException illegalAccessEx) { + // Famous last words... but I don't see how this can happen given the + // makeAccessible call above + throw new IllegalStateException("Unable to access ReflectionVar.varType field."); + } + } + } + + + private static abstract class InstanceOfResidueTestVisitor extends TestVisitorAdapter { + + private Class matchClass; + private boolean matches; + private int matchVarType; + + public InstanceOfResidueTestVisitor(Class matchClass, boolean defaultMatches, int matchVarType) { + this.matchClass = matchClass; + this.matches = defaultMatches; + this.matchVarType = matchVarType; + } + + public boolean instanceOfMatches(Test test) { + test.accept(this); + return matches; + } + + public void visit(Instanceof i) { + ResolvedType type = (ResolvedType) i.getType(); + int varType = getVarType((ReflectionVar) i.getVar()); + if (varType != this.matchVarType) { + return; + } + try { + Class typeClass = ClassUtils.forName(type.getName(), this.matchClass.getClassLoader()); + // Don't use ReflectionType.isAssignableFrom() as it won't be aware of (Spring) mixins + this.matches = typeClass.isAssignableFrom(this.matchClass); + } + catch (ClassNotFoundException ex) { + this.matches = false; + } + } + } + + + /** + * Check if residue of target(TYPE) kind. See SPR-3783 for more details. + */ + private static class TargetInstanceOfResidueTestVisitor extends InstanceOfResidueTestVisitor { + + public TargetInstanceOfResidueTestVisitor(Class targetClass) { + super(targetClass, false, TARGET_VAR); + } + + public boolean targetInstanceOfMatches(Test test) { + return instanceOfMatches(test); + } + } + + + /** + * Check if residue of this(TYPE) kind. See SPR-2979 for more details. + */ + private static class ThisInstanceOfResidueTestVisitor extends InstanceOfResidueTestVisitor { + + public ThisInstanceOfResidueTestVisitor(Class thisClass) { + super(thisClass, true, THIS_VAR); + } + + // TODO: Optimization: Process only if this() specifies a type and not an identifier. + public boolean thisInstanceOfMatches(Test test) { + return instanceOfMatches(test); + } + } + + + private static class SubtypeSensitiveVarTypeTestVisitor extends TestVisitorAdapter { + + private final Object thisObj = new Object(); + private final Object targetObj = new Object(); + private final Object[] argsObjs = new Object[0]; + private boolean testsSubtypeSensitiveVars = false; + + public boolean testsSubtypeSensitiveVars(Test aTest) { + aTest.accept(this); + return this.testsSubtypeSensitiveVars; + } + + public void visit(Instanceof i) { + ReflectionVar v = (ReflectionVar) i.getVar(); + Object varUnderTest = v.getBindingAtJoinPoint(thisObj,targetObj,argsObjs); + if ((varUnderTest == thisObj) || (varUnderTest == targetObj)) { + this.testsSubtypeSensitiveVars = true; + } + } + + public void visit(HasAnnotation hasAnn) { + // If you thought things were bad before, now we sink to new levels of horror... + ReflectionVar v = (ReflectionVar) hasAnn.getVar(); + int varType = getVarType(v); + if ((varType == AT_THIS_VAR) || (varType == AT_TARGET_VAR) || (varType == AT_ANNOTATION_VAR)) { + this.testsSubtypeSensitiveVars = true; + } + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java new file mode 100644 index 0000000000..99a1256571 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/SimpleAspectInstanceFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.springframework.aop.framework.AopConfigException; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +/** + * Implementation of {@link AspectInstanceFactory} that creates a new instance + * of the specified aspect class for every {@link #getAspectInstance()} call. + * + * @author Juergen Hoeller + * @since 2.0.4 + */ +public class SimpleAspectInstanceFactory implements AspectInstanceFactory { + + private final Class aspectClass; + + + /** + * Create a new SimpleAspectInstanceFactory for the given aspect class. + * @param aspectClass the aspect class + */ + public SimpleAspectInstanceFactory(Class aspectClass) { + Assert.notNull(aspectClass, "Aspect class must not be null"); + this.aspectClass = aspectClass; + } + + /** + * Return the specified aspect class (never null). + */ + public final Class getAspectClass() { + return this.aspectClass; + } + + + public final Object getAspectInstance() { + try { + return this.aspectClass.newInstance(); + } + catch (InstantiationException ex) { + throw new AopConfigException("Unable to instantiate aspect class [" + this.aspectClass.getName() + "]", ex); + } + catch (IllegalAccessException ex) { + throw new AopConfigException("Cannot access element class [" + this.aspectClass.getName() + "]", ex); + } + } + + public ClassLoader getAspectClassLoader() { + return this.aspectClass.getClassLoader(); + } + + /** + * Determine the order for this factory's aspect instance, + * either an instance-specific order expressed through implementing + * the {@link org.springframework.core.Ordered} interface, + * or a fallback order. + * @see org.springframework.core.Ordered + * @see #getOrderForAspectClass + */ + public int getOrder() { + return getOrderForAspectClass(this.aspectClass); + } + + /** + * Determine a fallback order for the case that the aspect instance + * does not express an instance-specific order through implementing + * the {@link org.springframework.core.Ordered} interface. + *

The default implementation simply returns Ordered.LOWEST_PRECEDENCE. + * @param aspectClass the aspect class + */ + protected int getOrderForAspectClass(Class aspectClass) { + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java new file mode 100644 index 0000000000..1820d5c0db --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/SingletonAspectInstanceFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2007 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.aop.aspectj; + +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +/** + * Implementation of {@link AspectInstanceFactory} that is backed by a + * specified singleton object, returning the same instance for every + * {@link #getAspectInstance()} call. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see SimpleAspectInstanceFactory + */ +public class SingletonAspectInstanceFactory implements AspectInstanceFactory { + + private final Object aspectInstance; + + + /** + * Create a new SingletonAspectInstanceFactory for the given aspect instance. + * @param aspectInstance the singleton aspect instance + */ + public SingletonAspectInstanceFactory(Object aspectInstance) { + Assert.notNull(aspectInstance, "Aspect instance must not be null"); + this.aspectInstance = aspectInstance; + } + + + public final Object getAspectInstance() { + return this.aspectInstance; + } + + public ClassLoader getAspectClassLoader() { + return this.aspectInstance.getClass().getClassLoader(); + } + + /** + * Determine the order for this factory's aspect instance, + * either an instance-specific order expressed through implementing + * the {@link org.springframework.core.Ordered} interface, + * or a fallback order. + * @see org.springframework.core.Ordered + * @see #getOrderForAspectClass + */ + public int getOrder() { + if (this.aspectInstance instanceof Ordered) { + return ((Ordered) this.aspectInstance).getOrder(); + } + return getOrderForAspectClass(this.aspectInstance.getClass()); + } + + /** + * Determine a fallback order for the case that the aspect instance + * does not express an instance-specific order through implementing + * the {@link org.springframework.core.Ordered} interface. + *

The default implementation simply returns Ordered.LOWEST_PRECEDENCE. + * @param aspectClass the aspect class + */ + protected int getOrderForAspectClass(Class aspectClass) { + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java new file mode 100644 index 0000000000..984c46f4f9 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/TypePatternClassFilter.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2006 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.aop.aspectj; + +import org.aspectj.weaver.tools.PointcutParser; +import org.aspectj.weaver.tools.TypePatternMatcher; + +import org.springframework.aop.ClassFilter; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Spring AOP {@link ClassFilter} implementation using AspectJ type matching. + * + * @author Rod Johnson + * @since 2.0 + */ +public class TypePatternClassFilter implements ClassFilter { + + private String typePattern; + + private TypePatternMatcher aspectJTypePatternMatcher; + + + /** + * Creates a new instance of the {@link TypePatternClassFilter} class. + *

This is the JavaBean constructor; be sure to set the + * {@link #setTypePattern(String) typePattern} property, else a + * no doubt fatal {@link IllegalStateException} will be thrown + * when the {@link #matches(Class)} method is first invoked. + */ + public TypePatternClassFilter() { + } + + /** + * Create a fully configured {@link TypePatternClassFilter} using the + * given type pattern. + * @param typePattern the type pattern that AspectJ weaver should parse + * @throws IllegalArgumentException if the supplied typePattern is null + * or is recognized as invalid + */ + public TypePatternClassFilter(String typePattern) { + setTypePattern(typePattern); + } + + + /** + * Set the AspectJ type pattern to match. + *

Examples include: + * + * org.springframework.beans.* + * + * This will match any class or interface in the given package. + * + * org.springframework.beans.ITestBean+ + * + * This will match the ITestBean interface and any class + * that implements it. + *

These conventions are established by AspectJ, not Spring AOP. + * @param typePattern the type pattern that AspectJ weaver should parse + * @throws IllegalArgumentException if the supplied typePattern is null + * or is recognized as invalid + */ + public void setTypePattern(String typePattern) { + Assert.notNull(typePattern); + this.typePattern = typePattern; + this.aspectJTypePatternMatcher = + PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution(). + parseTypePattern(replaceBooleanOperators(typePattern)); + } + + public String getTypePattern() { + return typePattern; + } + + /** + * Should the pointcut apply to the given interface or target class? + * @param clazz candidate target class + * @return whether the advice should apply to this candidate target class + * @throws IllegalStateException if no {@link #setTypePattern(String)} has been set + */ + public boolean matches(Class clazz) { + if (this.aspectJTypePatternMatcher == null) { + throw new IllegalStateException("No 'typePattern' has been set via ctor/setter."); + } + return this.aspectJTypePatternMatcher.matches(clazz); + } + + /** + * If a type pattern has been specified in XML, the user cannot + * write and as "&&" (though && will work). + * We also allow and between two sub-expressions. + *

This method converts back to && for the AspectJ pointcut parser. + */ + private String replaceBooleanOperators(String pcExpr) { + pcExpr = StringUtils.replace(pcExpr," and "," && "); + pcExpr = StringUtils.replace(pcExpr, " or ", " || "); + pcExpr = StringUtils.replace(pcExpr, " not ", " ! "); + return pcExpr; + } +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java new file mode 100644 index 0000000000..4a14ce17dc --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java @@ -0,0 +1,341 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.AjType; +import org.aspectj.lang.reflect.AjTypeSystem; +import org.aspectj.lang.reflect.PerClauseKind; + +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.framework.AopConfigException; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.PrioritizedParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.StringUtils; + +/** + * Abstract base class for factories that can create Spring AOP Advisors + * given AspectJ classes from classes honoring the AspectJ 5 annotation syntax. + * + *

This class handles annotation parsing and validation functionality. + * It does not actually generate Spring AOP Advisors, which is deferred to subclasses. + * + * @author Rod Johnson + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory { + + protected static final ParameterNameDiscoverer ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER = + new AspectJAnnotationParameterNameDiscoverer(); + + private static final String AJC_MAGIC = "ajc$"; + + + /** + * Find and return the first AspectJ annotation on the given method + * (there should only be one anyway...) + */ + protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method aMethod) { + Class[] classesToLookFor = (Class[]) new Class[] { + Before.class, + Around.class, + After.class, + AfterReturning.class, + AfterThrowing.class, + Pointcut.class + }; + for (Class c : classesToLookFor) { + AspectJAnnotation foundAnnotation = findAnnotation(aMethod, c); + if (foundAnnotation != null) { + return foundAnnotation; + } + } + return null; + } + + private static AspectJAnnotation findAnnotation(Method method, Class toLookFor) { + A result = AnnotationUtils.findAnnotation(method, toLookFor); + if (result != null) { + return new AspectJAnnotation(result); + } + else { + return null; + } + } + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + protected final ParameterNameDiscoverer parameterNameDiscoverer; + + + protected AbstractAspectJAdvisorFactory() { + PrioritizedParameterNameDiscoverer prioritizedParameterNameDiscoverer = new PrioritizedParameterNameDiscoverer(); + prioritizedParameterNameDiscoverer.addDiscoverer(ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER); + this.parameterNameDiscoverer = prioritizedParameterNameDiscoverer; + } + + /** + * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system + * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test + * is that aspects written in the code-style (AspectJ language) also have the annotation present + * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP. + */ + public boolean isAspect(Class clazz) { + return (AjTypeSystem.getAjType(clazz).isAspect() && + hasAspectAnnotation(clazz) && !compiledByAjc(clazz)); + } + + private boolean hasAspectAnnotation(Class clazz) { + return clazz.isAnnotationPresent(Aspect.class); + } + + /** + * We need to detect this as "code-style" AspectJ aspects should not be + * interpreted by Spring AOP. + */ + private boolean compiledByAjc(Class clazz) { + // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and + // annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on + // an implementation detail of the AspectJ compiler. + for (Field field : clazz.getDeclaredFields()) { + if (field.getName().startsWith(AJC_MAGIC)) { + return true; + } + } + return false; + } + + public void validate(Class aspectClass) throws AopConfigException { + // If the parent has the annotation and isn't abstract it's an error + if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null && + !Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) { + throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" + + aspectClass.getSuperclass().getName() + "]"); + } + + AjType ajType = AjTypeSystem.getAjType(aspectClass); + if (!ajType.isAspect()) { + throw new NotAnAtAspectException(aspectClass); + } + if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) { + throw new AopConfigException(aspectClass.getName() + " uses percflow instantiation model: " + + "This is not supported in Spring AOP."); + } + if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) { + throw new AopConfigException(aspectClass.getName() + " uses percflowbelow instantiation model: " + + "This is not supported in Spring AOP."); + } + } + + /** + * The pointcut and advice annotations both have an "argNames" member which contains a + * comma-separated list of the argument names. We use this (if non-empty) to build the + * formal parameters for the pointcut. + */ + protected AspectJExpressionPointcut createPointcutExpression( + Method annotatedMethod, Class declarationScope, String[] pointcutParameterNames) { + + Class [] pointcutParameterTypes = new Class[0]; + if (pointcutParameterNames != null) { + pointcutParameterTypes = extractPointcutParameterTypes(pointcutParameterNames,annotatedMethod); + } + + AspectJExpressionPointcut ajexp = + new AspectJExpressionPointcut(declarationScope,pointcutParameterNames,pointcutParameterTypes); + ajexp.setLocation(annotatedMethod.toString()); + return ajexp; + } + + /** + * Create the pointcut parameters needed by aspectj based on the given argument names + * and the argument types that are available from the adviceMethod. Needs to take into + * account (ignore) any JoinPoint based arguments as these are not pointcut context but + * rather part of the advice execution context (thisJoinPoint, thisJoinPointStaticPart) + */ + private Class[] extractPointcutParameterTypes(String[] argNames, Method adviceMethod) { + Class[] ret = new Class[argNames.length]; + Class[] paramTypes = adviceMethod.getParameterTypes(); + if (argNames.length > paramTypes.length) { + throw new IllegalStateException("Expecting at least " + argNames.length + + " arguments in the advice declaration, but only found " + paramTypes.length); + } + // Make the simplifying assumption for now that all of the JoinPoint based arguments + // come first in the advice declaration. + int typeOffset = paramTypes.length - argNames.length; + for (int i = 0; i < ret.length; i++) { + ret[i] = paramTypes[i+typeOffset]; + } + return ret; + } + + + protected enum AspectJAnnotationType { + AtPointcut, + AtBefore, + AtAfter, + AtAfterReturning, + AtAfterThrowing, + AtAround + }; + + + /** + * Class modelling an AspectJ annotation, exposing its type enumeration and + * pointcut String. + */ + protected static class AspectJAnnotation { + + private static Map annotationTypes = new HashMap(); + + private static final String[] EXPRESSION_PROPERTIES = new String[]{"value", "pointcut"}; + + static { + annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut); + annotationTypes.put(After.class,AspectJAnnotationType.AtAfter); + annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning); + annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing); + annotationTypes.put(Around.class,AspectJAnnotationType.AtAround); + annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore); + } + + private final A annotation; + private AspectJAnnotationType annotationType; + private final String expression; + private final String argNames; + + public AspectJAnnotation(A aspectjAnnotation) { + this.annotation = aspectjAnnotation; + for (Class type : annotationTypes.keySet()) { + if (type.isInstance(this.annotation)) { + this.annotationType = annotationTypes.get(type); + break; + } + } + if (this.annotationType == null) { + throw new IllegalStateException("Unknown annotation type: " + this.annotation.toString()); + } + + // We know these methods exist with the same name on each object, + // but need to invoke them reflectively as there isn't a common interface. + try { + this.expression = resolveExpression(); + this.argNames = (String) + this.annotation.getClass().getMethod("argNames", (Class[]) null).invoke(this.annotation); + } + catch (Exception ex) { + throw new IllegalArgumentException(aspectjAnnotation + " cannot be an AspectJ annotation", ex); + } + } + + private String resolveExpression() throws Exception { + String expression = null; + for (int i = 0; i < EXPRESSION_PROPERTIES.length; i++) { + String methodName = EXPRESSION_PROPERTIES[i]; + Method method; + try { + method = this.annotation.getClass().getDeclaredMethod(methodName); + } + catch (NoSuchMethodException ex) { + method = null; + } + if (method != null) { + String candidate = (String) method.invoke(this.annotation); + if (StringUtils.hasText(candidate)) { + expression = candidate; + } + } + } + return expression; + } + + public AspectJAnnotationType getAnnotationType() { + return this.annotationType; + } + + public A getAnnotation() { + return this.annotation; + } + + public String getPointcutExpression() { + return this.expression; + } + + public String getArgNames() { + return this.argNames; + } + + public String toString() { + return this.annotation.toString(); + } + } + + + /** + * ParameterNameDiscoverer implementation that analyzes the arg names + * specified at the AspectJ annotation level. + */ + private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer { + + public String[] getParameterNames(Method method) { + if (method.getParameterTypes().length == 0) { + return new String[0]; + } + AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method); + if (annotation == null) { + return null; + } + StringTokenizer strTok = new StringTokenizer(annotation.getArgNames(), ","); + if (strTok.countTokens() > 0) { + String[] names = new String[strTok.countTokens()]; + for (int i = 0; i < names.length; i++) { + names[i] = strTok.nextToken(); + } + return names; + } + else { + return null; + } + } + + public String[] getParameterNames(Constructor ctor) { + throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice"); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java new file mode 100644 index 0000000000..e11ba99122 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.Assert; + +/** + * {@link AspectJAwareAdvisorAutoProxyCreator} subclass that processes all AspectJ + * annotation aspects in the current application context, as well as Spring Advisors. + * + *

Any AspectJ annotated classes will automatically be recognized, and their + * advice applied if Spring AOP's proxy-based model is capable of applying it. + * This covers method execution joinpoints. + * + *

If the <aop:include> element is used, only @AspectJ beans with names matched by + * an include pattern will be considered as defining aspects to use for Spring auto-proxying. + * + *

Processing of Spring Advisors follows the rules established in + * {@link org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.aop.aspectj.annotation.AspectJAdvisorFactory + */ +public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator { + + private List includePatterns; + + private AspectJAdvisorFactory aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(); + + private BeanFactoryAspectJAdvisorsBuilder aspectJAdvisorsBuilder; + + + /** + * Set a list of regex patterns, matching eligible @AspectJ bean names. + *

Default is to consider all @AspectJ beans as eligible. + */ + public void setIncludePatterns(List patterns) { + this.includePatterns = new ArrayList(patterns.size()); + for (String patternText : patterns) { + this.includePatterns.add(Pattern.compile(patternText)); + } + } + + public void setAspectJAdvisorFactory(AspectJAdvisorFactory aspectJAdvisorFactory) { + Assert.notNull(this.aspectJAdvisorFactory, "AspectJAdvisorFactory must not be null"); + this.aspectJAdvisorFactory = aspectJAdvisorFactory; + } + + @Override + protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.initBeanFactory(beanFactory); + this.aspectJAdvisorsBuilder = + new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory); + } + + + @Override + protected List findCandidateAdvisors() { + // Add all the Spring advisors found according to superclass rules. + List advisors = super.findCandidateAdvisors(); + // Build Advisors for all AspectJ aspects in the bean factory. + advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); + return advisors; + } + + protected boolean isInfrastructureClass(Class beanClass) { + // Previously we setProxyTargetClass(true) in the constructor, but that has too + // broad an impact. Instead we now override isInfrastructureClass to avoid proxying + // aspects. I'm not entirely happy with that as there is no good reason not + // to advise aspects, except that it causes advice invocation to go through a + // proxy, and if the aspect implements e.g the Ordered interface it will be + // proxied by that interface and fail at runtime as the advice method is not + // defined on the interface. We could potentially relax the restriction about + // not advising aspects in the future. + return (super.isInfrastructureClass(beanClass) || this.aspectJAdvisorFactory.isAspect(beanClass)); + } + + /** + * Check whether the given aspect bean is eligible for auto-proxying. + *

If no <aop:include> elements were used then "includePatterns" will be + * null and all beans are included. If "includePatterns" is non-null, + * then one of the patterns must match. + */ + protected boolean isEligibleAspectBean(String beanName) { + if (this.includePatterns == null) { + return true; + } + else { + for (Pattern pattern : this.includePatterns) { + if (pattern.matcher(beanName).matches()) { + return true; + } + } + return false; + } + } + + + /** + * Subclass of BeanFactoryAspectJAdvisorsBuilderAdapter that delegates to + * surrounding AnnotationAwareAspectJAutoProxyCreator facilities. + */ + private class BeanFactoryAspectJAdvisorsBuilderAdapter extends BeanFactoryAspectJAdvisorsBuilder { + + public BeanFactoryAspectJAdvisorsBuilderAdapter( + ListableBeanFactory beanFactory, AspectJAdvisorFactory advisorFactory) { + super(beanFactory, advisorFactory); + } + + protected boolean isEligibleBean(String beanName) { + return AnnotationAwareAspectJAutoProxyCreator.this.isEligibleAspectBean(beanName); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorFactory.java new file mode 100644 index 0000000000..d6141af1ab --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorFactory.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import java.lang.reflect.Method; +import java.util.List; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.framework.AopConfigException; + +/** + * Interface for factories that can create Spring AOP Advisors from classes + * annotated with AspectJ annotation syntax. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see AspectMetadata + * @see org.aspectj.lang.reflect.AjTypeSystem + */ +public interface AspectJAdvisorFactory { + + /** + * Determine whether or not the given class is an aspect, as reported + * by AspectJ's {@link org.aspectj.lang.reflect.AjTypeSystem}. + *

Will simply return false if the supposed aspect is + * invalid (such as an extension of a concrete aspect class). + * Will return true for some aspects that Spring AOP cannot process, + * such as those with unsupported instantiation models. + * Use the {@link #validate} method to handle these cases if necessary. + * @param clazz the supposed annotation-style AspectJ class + * @return whether or not this class is recognized by AspectJ as an aspect class + */ + boolean isAspect(Class clazz); + + /** + * Is the given class a valid AspectJ aspect class? + * @param aspectClass the supposed AspectJ annotation-style class to validate + * @throws AopConfigException if the class is an invalid aspect + * (which can never be legal) + * @throws NotAnAtAspectException if the class is not an aspect at all + * (which may or may not be legal, depending on the context) + */ + void validate(Class aspectClass) throws AopConfigException; + + /** + * Build Spring AOP Advisors for all annotated At-AspectJ methods + * on the specified aspect instance. + * @param aif the aspect instance factory (not the aspect instance itself + * in order to avoid eager instantiation) + * @return a list of advisors for this class + */ + List getAdvisors(MetadataAwareAspectInstanceFactory aif); + + /** + * Build a Spring AOP Advisor for the given AspectJ advice method. + * @param candidateAdviceMethod the candidate advice method + * @param aif the aspect instance factory + * @param declarationOrderInAspect the declaration order within the aspect + * @param aspectName the name of the aspect + * @return null if the method is not an AspectJ advice method + * or if it is a pointcut that will be used by other advice but will not + * create a Spring advice in its own right + */ + Advisor getAdvisor(Method candidateAdviceMethod, + MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName); + + /** + * Build a Spring AOP Advice for the given AspectJ advice method. + * @param candidateAdviceMethod the candidate advice method + * @param pointcut the corresponding AspectJ expression pointcut + * @param aif the aspect instance factory + * @param declarationOrderInAspect the declaration order within the aspect + * @param aspectName the name of the aspect + * @return null if the method is not an AspectJ advice method + * or if it is a pointcut that will be used by other advice but will not + * create a Spring advice in its own right + * @see org.springframework.aop.aspectj.AspectJAroundAdvice + * @see org.springframework.aop.aspectj.AspectJMethodBeforeAdvice + * @see org.springframework.aop.aspectj.AspectJAfterAdvice + * @see org.springframework.aop.aspectj.AspectJAfterReturningAdvice + * @see org.springframework.aop.aspectj.AspectJAfterThrowingAdvice + */ + Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut pointcut, + MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java new file mode 100644 index 0000000000..341e3aff70 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJProxyFactory.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.aspectj.lang.reflect.PerClauseKind; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.AspectJProxyUtils; +import org.springframework.aop.framework.AopConfigException; +import org.springframework.aop.framework.ProxyCreatorSupport; +import org.springframework.aop.support.AopUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * AspectJ-based proxy factory, allowing for programmatic building + * of proxies which include AspectJ aspects (code style as well + * Java 5 annotation style). + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.0 + * @see #addAspect(Object) + * @see #addAspect(Class) + * @see #getProxy() + * @see #getProxy(ClassLoader) + * @see org.springframework.aop.framework.ProxyFactory + */ +public class AspectJProxyFactory extends ProxyCreatorSupport { + + /** Cache for singleton aspect instances */ + private static final Map aspectCache = new HashMap(); + + private final AspectJAdvisorFactory aspectFactory = new ReflectiveAspectJAdvisorFactory(); + + + /** + * Create a new AspectJProxyFactory. + */ + public AspectJProxyFactory() { + } + + /** + * Create a new AspectJProxyFactory. + *

Will proxy all interfaces that the given target implements. + * @param target the target object to be proxied + */ + public AspectJProxyFactory(Object target) { + Assert.notNull(target, "Target object must not be null"); + setInterfaces(ClassUtils.getAllInterfaces(target)); + setTarget(target); + } + + /** + * Create a new AspectJProxyFactory. + * No target, only interfaces. Must add interceptors. + */ + public AspectJProxyFactory(Class[] interfaces) { + setInterfaces(interfaces); + } + + + /** + * Add the supplied aspect instance to the chain. The type of the aspect instance + * supplied must be a singleton aspect. True singleton lifecycle is not honoured when + * using this method - the caller is responsible for managing the lifecycle of any + * aspects added in this way. + * @param aspectInstance the AspectJ aspect instance + */ + public void addAspect(Object aspectInstance) { + Class aspectClass = aspectInstance.getClass(); + String aspectName = aspectClass.getName(); + AspectMetadata am = createAspectMetadata(aspectClass, aspectName); + if (am.getAjType().getPerClause().getKind() != PerClauseKind.SINGLETON) { + throw new IllegalArgumentException( + "Aspect class [" + aspectClass.getName() + "] does not define a singleton aspect"); + } + addAdvisorsFromAspectInstanceFactory( + new SingletonMetadataAwareAspectInstanceFactory(aspectInstance, aspectName)); + } + + /** + * Add an aspect of the supplied type to the end of the advice chain. + * @param aspectClass the AspectJ aspect class + */ + public void addAspect(Class aspectClass) { + String aspectName = aspectClass.getName(); + AspectMetadata am = createAspectMetadata(aspectClass, aspectName); + MetadataAwareAspectInstanceFactory instanceFactory = createAspectInstanceFactory(am, aspectClass, aspectName); + addAdvisorsFromAspectInstanceFactory(instanceFactory); + } + + + /** + * Add all {@link Advisor Advisors} from the supplied {@link MetadataAwareAspectInstanceFactory} + * to the current chain. Exposes any special purpose {@link Advisor Advisors} if needed. + * @see #makeAdvisorChainAspectJCapableIfNecessary() + */ + private void addAdvisorsFromAspectInstanceFactory(MetadataAwareAspectInstanceFactory instanceFactory) { + List advisors = this.aspectFactory.getAdvisors(instanceFactory); + advisors = AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass()); + addAllAdvisors((Advisor[]) advisors.toArray(new Advisor[advisors.size()])); + makeAdvisorChainAspectJCapableIfNecessary(); + } + + /** + * Create an {@link AspectMetadata} instance for the supplied aspect type. + */ + private AspectMetadata createAspectMetadata(Class aspectClass, String aspectName) { + AspectMetadata am = new AspectMetadata(aspectClass, aspectName); + if (!am.getAjType().isAspect()) { + throw new IllegalArgumentException("Class [" + aspectClass.getName() + "] is not a valid aspect type"); + } + return am; + } + + /** + * Create a {@link MetadataAwareAspectInstanceFactory} for the supplied aspect type. If the aspect type + * has no per clause, then a {@link SingletonMetadataAwareAspectInstanceFactory} is returned, otherwise + * a {@link PrototypeAspectInstanceFactory} is returned. + */ + private MetadataAwareAspectInstanceFactory createAspectInstanceFactory( + AspectMetadata am, Class aspectClass, String aspectName) { + + MetadataAwareAspectInstanceFactory instanceFactory = null; + if (am.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { + // Create a shared aspect instance. + Object instance = getSingletonAspectInstance(aspectClass); + instanceFactory = new SingletonMetadataAwareAspectInstanceFactory(instance, aspectName); + } + else { + // Create a factory for independent aspect instances. + instanceFactory = new SimpleMetadataAwareAspectInstanceFactory(aspectClass, aspectName); + } + return instanceFactory; + } + + /** + * Add any special-purpose {@link Advisor Advisors} needed for AspectJ support + * to the chain. {@link #updateAdvisorArray() Updates} the {@link Advisor} array + * and fires {@link #adviceChanged events}. + */ + private void makeAdvisorChainAspectJCapableIfNecessary() { + if (AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(getAdvisorsInternal())) { + updateAdvisorArray(); + adviceChanged(); + } + } + + /** + * Get the singleton aspect instance for the supplied aspect type. An instance + * is created if one cannot be found in the instance cache. + */ + private Object getSingletonAspectInstance(Class aspectClass) { + synchronized (aspectCache) { + Object instance = aspectCache.get(aspectClass); + if (instance != null) { + return instance; + } + try { + instance = aspectClass.newInstance(); + aspectCache.put(aspectClass, instance); + return instance; + } + catch (InstantiationException ex) { + throw new AopConfigException("Unable to instantiate aspect class [" + aspectClass.getName() + "]", ex); + } + catch (IllegalAccessException ex) { + throw new AopConfigException("Cannot access aspect class [" + aspectClass.getName() + "]", ex); + } + } + } + + + /** + * Create a new proxy according to the settings in this factory. + *

Can be called repeatedly. Effect will vary if we've added + * or removed interfaces. Can add and remove interceptors. + *

Uses a default class loader: Usually, the thread context class loader + * (if necessary for proxy creation). + * @return the new proxy + */ + public T getProxy() { + return (T) createAopProxy().getProxy(); + } + + /** + * Create a new proxy according to the settings in this factory. + *

Can be called repeatedly. Effect will vary if we've added + * or removed interfaces. Can add and remove interceptors. + *

Uses the given class loader (if necessary for proxy creation). + * @param classLoader the class loader to create the proxy with + * @return the new proxy + */ + public T getProxy(ClassLoader classLoader) { + return (T) createAopProxy().getProxy(classLoader); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java new file mode 100644 index 0000000000..c239aac599 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectMetadata.java @@ -0,0 +1,166 @@ +/* + * Copyright 2002-2006 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.aop.aspectj.annotation; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.AjType; +import org.aspectj.lang.reflect.AjTypeSystem; +import org.aspectj.lang.reflect.PerClauseKind; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.aspectj.TypePatternClassFilter; +import org.springframework.aop.framework.AopConfigException; +import org.springframework.aop.support.ComposablePointcut; + +/** + * Metadata for an AspectJ aspect class, with an additional Spring AOP pointcut + * for the per clause. + * + *

Uses AspectJ 5 AJType reflection API, so is only supported on Java 5. + * Enables us to work with different AspectJ instantiation models such as + * "singleton", "pertarget" and "perthis". + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.aop.aspectj.AspectJExpressionPointcut + */ +public class AspectMetadata { + + /** + * AspectJ reflection information (AspectJ 5 / Java 5 specific). + */ + private final AjType ajType; + + /** + * Spring AOP pointcut corresponding to the per clause of the + * aspect. Will be the Pointcut.TRUE canonical instance in the + * case of a singleton, otherwise an AspectJExpressionPointcut. + */ + private final Pointcut perClausePointcut; + + /** + * The name of this aspect as defined to Spring (the bean name) - + * allows us to determine if two pieces of advice come from the + * same aspect and hence their relative precedence. + */ + private String aspectName; + + + /** + * Create a new AspectMetadata instance for the given aspect class. + * @param aspectClass the aspect class + * @param aspectName the name of the aspect + */ + public AspectMetadata(Class aspectClass, String aspectName) { + this.aspectName = aspectName; + this.ajType = AjTypeSystem.getAjType(aspectClass); + + if (!this.ajType.isAspect()) { + throw new IllegalArgumentException("Class '" + aspectClass.getName() + "' is not an @AspectJ aspect"); + } + if (this.ajType.getDeclarePrecedence().length > 0) { + throw new IllegalArgumentException("DeclarePrecendence not presently supported in Spring AOP"); + } + + switch (this.ajType.getPerClause().getKind()) { + case SINGLETON : + this.perClausePointcut = Pointcut.TRUE; + return; + case PERTARGET : case PERTHIS : + AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(); + ajexp.setLocation("@Aspect annotation on " + aspectClass.getName()); + ajexp.setExpression(findPerClause(aspectClass)); + this.perClausePointcut = ajexp; + return; + case PERTYPEWITHIN : + // Works with a type pattern + this.perClausePointcut = new ComposablePointcut(new TypePatternClassFilter(findPerClause(aspectClass))); + return; + default : + throw new AopConfigException( + "PerClause " + ajType.getPerClause().getKind() + " not supported by Spring AOP for " + aspectClass); + } + } + + /** + * Extract contents from String of form pertarget(contents). + */ + private String findPerClause(Class aspectClass) { + // TODO when AspectJ provides this, we can remove this hack. Hence we don't + // bother to make it elegant. Or efficient. Or robust :-) + String str = aspectClass.getAnnotation(Aspect.class).value(); + str = str.substring(str.indexOf("(") + 1); + str = str.substring(0, str.length() - 1); + return str; + } + + + /** + * Return AspectJ reflection information. + */ + public AjType getAjType() { + return this.ajType; + } + + /** + * Return the aspect class. + */ + public Class getAspectClass() { + return this.ajType.getJavaClass(); + } + + /** + * Return the aspect class. + */ + public String getAspectName() { + return this.aspectName; + } + + /** + * Return a Spring pointcut expression for a singleton aspect. + * (e.g. Pointcut.TRUE if it's a singleton). + */ + public Pointcut getPerClausePointcut() { + return this.perClausePointcut; + } + + /** + * Return whether the aspect is defined as "perthis" or "pertarget". + */ + public boolean isPerThisOrPerTarget() { + PerClauseKind kind = getAjType().getPerClause().getKind(); + return (kind == PerClauseKind.PERTARGET || kind == PerClauseKind.PERTHIS); + } + + /** + * Return whether the aspect is defined as "pertypewithin". + */ + public boolean isPerTypeWithin() { + PerClauseKind kind = getAjType().getPerClause().getKind(); + return (kind == PerClauseKind.PERTYPEWITHIN); + } + + /** + * Return whether the aspect needs to be lazily instantiated. + */ + public boolean isLazilyInstantiated() { + return (isPerThisOrPerTarget() || isPerTypeWithin()); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java new file mode 100644 index 0000000000..3b9cfd7279 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.util.ClassUtils; + +/** + * AspectInstanceFactory backed by a Spring + * {@link org.springframework.beans.factory.BeanFactory}. + * + *

Note that this may instantiate multiple times if using a prototype, + * which probably won't give the semantics you expect. + * Use a {@link LazySingletonAspectInstanceFactoryDecorator} + * to wrap this to ensure only one new aspect comes back. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory + * @see LazySingletonAspectInstanceFactoryDecorator + */ +public class BeanFactoryAspectInstanceFactory implements MetadataAwareAspectInstanceFactory { + + private final BeanFactory beanFactory; + + private final String name; + + private final AspectMetadata aspectMetadata; + + + /** + * Create a BeanFactoryAspectInstanceFactory. AspectJ will be called to + * introspect to create AJType metadata using the type returned for the + * given bean name from the BeanFactory. + * @param beanFactory BeanFactory to obtain instance(s) from + * @param name name of the bean + */ + public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name) { + this(beanFactory, name, beanFactory.getType(name)); + } + + /** + * Create a BeanFactoryAspectInstanceFactory, providing a type that AspectJ should + * introspect to create AJType metadata. Use if the BeanFactory may consider the type + * to be a subclass (as when using CGLIB), and the information should relate to a superclass. + * @param beanFactory BeanFactory to obtain instance(s) from + * @param name the name of the bean + * @param type the type that should be introspected by AspectJ + */ + public BeanFactoryAspectInstanceFactory(BeanFactory beanFactory, String name, Class type) { + this.beanFactory = beanFactory; + this.name = name; + this.aspectMetadata = new AspectMetadata(type, name); + } + + + public Object getAspectInstance() { + return this.beanFactory.getBean(this.name); + } + + public ClassLoader getAspectClassLoader() { + if (this.beanFactory instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); + } + else { + return ClassUtils.getDefaultClassLoader(); + } + } + + public AspectMetadata getAspectMetadata() { + return this.aspectMetadata; + } + + /** + * Determine the order for this factory's target aspect, either + * an instance-specific order expressed through implementing the + * {@link org.springframework.core.Ordered} interface (only + * checked for singleton beans), or an order expressed through the + * {@link org.springframework.core.annotation.Order} annotation + * at the class level. + * @see org.springframework.core.Ordered + * @see org.springframework.core.annotation.Order + */ + public int getOrder() { + Class type = this.beanFactory.getType(this.name); + if (type != null) { + if (Ordered.class.isAssignableFrom(type) && this.beanFactory.isSingleton(this.name)) { + return ((Ordered) this.beanFactory.getBean(this.name)).getOrder(); + } + Order order = (Order) type.getAnnotation(Order.class); + if (order != null) { + return order.value(); + } + } + return Ordered.LOWEST_PRECEDENCE; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + ": bean name '" + this.name + "'"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java new file mode 100644 index 0000000000..7231c9fd1c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.aspectj.lang.reflect.PerClauseKind; + +import org.springframework.aop.Advisor; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.util.Assert; + +/** + * Helper for retrieving @AspectJ beans from a BeanFactory and building + * Spring Advisors based on them, for use with auto-proxying. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see AnnotationAwareAspectJAutoProxyCreator + */ +public class BeanFactoryAspectJAdvisorsBuilder { + + private final ListableBeanFactory beanFactory; + + private final AspectJAdvisorFactory advisorFactory; + + private List aspectBeanNames; + + private final Map> advisorsCache = new HashMap>(); + + private final Map aspectFactoryCache = + new HashMap(); + + + /** + * Create a new BeanFactoryAspectJAdvisorsBuilder for the given BeanFactory. + * @param beanFactory the ListableBeanFactory to scan + */ + public BeanFactoryAspectJAdvisorsBuilder(ListableBeanFactory beanFactory) { + this(beanFactory, new ReflectiveAspectJAdvisorFactory()); + } + + /** + * Create a new BeanFactoryAspectJAdvisorsBuilder for the given BeanFactory. + * @param beanFactory the ListableBeanFactory to scan + * @param advisorFactory the AspectJAdvisorFactory to build each Advisor with + */ + public BeanFactoryAspectJAdvisorsBuilder(ListableBeanFactory beanFactory, AspectJAdvisorFactory advisorFactory) { + Assert.notNull(beanFactory, "ListableBeanFactory must not be null"); + Assert.notNull(advisorFactory, "AspectJAdvisorFactory must not be null"); + this.beanFactory = beanFactory; + this.advisorFactory = advisorFactory; + } + + + /** + * Look for AspectJ-annotated aspect beans in the current bean factory, + * and return to a list of Spring AOP Advisors representing them. + *

Creates a Spring Advisor for each AspectJ advice method. + * @return the list of {@link org.springframework.aop.Advisor} beans + * @see #isEligibleBean + */ + public List buildAspectJAdvisors() { + List aspectNames = null; + + synchronized (this) { + aspectNames = this.aspectBeanNames; + if (aspectNames == null) { + List advisors = new LinkedList(); + aspectNames = new LinkedList(); + String[] beanNames = + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false); + for (String beanName : beanNames) { + if (!isEligibleBean(beanName)) { + continue; + } + // We must be careful not to instantiate beans eagerly as in this + // case they would be cached by the Spring container but would not + // have been weaved + Class beanType = this.beanFactory.getType(beanName); + if (beanType == null) { + continue; + } + if (this.advisorFactory.isAspect(beanType)) { + aspectNames.add(beanName); + AspectMetadata amd = new AspectMetadata(beanType, beanName); + if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { + MetadataAwareAspectInstanceFactory factory = + new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); + List classAdvisors = this.advisorFactory.getAdvisors(factory); + if (this.beanFactory.isSingleton(beanName)) { + this.advisorsCache.put(beanName, classAdvisors); + } + else { + this.aspectFactoryCache.put(beanName, factory); + } + advisors.addAll(classAdvisors); + } + else { + // Per target or per this. + if (this.beanFactory.isSingleton(beanName)) { + throw new IllegalArgumentException("Bean with name '" + beanName + + "' is a singleton, but aspect instantiation model is not singleton"); + } + MetadataAwareAspectInstanceFactory factory = + new PrototypeAspectInstanceFactory(this.beanFactory, beanName); + this.aspectFactoryCache.put(beanName, factory); + advisors.addAll(this.advisorFactory.getAdvisors(factory)); + } + } + } + this.aspectBeanNames = aspectNames; + return advisors; + } + } + + if (aspectNames.isEmpty()) { + return Collections.EMPTY_LIST; + } + List advisors = new LinkedList(); + for (Iterator it = aspectNames.iterator(); it.hasNext();) { + String aspectName = (String) it.next(); + List cachedAdvisors = this.advisorsCache.get(aspectName); + if (cachedAdvisors != null) { + advisors.addAll(cachedAdvisors); + } + else { + MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); + advisors.addAll(this.advisorFactory.getAdvisors(factory)); + } + } + return advisors; + } + + /** + * Return whether the aspect bean with the given name is eligible. + * @param beanName the name of the aspect bean + * @return whether the bean is eligible + */ + protected boolean isEligibleBean(String beanName) { + return true; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java new file mode 100644 index 0000000000..5bce339063 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/InstantiationModelAwarePointcutAdvisorImpl.java @@ -0,0 +1,263 @@ +/* + * Copyright 2002-2006 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.aop.aspectj.annotation; + +import java.lang.reflect.Method; + +import org.aopalliance.aop.Advice; +import org.aspectj.lang.reflect.PerClauseKind; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.aspectj.AspectJPrecedenceInformation; +import org.springframework.aop.aspectj.InstantiationModelAwarePointcutAdvisor; +import org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory.AspectJAnnotation; +import org.springframework.aop.support.DynamicMethodMatcherPointcut; +import org.springframework.aop.support.Pointcuts; + +/** + * Internal implementation of AspectJPointcutAdvisor. + * Note that there will be one instance of this advisor for each target method. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +class InstantiationModelAwarePointcutAdvisorImpl + implements InstantiationModelAwarePointcutAdvisor, AspectJPrecedenceInformation { + + private final AspectJExpressionPointcut declaredPointcut; + + private Pointcut pointcut; + + private final MetadataAwareAspectInstanceFactory aspectInstanceFactory; + + private final Method method; + + private final boolean lazy; + + private final AspectJAdvisorFactory atAspectJAdvisorFactory; + + private Advice instantiatedAdvice; + + private int declarationOrder; + + private String aspectName; + + private Boolean isBeforeAdvice = null; + + private Boolean isAfterAdvice = null; + + + public InstantiationModelAwarePointcutAdvisorImpl( + AspectJAdvisorFactory af, + AspectJExpressionPointcut ajexp, + MetadataAwareAspectInstanceFactory aif, + Method method, + int declarationOrderInAspect, + String aspectName) { + + this.declaredPointcut = ajexp; + this.method = method; + this.atAspectJAdvisorFactory = af; + this.aspectInstanceFactory = aif; + this.declarationOrder = declarationOrderInAspect; + this.aspectName = aspectName; + + if (aif.getAspectMetadata().isLazilyInstantiated()) { + // Static part of the pointcut is a lazy type. + Pointcut preInstantiationPointcut = + Pointcuts.union(aif.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut); + + // Make it dynamic: must mutate from pre-instantiation to post-instantiation state. + // If it's not a dynamic pointcut, it may be optimized out + // by the Spring AOP infrastructure after the first evaluation. + this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aif); + this.lazy = true; + } + else { + // A singleton aspect. + this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); + this.pointcut = declaredPointcut; + this.lazy = false; + } + } + + + /** + * The pointcut for Spring AOP to use. Actual behaviour of the pointcut will change + * depending on the state of the advice. + */ + public Pointcut getPointcut() { + return this.pointcut; + } + + /** + * This is only of interest for Spring AOP: AspectJ instantiation semantics + * are much richer. In AspectJ terminology, all a return of true + * means here is that the aspect is not a SINGLETON. + */ + public boolean isPerInstance() { + return (getAspectMetadata().getAjType().getPerClause().getKind() != PerClauseKind.SINGLETON); + } + + /** + * Return the AspectJ AspectMetadata for this advisor. + */ + public AspectMetadata getAspectMetadata() { + return this.aspectInstanceFactory.getAspectMetadata(); + } + + /** + * Lazily instantiate advice if necessary. + */ + public synchronized Advice getAdvice() { + if (this.instantiatedAdvice == null) { + this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut); + } + return this.instantiatedAdvice; + } + + public boolean isLazy() { + return this.lazy; + } + + public synchronized boolean isAdviceInstantiated() { + return (this.instantiatedAdvice != null); + } + + + private Advice instantiateAdvice(AspectJExpressionPointcut pcut) { + return this.atAspectJAdvisorFactory.getAdvice( + this.method, pcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName); + } + + public MetadataAwareAspectInstanceFactory getAspectInstanceFactory() { + return this.aspectInstanceFactory; + } + + public AspectJExpressionPointcut getDeclaredPointcut() { + return this.declaredPointcut; + } + + public int getOrder() { + return this.aspectInstanceFactory.getOrder(); + } + + public String getAspectName() { + return this.aspectName; + } + + public int getDeclarationOrder() { + return this.declarationOrder; + } + + public boolean isBeforeAdvice() { + if (this.isBeforeAdvice == null) { + determineAdviceType(); + } + return this.isBeforeAdvice; + } + + public boolean isAfterAdvice() { + if (this.isAfterAdvice == null) { + determineAdviceType(); + } + return this.isAfterAdvice; + } + + /** + * Duplicates some logic from getAdvice, but importantly does not force + * creation of the advice. + */ + private void determineAdviceType() { + AspectJAnnotation aspectJAnnotation = + AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(this.method); + if (aspectJAnnotation == null) { + this.isBeforeAdvice = false; + this.isAfterAdvice = false; + } + else { + switch (aspectJAnnotation.getAnnotationType()) { + case AtAfter: + case AtAfterReturning: + case AtAfterThrowing: + this.isAfterAdvice = true; + this.isBeforeAdvice = false; + break; + case AtAround: + case AtPointcut: + this.isAfterAdvice = false; + this.isBeforeAdvice = false; + break; + case AtBefore: + this.isAfterAdvice = false; + this.isBeforeAdvice = true; + } + } + } + + + @Override + public String toString() { + return "InstantiationModelAwarePointcutAdvisor: expression [" + getDeclaredPointcut().getExpression() + + "]; advice method [" + this.method + "]; perClauseKind=" + + this.aspectInstanceFactory.getAspectMetadata().getAjType().getPerClause().getKind(); + + } + + + /** + * Pointcut implementation that changes its behaviour when the advice is instantiated. + * Note that this is a dynamic pointcut. Otherwise it might + * be optimized out if it does not at first match statically. + */ + private class PerTargetInstantiationModelPointcut extends DynamicMethodMatcherPointcut { + + private final AspectJExpressionPointcut declaredPointcut; + + private final Pointcut preInstantiationPointcut; + + private LazySingletonAspectInstanceFactoryDecorator aspectInstanceFactory; + + private PerTargetInstantiationModelPointcut(AspectJExpressionPointcut declaredPointcut, + Pointcut preInstantiationPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory) { + this.declaredPointcut = declaredPointcut; + this.preInstantiationPointcut = preInstantiationPointcut; + if (aspectInstanceFactory instanceof LazySingletonAspectInstanceFactoryDecorator) { + this.aspectInstanceFactory = (LazySingletonAspectInstanceFactoryDecorator) aspectInstanceFactory; + } + } + + @Override + public boolean matches(Method method, Class targetClass) { + // We're either instantiated and matching on declared pointcut, or uninstantiated matching on either pointcut + return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass)) || + this.preInstantiationPointcut.getMethodMatcher().matches(method, targetClass); + } + + public boolean matches(Method method, Class targetClass, Object[] args) { + // This can match only on declared pointcut. + return (isAspectMaterialized() && this.declaredPointcut.matches(method, targetClass)); + } + + private boolean isAspectMaterialized() { + return (this.aspectInstanceFactory == null || this.aspectInstanceFactory.isMaterialized()); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java new file mode 100644 index 0000000000..34bc6cc181 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/LazySingletonAspectInstanceFactoryDecorator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +/** + * Decorator to cause a MetadataAwareAspectInstanceFactory to instantiate only once. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + */ +public class LazySingletonAspectInstanceFactoryDecorator implements MetadataAwareAspectInstanceFactory { + + private final MetadataAwareAspectInstanceFactory maaif; + + private Object materialized; + + + /** + * Create a new lazily initializing decorator for the given AspectInstanceFactory. + * @param maaif the MetadataAwareAspectInstanceFactory to decorate + */ + public LazySingletonAspectInstanceFactoryDecorator(MetadataAwareAspectInstanceFactory maaif) { + Assert.notNull(maaif, "AspectInstanceFactory must not be null"); + this.maaif = maaif; + } + + public synchronized Object getAspectInstance() { + if (this.materialized == null) { + this.materialized = this.maaif.getAspectInstance(); + } + return this.materialized; + } + + public ClassLoader getAspectClassLoader() { + return this.maaif.getAspectClassLoader(); + } + + public boolean isMaterialized() { + return (this.materialized != null); + } + + public AspectMetadata getAspectMetadata() { + return this.maaif.getAspectMetadata(); + } + + public int getOrder() { + if (this.maaif instanceof Ordered) { + return ((Ordered) this.maaif).getOrder(); + } + return Ordered.LOWEST_PRECEDENCE; + } + + + @Override + public String toString() { + return "LazySingletonAspectInstanceFactoryDecorator: decorating " + this.maaif; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java new file mode 100644 index 0000000000..cb9a77d4e9 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/MetadataAwareAspectInstanceFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.aop.aspectj.AspectInstanceFactory; + +/** + * Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory} + * that returns {@link AspectMetadata} associated with AspectJ-annotated classes. + * + *

Ideally, AspectInstanceFactory would include this method itself, but because + * AspectMetadata uses Java-5-only {@link org.aspectj.lang.reflect.AjType}, + * we need to split out this subinterface. + * + * @author Rod Johnson + * @since 2.0 + * @see AspectMetadata + * @see org.aspectj.lang.reflect.AjType + */ +public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory { + + /** + * Return the AspectJ AspectMetadata for this factory's aspect. + * @return the aspect metadata + */ + AspectMetadata getAspectMetadata(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/NotAnAtAspectException.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/NotAnAtAspectException.java new file mode 100644 index 0000000000..297286cbf4 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/NotAnAtAspectException.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.aop.framework.AopConfigException; + +/** + * Extension of AopConfigException thrown when trying to perform + * an advisor generation operation on a class that is not an + * AspectJ annotation-style aspect. + * + * @author Rod Johnson + * @since 2.0 + */ +public class NotAnAtAspectException extends AopConfigException { + + private Class nonAspectClass; + + + /** + * Create a new NotAnAtAspectException for the given class. + * @param nonAspectClass the offending class + */ + public NotAnAtAspectException(Class nonAspectClass) { + super(nonAspectClass.getName() + " is not an @AspectJ aspect"); + this.nonAspectClass = nonAspectClass; + } + + /** + * Returns the offending class. + */ + public Class getNonAspectClass() { + return this.nonAspectClass; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java new file mode 100644 index 0000000000..5d6fc9079b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/PrototypeAspectInstanceFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.beans.factory.BeanFactory; + +/** + * AspectInstanceFactory backed by a BeanFactory-provided prototype, + * enforcing prototype semantics. + * + *

Note that this may instantiate multiple times, which probably won't give the + * semantics you expect. Use a {@link LazySingletonAspectInstanceFactoryDecorator} + * to wrap this to ensure only one new aspect comes back. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory + * @see LazySingletonAspectInstanceFactoryDecorator + */ +public class PrototypeAspectInstanceFactory extends BeanFactoryAspectInstanceFactory { + + /** + * Create a PrototypeAspectInstanceFactory. AspectJ will be called to + * introspect to create AJType metadata using the type returned for the + * given bean name from the BeanFactory. + * @param beanFactory the BeanFactory to obtain instance(s) from + * @param name the name of the bean + */ + public PrototypeAspectInstanceFactory(BeanFactory beanFactory, String name) { + super(beanFactory, name); + if (!beanFactory.isPrototype(name)) { + throw new IllegalArgumentException( + "Cannot use PrototypeAspectInstanceFactory with bean named '" + name + "': not a prototype"); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java new file mode 100644 index 0000000000..2adede9979 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/ReflectiveAspectJAdvisorFactory.java @@ -0,0 +1,239 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import org.aopalliance.aop.Advice; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.DeclareParents; +import org.aspectj.lang.annotation.Pointcut; + +import org.springframework.aop.Advisor; +import org.springframework.aop.MethodBeforeAdvice; +import org.springframework.aop.aspectj.AbstractAspectJAdvice; +import org.springframework.aop.aspectj.AspectJAfterAdvice; +import org.springframework.aop.aspectj.AspectJAfterReturningAdvice; +import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice; +import org.springframework.aop.aspectj.AspectJAroundAdvice; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice; +import org.springframework.aop.aspectj.DeclareParentsAdvisor; +import org.springframework.aop.framework.AopConfigException; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Factory that can create Spring AOP Advisors given AspectJ classes from + * classes honoring the AspectJ 5 annotation syntax, using reflection to + * invoke the corresponding advice methods. + * + * @author Rod Johnson + * @author Adrian Colyer + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.0 + */ +public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory { + + public List getAdvisors(MetadataAwareAspectInstanceFactory maaif) { + final Class aspectClass = maaif.getAspectMetadata().getAspectClass(); + final String aspectName = maaif.getAspectMetadata().getAspectName(); + validate(aspectClass); + + // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator + // so that it will only instantiate once. + final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = + new LazySingletonAspectInstanceFactoryDecorator(maaif); + + final List advisors = new LinkedList(); + ReflectionUtils.doWithMethods(aspectClass, new ReflectionUtils.MethodCallback() { + public void doWith(Method method) throws IllegalArgumentException { + // Exclude pointcuts + if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { + Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); + if (advisor != null) { + advisors.add(advisor); + } + } + } + }); + + // If it's a per target aspect, emit the dummy instantiating aspect. + if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { + Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); + advisors.add(0, instantiationAdvisor); + } + + // Find introduction fields. + for (Field field : aspectClass.getDeclaredFields()) { + Advisor advisor = getDeclareParentsAdvisor(field); + if (advisor != null) { + advisors.add(advisor); + } + } + + return advisors; + } + + /** + * Build a {@link org.springframework.aop.aspectj.DeclareParentsAdvisor} + * for the given introduction field. + *

Resulting Advisors will need to be evaluated for targets. + * @param introductionField the field to introspect + * @return null if not an Advisor + */ + private Advisor getDeclareParentsAdvisor(Field introductionField) { + DeclareParents declareParents = (DeclareParents) introductionField.getAnnotation(DeclareParents.class); + if (declareParents == null) { + // Not an introduction field + return null; + } + + if (DeclareParents.class.equals(declareParents.defaultImpl())) { + // This is what comes back if it wasn't set. This seems bizarre... + // TODO this restriction possibly should be relaxed + throw new IllegalStateException("defaultImpl must be set on DeclareParents"); + } + + return new DeclareParentsAdvisor( + introductionField.getType(), declareParents.value(), declareParents.defaultImpl()); + } + + + public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif, + int declarationOrderInAspect, String aspectName) { + + validate(aif.getAspectMetadata().getAspectClass()); + + AspectJExpressionPointcut ajexp = + getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass()); + if (ajexp == null) { + return null; + } + return new InstantiationModelAwarePointcutAdvisorImpl( + this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName); + } + + private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class candidateAspectClass) { + AspectJAnnotation aspectJAnnotation = + AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); + if (aspectJAnnotation == null) { + return null; + } + AspectJExpressionPointcut ajexp = + new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]); + ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); + return ajexp; + } + + + public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp, + MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) { + + Class candidateAspectClass = aif.getAspectMetadata().getAspectClass(); + validate(candidateAspectClass); + + AspectJAnnotation aspectJAnnotation = + AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); + if (aspectJAnnotation == null) { + return null; + } + + // If we get here, we know we have an AspectJ method. + // Check that it's an AspectJ-annotated class + if (!isAspect(candidateAspectClass)) { + throw new AopConfigException("Advice must be declared inside an aspect type: " + + "Offending method '" + candidateAdviceMethod + "' in class [" + + candidateAspectClass.getName() + "]"); + } + + if (logger.isDebugEnabled()) { + logger.debug("Found AspectJ method: " + candidateAdviceMethod); + } + + AbstractAspectJAdvice springAdvice; + + switch (aspectJAnnotation.getAnnotationType()) { + case AtBefore: + springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif); + break; + case AtAfter: + springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif); + break; + case AtAfterReturning: + springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif); + AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); + if (StringUtils.hasText(afterReturningAnnotation.returning())) { + springAdvice.setReturningName(afterReturningAnnotation.returning()); + } + break; + case AtAfterThrowing: + springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif); + AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); + if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { + springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); + } + break; + case AtAround: + springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif); + break; + case AtPointcut: + if (logger.isDebugEnabled()) { + logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'"); + } + return null; + default: + throw new UnsupportedOperationException( + "Unsupported advice type on method " + candidateAdviceMethod); + } + + // Now to configure the advice... + springAdvice.setAspectName(aspectName); + springAdvice.setDeclarationOrder(declarationOrderInAspect); + String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); + if (argNames != null) { + springAdvice.setArgumentNamesFromStringArray(argNames); + } + springAdvice.calculateArgumentBindings(); + return springAdvice; + } + + /** + * Synthetic advisor that instantiates the aspect. + * Triggered by per-clause pointcut on non-singleton aspect. + * The advice has no effect. + */ + protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor { + + public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) { + super(aif.getAspectMetadata().getPerClausePointcut(), new MethodBeforeAdvice() { + public void before(Method method, Object[] args, Object target) { + // Simply instantiate the aspect + aif.getAspectInstance(); + } + }); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java new file mode 100644 index 0000000000..7b5581c49a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/SimpleMetadataAwareAspectInstanceFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.aop.aspectj.SimpleAspectInstanceFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +/** + * Implementation of {@link MetadataAwareAspectInstanceFactory} that + * creates a new instance of the specified aspect class for every + * {@link #getAspectInstance()} call. + * + * @author Juergen Hoeller + * @since 2.0.4 + */ +public class SimpleMetadataAwareAspectInstanceFactory extends SimpleAspectInstanceFactory + implements MetadataAwareAspectInstanceFactory { + + private final AspectMetadata metadata; + + + /** + * Create a new SimpleMetadataAwareAspectInstanceFactory for the given aspect class. + * @param aspectClass the aspect class + * @param aspectName the aspect name + */ + public SimpleMetadataAwareAspectInstanceFactory(Class aspectClass, String aspectName) { + super(aspectClass); + this.metadata = new AspectMetadata(aspectClass, aspectName); + } + + + public final AspectMetadata getAspectMetadata() { + return this.metadata; + } + + /** + * Determine a fallback order for the case that the aspect instance + * does not express an instance-specific order through implementing + * the {@link org.springframework.core.Ordered} interface. + *

The default implementation simply returns Ordered.LOWEST_PRECEDENCE. + * @param aspectClass the aspect class + */ + protected int getOrderForAspectClass(Class aspectClass) { + Order order = (Order) aspectClass.getAnnotation(Order.class); + if (order != null) { + return order.value(); + } + return Ordered.LOWEST_PRECEDENCE; + } + +} + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java new file mode 100644 index 0000000000..f4c70b4d80 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/SingletonMetadataAwareAspectInstanceFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.annotation; + +import org.springframework.aop.aspectj.SingletonAspectInstanceFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +/** + * Implementation of {@link MetadataAwareAspectInstanceFactory} that is backed + * by a specified singleton object, returning the same instance for every + * {@link #getAspectInstance()} call. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see SimpleMetadataAwareAspectInstanceFactory + */ +public class SingletonMetadataAwareAspectInstanceFactory extends SingletonAspectInstanceFactory + implements MetadataAwareAspectInstanceFactory { + + private final AspectMetadata metadata; + + + /** + * Create a new SingletonMetadataAwareAspectInstanceFactory for the given aspect. + * @param aspectInstance the singleton aspect instance + * @param aspectName the name of the aspect + */ + public SingletonMetadataAwareAspectInstanceFactory(Object aspectInstance, String aspectName) { + super(aspectInstance); + this.metadata = new AspectMetadata(aspectInstance.getClass(), aspectName); + } + + + public final AspectMetadata getAspectMetadata() { + return this.metadata; + } + + /** + * Check whether the aspect class carries an + * {@link org.springframework.core.annotation.Order} annotation, + * falling back to Ordered.LOWEST_PRECEDENCE. + * @see org.springframework.core.annotation.Order + */ + protected int getOrderForAspectClass(Class aspectClass) { + Order order = (Order) aspectClass.getAnnotation(Order.class); + if (order != null) { + return order.value(); + } + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/package.html new file mode 100644 index 0000000000..b80ecb6af4 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/annotation/package.html @@ -0,0 +1,9 @@ + + + +Classes enabling AspectJ 5 @Annotated classes to be used in Spring AOP. + +

Normally to be used through an AspectJAutoProxyCreator rather than directly. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java new file mode 100644 index 0000000000..04d798e355 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJAwareAdvisorAutoProxyCreator.java @@ -0,0 +1,162 @@ +/* + * Copyright 2002-2007 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.aop.aspectj.autoproxy; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.aopalliance.aop.Advice; +import org.aspectj.util.PartialOrder; +import org.aspectj.util.PartialOrder.PartialComparable; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.AbstractAspectJAdvice; +import org.springframework.aop.aspectj.AspectJPointcutAdvisor; +import org.springframework.aop.aspectj.AspectJProxyUtils; +import org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.core.Ordered; +import org.springframework.util.ClassUtils; + +/** + * {@link org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator} + * subclass that exposes AspectJ's invocation context and understands AspectJ's rules + * for advice precedence when multiple pieces of advice come from the same aspect. + * + * @author Adrian Colyer + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.0 + */ +public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator { + + private static final Comparator DEFAULT_PRECEDENCE_COMPARATOR = new AspectJPrecedenceComparator(); + + + /** + * Sort the rest by AspectJ precedence. If two pieces of advice have + * come from the same aspect they will have the same order. + * Advice from the same aspect is then further ordered according to the + * following rules: + *

+ *

Important: Advisors are sorted in precedence order, from highest + * precedence to lowest. "On the way in" to a join point, the highest precedence + * advisor should run first. "On the way out" of a join point, the highest precedence + * advisor should run last. + */ + protected List sortAdvisors(List advisors) { + // build list for sorting + List partiallyComparableAdvisors = new LinkedList(); + for (Iterator it = advisors.iterator(); it.hasNext();) { + Advisor element = (Advisor) it.next(); + PartiallyComparableAdvisorHolder advisor = + new PartiallyComparableAdvisorHolder(element, DEFAULT_PRECEDENCE_COMPARATOR); + partiallyComparableAdvisors.add(advisor); + } + + // sort it + List sorted = PartialOrder.sort(partiallyComparableAdvisors); + if (sorted == null) { + // TODO: work much harder to give a better error message here. + throw new IllegalArgumentException("Advice precedence circularity error"); + } + + // extract results again + List result = new LinkedList(); + for (Iterator it = sorted.iterator(); it.hasNext();) { + PartiallyComparableAdvisorHolder pcAdvisor = (PartiallyComparableAdvisorHolder) it.next(); + result.add(pcAdvisor.getAdvisor()); + } + + return result; + } + + /** + * Adds an {@link ExposeInvocationInterceptor} to the beginning of the advice chain. + * These additional advices are needed when using AspectJ expression pointcuts + * and when using AspectJ-style advice. + */ + protected void extendAdvisors(List candidateAdvisors) { + AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors); + } + + protected boolean shouldSkip(Class beanClass, String beanName) { + // TODO: Consider optimization by caching the list of the aspect names + List candidtate = findCandidateAdvisors(); + for (Iterator it = candidtate.iterator(); it.hasNext();) { + Advisor advisor = (Advisor) it.next(); + if (advisor instanceof AspectJPointcutAdvisor) { + if(((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) { + return true; + } + } + } + return super.shouldSkip(beanClass, beanName); + } + + /** + * Implements AspectJ PartialComparable interface for defining partial orderings. + */ + private static class PartiallyComparableAdvisorHolder implements PartialComparable { + + private final Advisor advisor; + + private final Comparator comparator; + + public PartiallyComparableAdvisorHolder(Advisor advisor, Comparator comparator) { + this.advisor = advisor; + this.comparator = comparator; + } + + public int compareTo(Object obj) { + Advisor otherAdvisor = ((PartiallyComparableAdvisorHolder) obj).advisor; + return this.comparator.compare(this.advisor, otherAdvisor); + } + + public int fallbackCompareTo(Object obj) { + return 0; + } + + public Advisor getAdvisor() { + return this.advisor; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + Advice advice = this.advisor.getAdvice(); + sb.append(ClassUtils.getShortName(advice.getClass())); + sb.append(": "); + if (this.advisor instanceof Ordered) { + sb.append("order " + ((Ordered) this.advisor).getOrder() + ", "); + } + if (advice instanceof AbstractAspectJAdvice) { + AbstractAspectJAdvice ajAdvice = (AbstractAspectJAdvice) advice; + sb.append(ajAdvice.getAspectName()); + sb.append(", declaration order "); + sb.append(ajAdvice.getDeclarationOrder()); + } + return sb.toString(); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java new file mode 100644 index 0000000000..0e3037f891 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/AspectJPrecedenceComparator.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2006 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.aop.aspectj.autoproxy; + +import java.util.Comparator; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.AspectJAopUtils; +import org.springframework.aop.aspectj.AspectJPrecedenceInformation; +import org.springframework.core.OrderComparator; +import org.springframework.util.Assert; + +/** + * Orders AspectJ advice/advisors by precedence (not invocation order). + * + *

Given two pieces of advice, a and b: + *

    + *
  • if a and b are defined in different + * aspects, then the advice in the aspect with the lowest order + * value has the highest precedence
  • + *
  • if a and b are defined in the same + * aspect, then if one of a or b is a form of + * after advice, then the advice declared last in the aspect has the + * highest precedence. If neither a nor b is a + * form of after advice, then the advice declared first in the aspect has + * the highest precedence.
  • + *
+ * + *

Important: Note that unlike a normal comparator a return of 0 means + * we don't care about the ordering, not that the two elements must be sorted + * identically. Used with AspectJ PartialOrder class. + * + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + */ +class AspectJPrecedenceComparator implements Comparator { + + private static final int HIGHER_PRECEDENCE = -1; + private static final int SAME_PRECEDENCE = 0; + private static final int LOWER_PRECEDENCE = 1; + private static final int NOT_COMPARABLE = 0; + + private final Comparator advisorComparator; + + + /** + * Create a default AspectJPrecedenceComparator. + */ + public AspectJPrecedenceComparator() { + this.advisorComparator = new OrderComparator(); + } + + /** + * Create a AspectJPrecedenceComparator, using the given Comparator + * for comparing {@link org.springframework.aop.Advisor} instances. + * @param advisorComparator the Comparator to use for Advisors + */ + public AspectJPrecedenceComparator(Comparator advisorComparator) { + Assert.notNull(advisorComparator, "Advisor comparator must not be null"); + this.advisorComparator = advisorComparator; + } + + + public int compare(Object o1, Object o2) { + if (!(o1 instanceof Advisor && o2 instanceof Advisor)) { + throw new IllegalArgumentException( + "AspectJPrecedenceComparator can only compare the order of Advisors, " + + "but was passed [" + o1 + "] and [" + o2 + "]"); + } + + Advisor advisor1 = (Advisor) o1; + Advisor advisor2 = (Advisor) o2; + + boolean oneOrOtherIsAfterAdvice = + (AspectJAopUtils.isAfterAdvice(advisor1) || AspectJAopUtils.isAfterAdvice(advisor2)); + boolean oneOrOtherIsBeforeAdvice = + (AspectJAopUtils.isBeforeAdvice(advisor1) || AspectJAopUtils.isBeforeAdvice(advisor2)); + if (oneOrOtherIsAfterAdvice && oneOrOtherIsBeforeAdvice) { + return NOT_COMPARABLE; + } + else { + int advisorPrecedence = this.advisorComparator.compare(advisor1, advisor2); + if (advisorPrecedence == SAME_PRECEDENCE && declaredInSameAspect(advisor1, advisor2)) { + advisorPrecedence = comparePrecedenceWithinAspect(advisor1, advisor2); + } + return advisorPrecedence; + } + } + + private int comparePrecedenceWithinAspect(Advisor advisor1, Advisor advisor2) { + boolean oneOrOtherIsAfterAdvice = + (AspectJAopUtils.isAfterAdvice(advisor1) || AspectJAopUtils.isAfterAdvice(advisor2)); + int adviceDeclarationOrderDelta = getAspectDeclarationOrder(advisor1) - getAspectDeclarationOrder(advisor2); + + if (oneOrOtherIsAfterAdvice) { + // the advice declared last has higher precedence + if (adviceDeclarationOrderDelta < 0) { + // advice1 was declared before advice2 + // so advice1 has lower precedence + return LOWER_PRECEDENCE; + } + else if (adviceDeclarationOrderDelta == 0) { + return SAME_PRECEDENCE; + } + else { + return HIGHER_PRECEDENCE; + } + } + else { + // the advice declared first has higher precedence + if (adviceDeclarationOrderDelta < 0) { + // advice1 was declared before advice2 + // so advice1 has higher precedence + return HIGHER_PRECEDENCE; + } + else if (adviceDeclarationOrderDelta == 0) { + return SAME_PRECEDENCE; + } + else { + return LOWER_PRECEDENCE; + } + } + } + + private boolean declaredInSameAspect(Advisor advisor1, Advisor advisor2) { + if (!(hasAspectName(advisor1) && hasAspectName(advisor2))) { + return false; + } + else { + return getAspectName(advisor1).equals(getAspectName(advisor2)); + } + } + + private boolean hasAspectName(Advisor anAdvisor) { + return (anAdvisor instanceof AspectJPrecedenceInformation || + anAdvisor.getAdvice() instanceof AspectJPrecedenceInformation); + } + + // pre-condition is that hasAspectName returned true + private String getAspectName(Advisor anAdvisor) { + return AspectJAopUtils.getAspectJPrecedenceInformationFor(anAdvisor).getAspectName(); + } + + private int getAspectDeclarationOrder(Advisor anAdvisor) { + AspectJPrecedenceInformation precedenceInfo = + AspectJAopUtils.getAspectJPrecedenceInformationFor(anAdvisor); + if (precedenceInfo != null) { + return precedenceInfo.getDeclarationOrder(); + } + else { + return 0; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/package.html new file mode 100644 index 0000000000..56957ee6e1 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/autoproxy/package.html @@ -0,0 +1,8 @@ + + + +Base classes enabling auto-proxying based on AspectJ. +Support for AspectJ annotation aspects resides in the "aspectj.annotation" package. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/package.html new file mode 100644 index 0000000000..e35941ad15 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/aspectj/package.html @@ -0,0 +1,14 @@ + + + +AspectJ integration package. Includes Spring AOP advice implementations for AspectJ 5 +annotation-style methods, and an AspectJExpressionPointcut: a Spring AOP Pointcut +implementation that allows use of the AspectJ pointcut expression language with the Spring AOP +runtime framework. + +

Note that use of this package does not require the use of the ajc compiler +or AspectJ load-time weaver. It is intended to enable the use of a valuable subset of AspectJ +functionality, with consistent semantics, with the proxy-based Spring AOP framework. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java new file mode 100644 index 0000000000..4e88557fe8 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AbstractInterceptorDrivenBeanDefinitionDecorator.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2006 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.aop.config; + +import java.util.List; + +import org.w3c.dom.Node; + +import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionDecorator; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Base implementation for {@link org.springframework.beans.factory.xml.BeanDefinitionDecorator BeanDefinitionDecorators} wishing + * to add an {@link org.aopalliance.intercept.MethodInterceptor interceptor} to the resulting + * bean. + * + *

This base class controls the creation of the {@link ProxyFactoryBean} bean definition + * and wraps the original as an inner-bean definition for the target property of + * {@link ProxyFactoryBean}. + * + *

Chaining is correctly handled, ensuring that only one {@link ProxyFactoryBean} definition + * is created. If a previous {@link org.springframework.beans.factory.xml.BeanDefinitionDecorator} already created the {@link org.springframework.aop.framework.ProxyFactoryBean} + * then the interceptor is simply added to the existing definition. + * + *

Subclasses have only to create the BeanDefinition to the interceptor they + * wish to add. + * + * @author Rob Harrop + * @since 2.0 + * @see org.aopalliance.intercept.MethodInterceptor + */ +public abstract class AbstractInterceptorDrivenBeanDefinitionDecorator implements BeanDefinitionDecorator { + + public final BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definitionHolder, ParserContext parserContext) { + BeanDefinitionRegistry registry = parserContext.getRegistry(); + + // get the root bean name - will be the name of the generated proxy factory bean + String existingBeanName = definitionHolder.getBeanName(); + BeanDefinition existingDefinition = definitionHolder.getBeanDefinition(); + + // delegate to subclass for interceptor def + BeanDefinition interceptorDefinition = createInterceptorDefinition(node); + + // generate name and register the interceptor + String interceptorName = existingBeanName + "." + getInterceptorNameSuffix(interceptorDefinition); + BeanDefinitionReaderUtils.registerBeanDefinition(new BeanDefinitionHolder(interceptorDefinition, interceptorName), registry); + + BeanDefinitionHolder result = definitionHolder; + + if (!isProxyFactoryBeanDefinition(existingDefinition)) { + + // create the proxy definitionHolder + RootBeanDefinition proxyDefinition = new RootBeanDefinition(); + // create proxy factory bean definitionHolder + proxyDefinition.setBeanClass(ProxyFactoryBean.class); + + // set up property values + MutablePropertyValues mpvs = new MutablePropertyValues(); + proxyDefinition.setPropertyValues(mpvs); + + // set the target + mpvs.addPropertyValue("target", existingDefinition); + + // create the interceptor names list + ManagedList interceptorList = new ManagedList(); + mpvs.addPropertyValue("interceptorNames", interceptorList); + + result = new BeanDefinitionHolder(proxyDefinition, existingBeanName); + } + + addInterceptorNameToList(interceptorName, result.getBeanDefinition()); + + return result; + + } + + private void addInterceptorNameToList(String interceptorName, BeanDefinition beanDefinition) { + List list = (List) beanDefinition.getPropertyValues().getPropertyValue("interceptorNames").getValue(); + list.add(interceptorName); + } + + private boolean isProxyFactoryBeanDefinition(BeanDefinition existingDefinition) { + return existingDefinition.getBeanClassName().equals(ProxyFactoryBean.class.getName()); + } + + protected String getInterceptorNameSuffix(BeanDefinition interceptorDefinition) { + return StringUtils.uncapitalize(ClassUtils.getShortName(interceptorDefinition.getBeanClassName())); + } + + /** + * Subclasses should implement this method to return the BeanDefinition + * for the interceptor they wish to apply to the bean being decorated. + */ + protected abstract BeanDefinition createInterceptorDefinition(Node node); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AdviceEntry.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AdviceEntry.java new file mode 100644 index 0000000000..3024fb2ba5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AdviceEntry.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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.aop.config; + +import org.springframework.beans.factory.parsing.ParseState; + +/** + * {@link ParseState} entry representing an advice element. + * + * @author Mark Fisher + * @since 2.0 + */ +public class AdviceEntry implements ParseState.Entry { + + private final String kind; + + + /** + * Creates a new instance of the {@link AdviceEntry} class. + * @param kind the kind of advice represented by this entry (before, after, around, etc.) + */ + public AdviceEntry(String kind) { + this.kind = kind; + } + + public String toString() { + return "Advice (" + this.kind + ")"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AdvisorComponentDefinition.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AdvisorComponentDefinition.java new file mode 100644 index 0000000000..342bbd195d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AdvisorComponentDefinition.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; +import org.springframework.beans.factory.parsing.AbstractComponentDefinition; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.beans.factory.parsing.ComponentDefinition} + * that bridges the gap between the advisor bean definition configured + * by the <aop:advisor> tag and the component definition + * infrastructure. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class AdvisorComponentDefinition extends AbstractComponentDefinition { + + private final String advisorBeanName; + + private final BeanDefinition advisorDefinition; + + private String description; + + private BeanReference[] beanReferences; + + private BeanDefinition[] beanDefinitions; + + + public AdvisorComponentDefinition(String advisorBeanName, BeanDefinition advisorDefinition) { + this(advisorBeanName, advisorDefinition, null); + } + + public AdvisorComponentDefinition( + String advisorBeanName, BeanDefinition advisorDefinition, BeanDefinition pointcutDefinition) { + + Assert.notNull(advisorBeanName, "'advisorBeanName' must not be null"); + Assert.notNull(advisorDefinition, "'advisorDefinition' must not be null"); + this.advisorBeanName = advisorBeanName; + this.advisorDefinition = advisorDefinition; + unwrapDefinitions(advisorDefinition, pointcutDefinition); + } + + + private void unwrapDefinitions(BeanDefinition advisorDefinition, BeanDefinition pointcutDefinition) { + MutablePropertyValues pvs = advisorDefinition.getPropertyValues(); + BeanReference adviceReference = (BeanReference) pvs.getPropertyValue("adviceBeanName").getValue(); + + if (pointcutDefinition != null) { + this.beanReferences = new BeanReference[] {adviceReference}; + this.beanDefinitions = new BeanDefinition[] {advisorDefinition, pointcutDefinition}; + this.description = buildDescription(adviceReference, pointcutDefinition); + } + else { + BeanReference pointcutReference = (BeanReference) pvs.getPropertyValue("pointcut").getValue(); + this.beanReferences = new BeanReference[] {adviceReference, pointcutReference}; + this.beanDefinitions = new BeanDefinition[] {advisorDefinition}; + this.description = buildDescription(adviceReference, pointcutReference); + } + } + + private String buildDescription(BeanReference adviceReference, BeanDefinition pointcutDefinition) { + return new StringBuffer("Advisor ").toString(); + } + + private String buildDescription(BeanReference adviceReference, BeanReference pointcutReference) { + return new StringBuffer("Advisor ").toString(); + } + + + public String getName() { + return this.advisorBeanName; + } + + public String getDescription() { + return this.description; + } + + public BeanDefinition[] getBeanDefinitions() { + return this.beanDefinitions; + } + + public BeanReference[] getBeanReferences() { + return this.beanReferences; + } + + public Object getSource() { + return this.advisorDefinition.getSource(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java new file mode 100644 index 0000000000..0fd4e0166a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AdvisorEntry.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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.aop.config; + +import org.springframework.beans.factory.parsing.ParseState; + +/** + * {@link ParseState} entry representing an advisor. + * + * @author Mark Fisher + * @since 2.0 + */ +public class AdvisorEntry implements ParseState.Entry { + + private final String name; + + + /** + * Creates a new instance of the {@link AdvisorEntry} class. + * @param name the bean name of the advisor + */ + public AdvisorEntry(String name) { + this.name = name; + } + + public String toString() { + return "Advisor '" + this.name + "'"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java new file mode 100644 index 0000000000..db96239539 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java @@ -0,0 +1,159 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator; +import org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.JdkVersion; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Utility class for handling registration of AOP auto-proxy creators. + * + *

Only a single auto-proxy creator can be registered yet multiple concrete + * implementations are available. Therefore this class wraps a simple escalation + * protocol, allowing classes to request a particular auto-proxy creator and know + * that class, or a subclass thereof, will eventually be resident + * in the application context. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.5 + * @see AopNamespaceUtils + */ +public abstract class AopConfigUtils { + + /** + * The bean name of the internally managed auto-proxy creator. + */ + public static final String AUTO_PROXY_CREATOR_BEAN_NAME = + "org.springframework.aop.config.internalAutoProxyCreator"; + + /** + * The class name of the AnnotationAwareAspectJAutoProxyCreator class. + * Only available with AspectJ and Java 5. + */ + private static final String ASPECTJ_ANNOTATION_AUTO_PROXY_CREATOR_CLASS_NAME = + "org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"; + + + /** + * Stores the auto proxy creator classes in escalation order. + */ + private static final List APC_PRIORITY_LIST = new ArrayList(); + + /** + * Setup the escalation list. + */ + static { + APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class.getName()); + APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class.getName()); + APC_PRIORITY_LIST.add(ASPECTJ_ANNOTATION_AUTO_PROXY_CREATOR_CLASS_NAME); + } + + + public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) { + return registerAutoProxyCreatorIfNecessary(registry, null); + } + + public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) { + return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source); + } + + public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) { + return registerAspectJAutoProxyCreatorIfNecessary(registry, null); + } + + public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) { + return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source); + } + + public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) { + return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null); + } + + public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) { + Class cls = getAspectJAnnotationAutoProxyCreatorClassIfPossible(); + return registerOrEscalateApcAsRequired(cls, registry, source); + } + + public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { + if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { + BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); + definition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.TRUE); + } + } + + + private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) { + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { + BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); + if (!cls.getName().equals(apcDefinition.getBeanClassName())) { + int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); + int requiredPriority = findPriorityForClass(cls.getName()); + if (currentPriority < requiredPriority) { + apcDefinition.setBeanClassName(cls.getName()); + } + } + return null; + } + RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); + beanDefinition.setSource(source); + beanDefinition.getPropertyValues().addPropertyValue("order", new Integer(Ordered.HIGHEST_PRECEDENCE)); + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); + return beanDefinition; + } + + private static Class getAspectJAnnotationAutoProxyCreatorClassIfPossible() { + if (JdkVersion.getMajorJavaVersion() < JdkVersion.JAVA_15) { + throw new IllegalStateException( + "AnnotationAwareAspectJAutoProxyCreator is only available on Java 1.5 and higher"); + } + try { + return ClassUtils.forName( + ASPECTJ_ANNOTATION_AUTO_PROXY_CREATOR_CLASS_NAME, AopConfigUtils.class.getClassLoader()); + } + catch (Throwable ex) { + throw new IllegalStateException("Unable to load Java 1.5 dependent class [" + + ASPECTJ_ANNOTATION_AUTO_PROXY_CREATOR_CLASS_NAME + "]", ex); + } + } + + private static int findPriorityForClass(String className) { + Assert.notNull(className, "Class name must not be null"); + for (int i = 0; i < APC_PRIORITY_LIST.size(); i++) { + String str = (String) APC_PRIORITY_LIST.get(i); + if (className.equals(str)) { + return i; + } + } + throw new IllegalArgumentException( + "Class name [" + className + "] is not a known auto-proxy creator class"); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java new file mode 100644 index 0000000000..9f0dc56d03 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * NamespaceHandler for the aop namespace. + * + *

Provides a {@link org.springframework.beans.factory.xml.BeanDefinitionParser} for the + * <aop:config> tag. A config tag can include nested + * pointcut, advisor and aspect tags. + * + *

The pointcut tag allows for creation of named + * {@link AspectJExpressionPointcut} beans using a simple syntax: + *

+ * <aop:pointcut id="getNameCalls" expression="execution(* *..ITestBean.getName(..))"/>
+ * 
+ * + *

Using the advisor tag you can configure an {@link org.springframework.aop.Advisor} + * and have it applied to all relevant beans in you {@link org.springframework.beans.factory.BeanFactory} + * automatically. The advisor tag supports both in-line and referenced + * {@link org.springframework.aop.Pointcut Pointcuts}: + * + *

+ * <aop:advisor id="getAgeAdvisor"
+ *     pointcut="execution(* *..ITestBean.getAge(..))"
+ *     advice-ref="getAgeCounter"/>
+ *
+ * <aop:advisor id="getNameAdvisor"
+ *     pointcut-ref="getNameCalls"
+ *     advice-ref="getNameCounter"/>
+ * + * @author Rob Harrop + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + */ +public class AopNamespaceHandler extends NamespaceHandlerSupport { + + /** + * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the + * 'config', 'spring-configured', 'aspectj-autoproxy' + * and 'scoped-proxy' tags. + */ + public void init() { + // In 2.0 XSD as well as in 2.1 XSD. + registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); + registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); + registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); + + // Only in 2.0 XSD: moved to context namespace as of 2.1 + registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java new file mode 100644 index 0000000000..2b3cc33a46 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2008 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.aop.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.ParserContext; + +/** + * Utility class for handling registration of auto-proxy creators used internally + * by the 'aop' namespace tags. + * + *

Only a single auto-proxy creator can be registered and multiple tags may wish + * to register different concrete implementations. As such this class delegates to + * {@link AopConfigUtils} which wraps a simple escalation protocol. Therefore classes + * may request a particular auto-proxy creator and know that class, or a subclass + * thereof, will eventually be resident in the application context. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.0 + * @see AopConfigUtils + */ +public abstract class AopNamespaceUtils { + + /** + * The proxy-target-class attribute as found on AOP-related XML tags. + */ + public static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class"; + + + public static void registerAutoProxyCreatorIfNecessary( + ParserContext parserContext, Element sourceElement) { + + BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary( + parserContext.getRegistry(), parserContext.extractSource(sourceElement)); + useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); + registerComponentIfNecessary(beanDefinition, parserContext); + } + + public static void registerAspectJAutoProxyCreatorIfNecessary( + ParserContext parserContext, Element sourceElement) { + + BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary( + parserContext.getRegistry(), parserContext.extractSource(sourceElement)); + useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); + registerComponentIfNecessary(beanDefinition, parserContext); + } + + public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( + ParserContext parserContext, Element sourceElement) { + + BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( + parserContext.getRegistry(), parserContext.extractSource(sourceElement)); + useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); + registerComponentIfNecessary(beanDefinition, parserContext); + } + + /** + * @deprecated since Spring 2.5, in favor of + * {@link #registerAutoProxyCreatorIfNecessary(ParserContext, Element)} and + * {@link AopConfigUtils#registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry, Object)} + */ + public static void registerAutoProxyCreatorIfNecessary(ParserContext parserContext, Object source) { + BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary( + parserContext.getRegistry(), source); + registerComponentIfNecessary(beanDefinition, parserContext); + } + + /** + * @deprecated since Spring 2.5, in favor of + * {@link AopConfigUtils#forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry)} + */ + public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); + } + + + private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) { + if (sourceElement != null) { + boolean proxyTargetClass = Boolean.valueOf( + sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)).booleanValue(); + if (proxyTargetClass) { + AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); + } + } + } + + private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ParserContext parserContext) { + if (beanDefinition != null) { + BeanComponentDefinition componentDefinition = + new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); + parserContext.registerComponent(componentDefinition); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectComponentDefinition.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectComponentDefinition.java new file mode 100644 index 0000000000..3a5f9e189c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectComponentDefinition.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; + +/** + * {@link org.springframework.beans.factory.parsing.ComponentDefinition} + * that holds an aspect definition, including its nested pointcuts. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see #getNestedComponents() + * @see PointcutComponentDefinition + */ +public class AspectComponentDefinition extends CompositeComponentDefinition { + + private final BeanDefinition[] beanDefinitions; + + private final BeanReference[] beanReferences; + + + public AspectComponentDefinition( + String aspectName, BeanDefinition[] beanDefinitions, BeanReference[] beanReferences, Object source) { + + super(aspectName, source); + this.beanDefinitions = (beanDefinitions != null ? beanDefinitions : new BeanDefinition[0]); + this.beanReferences = (beanReferences != null ? beanReferences : new BeanReference[0]); + } + + + public BeanDefinition[] getBeanDefinitions() { + return this.beanDefinitions; + } + + public BeanReference[] getBeanReferences() { + return this.beanReferences; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectEntry.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectEntry.java new file mode 100644 index 0000000000..64dd3c1646 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.springframework.beans.factory.parsing.ParseState; +import org.springframework.util.StringUtils; + +/** + * {@link ParseState} entry representing an aspect. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @since 2.0 + */ +public class AspectEntry implements ParseState.Entry { + + private final String id; + + private final String ref; + + + /** + * Create a new AspectEntry. + * @param id the id of the aspect element + * @param ref the bean name referenced by this aspect element + */ + public AspectEntry(String id, String ref) { + this.id = id; + this.ref = ref; + } + + public String toString() { + return "Aspect: " + (StringUtils.hasLength(this.id) ? "id='" + this.id + "'" : "ref='" + this.ref + "'"); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectJAutoProxyBeanDefinitionParser.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectJAutoProxyBeanDefinitionParser.java new file mode 100644 index 0000000000..9ff8513182 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AspectJAutoProxyBeanDefinitionParser.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; + +/** + * {@link BeanDefinitionParser} for the aspectj-autoproxy tag, + * enabling the automatic application of @AspectJ-style aspects found in + * the {@link org.springframework.beans.factory.BeanFactory}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser { + + public BeanDefinition parse(Element element, ParserContext parserContext) { + AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); + extendBeanDefinition(element, parserContext); + return null; + } + + private void extendBeanDefinition(Element element, ParserContext parserContext) { + BeanDefinition beanDef = + parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); + if (element.hasChildNodes()) { + addIncludePatterns(element, parserContext, beanDef); + } + } + + private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) { + ManagedList includePatterns = new ManagedList(); + NodeList childNodes = element.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element includeElement = (Element) node; + TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name")); + valueHolder.setSource(parserContext.extractSource(includeElement)); + includePatterns.add(valueHolder); + } + } + if (!includePatterns.isEmpty()) { + includePatterns.setSource(parserContext.extractSource(element)); + beanDef.getPropertyValues().addPropertyValue("includePatterns", includePatterns); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java new file mode 100644 index 0000000000..8c1a84d0b2 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/ConfigBeanDefinitionParser.java @@ -0,0 +1,546 @@ +/* + * Copyright 2002-2008 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.aop.config; + +import java.util.ArrayList; +import java.util.List; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.springframework.aop.aspectj.AspectJAfterAdvice; +import org.springframework.aop.aspectj.AspectJAfterReturningAdvice; +import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice; +import org.springframework.aop.aspectj.AspectJAroundAdvice; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice; +import org.springframework.aop.aspectj.AspectJPointcutAdvisor; +import org.springframework.aop.aspectj.DeclareParentsAdvisor; +import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanReference; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.RuntimeBeanNameReference; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.parsing.CompositeComponentDefinition; +import org.springframework.beans.factory.parsing.ParseState; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; + +/** + * {@link BeanDefinitionParser} for the <aop:config> tag. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Adrian Colyer + * @author Mark Fisher + * @author Ramnivas Laddad + * @since 2.0 + */ +class ConfigBeanDefinitionParser implements BeanDefinitionParser { + + private static final String ASPECT = "aspect"; + + private static final String EXPRESSION = "expression"; + + private static final String ID = "id"; + + private static final String POINTCUT = "pointcut"; + + private static final String ADVICE_BEAN_NAME = "adviceBeanName"; + + private static final String ADVISOR = "advisor"; + + private static final String ADVICE_REF = "advice-ref"; + + private static final String POINTCUT_REF = "pointcut-ref"; + + private static final String REF = "ref"; + + private static final String BEFORE = "before"; + + private static final String DECLARE_PARENTS = "declare-parents"; + + private static final String TYPE_PATTERN = "types-matching"; + + private static final String DEFAULT_IMPL = "default-impl"; + + private static final String DELEGATE_REF = "delegate-ref"; + + private static final String IMPLEMENT_INTERFACE = "implement-interface"; + + private static final String AFTER = "after"; + + private static final String AFTER_RETURNING_ELEMENT = "after-returning"; + + private static final String AFTER_THROWING_ELEMENT = "after-throwing"; + + private static final String AROUND = "around"; + + private static final String RETURNING = "returning"; + + private static final String RETURNING_PROPERTY = "returningName"; + + private static final String THROWING = "throwing"; + + private static final String THROWING_PROPERTY = "throwingName"; + + private static final String ARG_NAMES = "arg-names"; + + private static final String ARG_NAMES_PROPERTY = "argumentNames"; + + private static final String ASPECT_NAME_PROPERTY = "aspectName"; + + private static final String DECLARATION_ORDER_PROPERTY = "declarationOrder"; + + private static final String ORDER_PROPERTY = "order"; + + private static final int METHOD_INDEX = 0; + + private static final int POINTCUT_INDEX = 1; + + private static final int ASPECT_INSTANCE_FACTORY_INDEX = 2; + + + private ParseState parseState = new ParseState(); + + + public BeanDefinition parse(Element element, ParserContext parserContext) { + CompositeComponentDefinition compositeDef = + new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); + parserContext.pushContainingComponent(compositeDef); + + configureAutoProxyCreator(parserContext, element); + + NodeList childNodes = element.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + String localName = node.getLocalName(); + if (POINTCUT.equals(localName)) { + parsePointcut((Element) node, parserContext); + } + else if (ADVISOR.equals(localName)) { + parseAdvisor((Element) node, parserContext); + } + else if (ASPECT.equals(localName)) { + parseAspect((Element) node, parserContext); + } + } + } + + parserContext.popAndRegisterContainingComponent(); + return null; + } + + /** + * Configures the auto proxy creator needed to support the {@link BeanDefinition BeanDefinitions} + * created by the '<aop:config/>' tag. Will force class proxying if the + * 'proxy-target-class' attribute is set to 'true'. + * @see AopNamespaceUtils + */ + private void configureAutoProxyCreator(ParserContext parserContext, Element element) { + AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element); + } + + /** + * Parses the supplied <advisor> element and registers the resulting + * {@link org.springframework.aop.Advisor} and any resulting {@link org.springframework.aop.Pointcut} + * with the supplied {@link BeanDefinitionRegistry}. + */ + private void parseAdvisor(Element advisorElement, ParserContext parserContext) { + AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext); + String id = advisorElement.getAttribute(ID); + + try { + this.parseState.push(new AdvisorEntry(id)); + String advisorBeanName = id; + if (StringUtils.hasText(advisorBeanName)) { + parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef); + } + else { + advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef); + } + + Object pointcut = parsePointcutProperty(advisorElement, parserContext); + if (pointcut instanceof BeanDefinition) { + advisorDef.getPropertyValues().addPropertyValue(POINTCUT, pointcut); + parserContext.registerComponent( + new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut)); + } + else if (pointcut instanceof String) { + advisorDef.getPropertyValues().addPropertyValue(POINTCUT, new RuntimeBeanReference((String) pointcut)); + parserContext.registerComponent( + new AdvisorComponentDefinition(advisorBeanName, advisorDef)); + } + } + finally { + this.parseState.pop(); + } + } + + /** + * Create a {@link RootBeanDefinition} for the advisor described in the supplied. Does not + * parse any associated 'pointcut' or 'pointcut-ref' attributes. + */ + private AbstractBeanDefinition createAdvisorBeanDefinition(Element advisorElement, ParserContext parserContext) { + RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultBeanFactoryPointcutAdvisor.class); + advisorDefinition.setSource(parserContext.extractSource(advisorElement)); + + String adviceRef = advisorElement.getAttribute(ADVICE_REF); + if (!StringUtils.hasText(adviceRef)) { + parserContext.getReaderContext().error( + "'advice-ref' attribute contains empty value.", advisorElement, this.parseState.snapshot()); + } + else { + advisorDefinition.getPropertyValues().addPropertyValue( + ADVICE_BEAN_NAME, new RuntimeBeanNameReference(adviceRef)); + } + + if (advisorElement.hasAttribute(ORDER_PROPERTY)) { + advisorDefinition.getPropertyValues().addPropertyValue( + ORDER_PROPERTY, advisorElement.getAttribute(ORDER_PROPERTY)); + } + + return advisorDefinition; + } + + private void parseAspect(Element aspectElement, ParserContext parserContext) { + String aspectId = aspectElement.getAttribute(ID); + String aspectName = aspectElement.getAttribute(REF); + + try { + this.parseState.push(new AspectEntry(aspectId, aspectName)); + List beanDefinitions = new ArrayList(); + List beanReferences = new ArrayList(); + + List declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS); + for (int i = METHOD_INDEX; i < declareParents.size(); i++) { + Element declareParentsElement = (Element) declareParents.get(i); + beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext)); + } + + // We have to parse "advice" and all the advice kinds in one loop, to get the + // ordering semantics right. + NodeList nodeList = aspectElement.getChildNodes(); + boolean adviceFoundAlready = false; + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (isAdviceNode(node)) { + if (!adviceFoundAlready) { + adviceFoundAlready = true; + if (!StringUtils.hasText(aspectName)) { + parserContext.getReaderContext().error( + " tag needs aspect bean reference via 'ref' attribute when declaring advices.", + aspectElement, this.parseState.snapshot()); + return; + } + beanReferences.add(new RuntimeBeanReference(aspectName)); + } + AbstractBeanDefinition advisorDefinition = parseAdvice( + aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences); + beanDefinitions.add(advisorDefinition); + } + } + + AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition( + aspectElement, aspectId, beanDefinitions, beanReferences, parserContext); + parserContext.pushContainingComponent(aspectComponentDefinition); + + List pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT); + for (int i = 0; i < pointcuts.size(); i++) { + Element pointcutElement = (Element) pointcuts.get(i); + parsePointcut(pointcutElement, parserContext); + } + + parserContext.popAndRegisterContainingComponent(); + } + finally { + this.parseState.pop(); + } + } + + private AspectComponentDefinition createAspectComponentDefinition( + Element aspectElement, String aspectId, List beanDefs, List beanRefs, ParserContext parserContext) { + + BeanDefinition[] beanDefArray = (BeanDefinition[]) beanDefs.toArray(new BeanDefinition[beanDefs.size()]); + BeanReference[] beanRefArray = (BeanReference[]) beanRefs.toArray(new BeanReference[beanRefs.size()]); + Object source = parserContext.extractSource(aspectElement); + return new AspectComponentDefinition(aspectId, beanDefArray, beanRefArray, source); + } + + /** + * Return true if the supplied node describes an advice type. May be one of: + * 'before', 'after', 'after-returning', + * 'after-throwing' or 'around'. + */ + private boolean isAdviceNode(Node aNode) { + if (!(aNode instanceof Element)) { + return false; + } + else { + String name = aNode.getLocalName(); + return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) || + AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name)); + } + } + + /** + * Parse a 'declare-parents' element and register the appropriate + * DeclareParentsAdvisor with the BeanDefinitionRegistry encapsulated in the + * supplied ParserContext. + */ + private AbstractBeanDefinition parseDeclareParents(Element declareParentsElement, ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DeclareParentsAdvisor.class); + builder.addConstructorArgValue(declareParentsElement.getAttribute(IMPLEMENT_INTERFACE)); + builder.addConstructorArgValue(declareParentsElement.getAttribute(TYPE_PATTERN)); + + String defaultImpl = declareParentsElement.getAttribute(DEFAULT_IMPL); + String delegateRef = declareParentsElement.getAttribute(DELEGATE_REF); + + if (StringUtils.hasText(defaultImpl) && !StringUtils.hasText(delegateRef)) { + builder.addConstructorArgValue(defaultImpl); + } + else if (StringUtils.hasText(delegateRef) && !StringUtils.hasText(defaultImpl)) { + builder.addConstructorArgReference(delegateRef); + } + else { + parserContext.getReaderContext().error( + "Exactly one of the " + DEFAULT_IMPL + " or " + DELEGATE_REF + " attributes must be specified", + declareParentsElement, this.parseState.snapshot()); + } + + AbstractBeanDefinition definition = builder.getBeanDefinition(); + definition.setSource(parserContext.extractSource(declareParentsElement)); + parserContext.getReaderContext().registerWithGeneratedName(definition); + return definition; + } + + /** + * Parses one of 'before', 'after', 'after-returning', + * 'after-throwing' or 'around' and registers the resulting + * BeanDefinition with the supplied BeanDefinitionRegistry. + * @return the generated advice RootBeanDefinition + */ + private AbstractBeanDefinition parseAdvice( + String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext, + List beanDefinitions, List beanReferences) { + + try { + this.parseState.push(new AdviceEntry(adviceElement.getLocalName())); + + // create the method factory bean + RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class); + methodDefinition.getPropertyValues().addPropertyValue("targetBeanName", aspectName); + methodDefinition.getPropertyValues().addPropertyValue("methodName", adviceElement.getAttribute("method")); + methodDefinition.setSynthetic(true); + + // create instance factory definition + RootBeanDefinition aspectFactoryDef = + new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class); + aspectFactoryDef.getPropertyValues().addPropertyValue("aspectBeanName", aspectName); + aspectFactoryDef.setSynthetic(true); + + // register the pointcut + AbstractBeanDefinition adviceDef = createAdviceDefinition( + adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef, + beanDefinitions, beanReferences); + + // configure the advisor + RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class); + advisorDefinition.setSource(parserContext.extractSource(adviceElement)); + advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef); + if (aspectElement.hasAttribute(ORDER_PROPERTY)) { + advisorDefinition.getPropertyValues().addPropertyValue( + ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY)); + } + + // register the final advisor + parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition); + + return advisorDefinition; + } + finally { + this.parseState.pop(); + } + } + + /** + * Creates the RootBeanDefinition for a POJO advice bean. Also causes pointcut + * parsing to occur so that the pointcut may be associate with the advice bean. + * This same pointcut is also configured as the pointcut for the enclosing + * Advisor definition using the supplied MutablePropertyValues. + */ + private AbstractBeanDefinition createAdviceDefinition( + Element adviceElement, ParserContext parserContext, String aspectName, int order, + RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef, List beanDefinitions, List beanReferences) { + + RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement)); + adviceDefinition.setSource(parserContext.extractSource(adviceElement)); + + adviceDefinition.getPropertyValues().addPropertyValue( + ASPECT_NAME_PROPERTY, aspectName); + adviceDefinition.getPropertyValues().addPropertyValue( + DECLARATION_ORDER_PROPERTY, new Integer(order)); + + if (adviceElement.hasAttribute(RETURNING)) { + adviceDefinition.getPropertyValues().addPropertyValue( + RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING)); + } + if (adviceElement.hasAttribute(THROWING)) { + adviceDefinition.getPropertyValues().addPropertyValue( + THROWING_PROPERTY, adviceElement.getAttribute(THROWING)); + } + if (adviceElement.hasAttribute(ARG_NAMES)) { + adviceDefinition.getPropertyValues().addPropertyValue( + ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES)); + } + + ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues(); + cav.addIndexedArgumentValue(METHOD_INDEX, methodDef); + + Object pointcut = parsePointcutProperty(adviceElement, parserContext); + if (pointcut instanceof BeanDefinition) { + cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut); + beanDefinitions.add(pointcut); + } + else if (pointcut instanceof String) { + RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut); + cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef); + beanReferences.add(pointcutRef); + } + + cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef); + + return adviceDefinition; + } + + /** + * Gets the advice implementation class corresponding to the supplied {@link Element}. + */ + private Class getAdviceClass(Element adviceElement) { + String elementName = adviceElement.getLocalName(); + if (BEFORE.equals(elementName)) { + return AspectJMethodBeforeAdvice.class; + } + else if (AFTER.equals(elementName)) { + return AspectJAfterAdvice.class; + } + else if (AFTER_RETURNING_ELEMENT.equals(elementName)) { + return AspectJAfterReturningAdvice.class; + } + else if (AFTER_THROWING_ELEMENT.equals(elementName)) { + return AspectJAfterThrowingAdvice.class; + } + else if (AROUND.equals(elementName)) { + return AspectJAroundAdvice.class; + } + else { + throw new IllegalArgumentException("Unknown advice kind [" + elementName + "]."); + } + } + + /** + * Parses the supplied <pointcut> and registers the resulting + * Pointcut with the BeanDefinitionRegistry. + */ + private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) { + String id = pointcutElement.getAttribute(ID); + String expression = pointcutElement.getAttribute(EXPRESSION); + + AbstractBeanDefinition pointcutDefinition = null; + + try { + this.parseState.push(new PointcutEntry(id)); + pointcutDefinition = createPointcutDefinition(expression); + pointcutDefinition.setSource(parserContext.extractSource(pointcutElement)); + + String pointcutBeanName = id; + if (StringUtils.hasText(pointcutBeanName)) { + parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition); + } + else { + pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition); + } + + parserContext.registerComponent( + new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression)); + } + finally { + this.parseState.pop(); + } + + return pointcutDefinition; + } + + /** + * Parses the pointcut or pointcut-ref attributes of the supplied + * {@link Element} and add a pointcut property as appropriate. Generates a + * {@link org.springframework.beans.factory.config.BeanDefinition} for the pointcut if necessary + * and returns its bean name, otherwise returns the bean name of the referred pointcut. + */ + private Object parsePointcutProperty(Element element, ParserContext parserContext) { + if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) { + parserContext.getReaderContext().error( + "Cannot define both 'pointcut' and 'pointcut-ref' on tag.", + element, this.parseState.snapshot()); + return null; + } + else if (element.hasAttribute(POINTCUT)) { + // Create a pointcut for the anonymous pc and register it. + String expression = element.getAttribute(POINTCUT); + AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression); + pointcutDefinition.setSource(parserContext.extractSource(element)); + return pointcutDefinition; + } + else if (element.hasAttribute(POINTCUT_REF)) { + String pointcutRef = element.getAttribute(POINTCUT_REF); + if (!StringUtils.hasText(pointcutRef)) { + parserContext.getReaderContext().error( + "'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot()); + return null; + } + return pointcutRef; + } + else { + parserContext.getReaderContext().error( + "Must define one of 'pointcut' or 'pointcut-ref' on tag.", + element, this.parseState.snapshot()); + return null; + } + } + + /** + * Creates a {@link BeanDefinition} for the {@link AspectJExpressionPointcut} class using + * the supplied pointcut expression. + */ + protected AbstractBeanDefinition createPointcutDefinition(String expression) { + RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class); + beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); + beanDefinition.setSynthetic(true); + beanDefinition.getPropertyValues().addPropertyValue(EXPRESSION, expression); + return beanDefinition; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/MethodLocatingFactoryBean.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/MethodLocatingFactoryBean.java new file mode 100644 index 0000000000..fe08d7dcbe --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/MethodLocatingFactoryBean.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2006 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.aop.config; + +import java.lang.reflect.Method; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.StringUtils; + +/** + * {@link FactoryBean} implementation that locates a {@link Method} on a specified bean. + * + * @author Rob Harrop + * @since 2.0 + */ +public class MethodLocatingFactoryBean implements FactoryBean, BeanFactoryAware { + + private String targetBeanName; + + private String methodName; + + private Method method; + + + /** + * Set the name of the bean to locate the {@link Method} on. + *

This property is required. + * @param targetBeanName the name of the bean to locate the {@link Method} on + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + /** + * Set the name of the {@link Method} to locate. + *

This property is required. + * @param methodName the name of the {@link Method} to locate + */ + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public void setBeanFactory(BeanFactory beanFactory) { + if (!StringUtils.hasText(this.targetBeanName)) { + throw new IllegalArgumentException("Property 'targetBeanName' is required"); + } + if (!StringUtils.hasText(this.methodName)) { + throw new IllegalArgumentException("Property 'methodName' is required"); + } + + Class beanClass = beanFactory.getType(this.targetBeanName); + if (beanClass == null) { + throw new IllegalArgumentException("Can't determine type of bean with name '" + this.targetBeanName + "'"); + } + this.method = BeanUtils.resolveSignature(this.methodName, beanClass); + + if (this.method == null) { + throw new IllegalArgumentException("Unable to locate method [" + this.methodName + + "] on bean [" + this.targetBeanName + "]"); + } + } + + + public Object getObject() throws Exception { + return this.method; + } + + public Class getObjectType() { + return Method.class; + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/PointcutComponentDefinition.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/PointcutComponentDefinition.java new file mode 100644 index 0000000000..e6fe127822 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/PointcutComponentDefinition.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.AbstractComponentDefinition; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.beans.factory.parsing.ComponentDefinition} + * implementation that holds a pointcut definition. + * + * @author Rob Harrop + * @since 2.0 + */ +public class PointcutComponentDefinition extends AbstractComponentDefinition { + + private final String pointcutBeanName; + + private final BeanDefinition pointcutDefinition; + + private final String description; + + + public PointcutComponentDefinition(String pointcutBeanName, BeanDefinition pointcutDefinition, String expression) { + Assert.notNull(pointcutBeanName, "Bean name must not be null"); + Assert.notNull(pointcutDefinition, "Pointcut definition must not be null"); + Assert.notNull(expression, "Expression must not be null"); + this.pointcutBeanName = pointcutBeanName; + this.pointcutDefinition = pointcutDefinition; + this.description = "Pointcut "; + } + + + public String getName() { + return this.pointcutBeanName; + } + + public String getDescription() { + return this.description; + } + + public BeanDefinition[] getBeanDefinitions() { + return new BeanDefinition[] {this.pointcutDefinition}; + } + + public Object getSource() { + return this.pointcutDefinition.getSource(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/PointcutEntry.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/PointcutEntry.java new file mode 100644 index 0000000000..c726178f7b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/PointcutEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2006 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.aop.config; + +import org.springframework.beans.factory.parsing.ParseState; + +/** + * {@link ParseState} entry representing a pointcut. + * + * @author Mark Fisher + * @since 2.0 + */ +public class PointcutEntry implements ParseState.Entry { + + private final String name; + + /** + * Creates a new instance of the {@link PointcutEntry} class. + * @param name the bean name of the pointcut + */ + public PointcutEntry(String name) { + this.name = name; + } + + public String toString() { + return "Pointcut '" + this.name + "'"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/ScopedProxyBeanDefinitionDecorator.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/ScopedProxyBeanDefinitionDecorator.java new file mode 100644 index 0000000000..e88ad1e8b8 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/ScopedProxyBeanDefinitionDecorator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionDecorator; +import org.springframework.beans.factory.xml.ParserContext; + +/** + * {@link BeanDefinitionDecorator} responsible for parsing the + * <aop:scoped-proxy/> tag. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.0 + */ +class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator { + + private static final String PROXY_TARGET_CLASS = "proxy-target-class"; + + + public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { + boolean proxyTargetClass = true; + if (node instanceof Element) { + Element ele = (Element) node; + if (ele.hasAttribute(PROXY_TARGET_CLASS)) { + proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS)).booleanValue(); + } + } + + // Register the original bean definition as it will be referenced by the scoped proxy and is relevant for tooling (validation, navigation). + String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName()); + parserContext.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName)); + + return ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java new file mode 100644 index 0000000000..6ffc8c8868 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/SimpleBeanFactoryAwareAspectInstanceFactory.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.springframework.aop.aspectj.AspectInstanceFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.Ordered; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link AspectInstanceFactory} that locates the aspect from the + * {@link org.springframework.beans.factory.BeanFactory} using a configured bean name. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public class SimpleBeanFactoryAwareAspectInstanceFactory implements AspectInstanceFactory, BeanFactoryAware { + + private String aspectBeanName; + + private BeanFactory beanFactory; + + + /** + * Set the name of the aspect bean. This is the bean that is returned when calling + * {@link #getAspectInstance()}. + */ + public void setAspectBeanName(String aspectBeanName) { + this.aspectBeanName = aspectBeanName; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + if (!StringUtils.hasText(this.aspectBeanName)) { + throw new IllegalArgumentException("'aspectBeanName' is required"); + } + } + + + /** + * Look up the aspect bean from the {@link BeanFactory} and returns it. + * @see #setAspectBeanName + */ + public Object getAspectInstance() { + return this.beanFactory.getBean(this.aspectBeanName); + } + + public ClassLoader getAspectClassLoader() { + if (this.beanFactory instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader(); + } + else { + return ClassUtils.getDefaultClassLoader(); + } + } + + public int getOrder() { + if (this.beanFactory.isSingleton(this.aspectBeanName) && + this.beanFactory.isTypeMatch(this.aspectBeanName, Ordered.class)) { + return ((Ordered) this.beanFactory.getBean(this.aspectBeanName)).getOrder(); + } + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/SpringConfiguredBeanDefinitionParser.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/SpringConfiguredBeanDefinitionParser.java new file mode 100644 index 0000000000..65668efa88 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/SpringConfiguredBeanDefinitionParser.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2007 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.aop.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; + +/** + * {@link BeanDefinitionParser} responsible for parsing the + * <aop:spring-configured/> tag. + * + *

NOTE: This is essentially a duplicate of Spring 2.5's + * {@link org.springframework.context.config.SpringConfiguredBeanDefinitionParser} + * for the <context:spring-configured/> tag, mirrored here + * for compatibility with Spring 2.0's <aop:spring-configured/> + * tag (avoiding a direct dependency on the context package). + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +class SpringConfiguredBeanDefinitionParser implements BeanDefinitionParser { + + /** + * The bean name of the internally managed bean configurer aspect. + */ + public static final String BEAN_CONFIGURER_ASPECT_BEAN_NAME = + "org.springframework.context.config.internalBeanConfigurerAspect"; + + private static final String BEAN_CONFIGURER_ASPECT_CLASS_NAME = + "org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"; + + + public BeanDefinition parse(Element element, ParserContext parserContext) { + if (!parserContext.getRegistry().containsBeanDefinition(BEAN_CONFIGURER_ASPECT_BEAN_NAME)) { + RootBeanDefinition def = new RootBeanDefinition(); + def.setBeanClassName(BEAN_CONFIGURER_ASPECT_CLASS_NAME); + def.setFactoryMethodName("aspectOf"); + def.setSource(parserContext.extractSource(element)); + parserContext.registerBeanComponent(new BeanComponentDefinition(def, BEAN_CONFIGURER_ASPECT_BEAN_NAME)); + } + return null; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/config/package.html new file mode 100644 index 0000000000..87d4b96fde --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/package.html @@ -0,0 +1,8 @@ + + + +Support package for declarative AOP configuration, +with XML schema being the primary configuration format. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/spring-aop-2.0.xsd b/org.springframework.aop/src/main/java/org/springframework/aop/config/spring-aop-2.0.xsd new file mode 100644 index 0000000000..20105447e0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/spring-aop-2.0.xsd @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/spring-aop-2.5.xsd b/org.springframework.aop/src/main/java/org/springframework/aop/config/spring-aop-2.5.xsd new file mode 100644 index 0000000000..40bf1ec51b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/spring-aop-2.5.xsd @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java new file mode 100644 index 0000000000..e3969666ed --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java @@ -0,0 +1,241 @@ +/* + * Copyright 2002-2008 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.aop.framework; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry; +import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry; +import org.springframework.aop.target.SingletonTargetSource; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.ClassUtils; + +/** + * Convenient proxy factory bean superclass for proxy factory + * beans that create only singletons. + * + *

Manages pre- and post-interceptors (references, rather than + * interceptor names, as in {@link ProxyFactoryBean}) and provides + * consistent interface management. + * + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig + implements FactoryBean, BeanClassLoaderAware, InitializingBean { + + private Object target; + + private Class[] proxyInterfaces; + + private Object[] preInterceptors; + + private Object[] postInterceptors; + + /** Default is global AdvisorAdapterRegistry */ + private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); + + private transient ClassLoader proxyClassLoader; + + private Object proxy; + + + /** + * Set the target object, that is, the bean to be wrapped with a transactional proxy. + *

The target may be any object, in which case a SingletonTargetSource will + * be created. If it is a TargetSource, no wrapper TargetSource is created: + * This enables the use of a pooling or prototype TargetSource etc. + * @see org.springframework.aop.TargetSource + * @see org.springframework.aop.target.SingletonTargetSource + * @see org.springframework.aop.target.LazyInitTargetSource + * @see org.springframework.aop.target.PrototypeTargetSource + * @see org.springframework.aop.target.CommonsPoolTargetSource + */ + public void setTarget(Object target) { + this.target = target; + } + + /** + * Specify the set of interfaces being proxied. + *

If not specified (the default), the AOP infrastructure works + * out which interfaces need proxying by analyzing the target, + * proxying all the interfaces that the target object implements. + */ + public void setProxyInterfaces(Class[] proxyInterfaces) { + this.proxyInterfaces = proxyInterfaces; + } + + /** + * Set additional interceptors (or advisors) to be applied before the + * implicit transaction interceptor, e.g. a PerformanceMonitorInterceptor. + *

You may specify any AOP Alliance MethodInterceptors or other + * Spring AOP Advices, as well as Spring AOP Advisors. + * @see org.springframework.aop.interceptor.PerformanceMonitorInterceptor + */ + public void setPreInterceptors(Object[] preInterceptors) { + this.preInterceptors = preInterceptors; + } + + /** + * Set additional interceptors (or advisors) to be applied after the + * implicit transaction interceptor. + *

You may specify any AOP Alliance MethodInterceptors or other + * Spring AOP Advices, as well as Spring AOP Advisors. + */ + public void setPostInterceptors(Object[] postInterceptors) { + this.postInterceptors = postInterceptors; + } + + /** + * Specify the AdvisorAdapterRegistry to use. + * Default is the global AdvisorAdapterRegistry. + * @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry + */ + public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { + this.advisorAdapterRegistry = advisorAdapterRegistry; + } + + /** + * Set the ClassLoader to generate the proxy class in. + *

Default is the bean ClassLoader, i.e. the ClassLoader used by the + * containing BeanFactory for loading all bean classes. This can be + * overridden here for specific proxies. + */ + public void setProxyClassLoader(ClassLoader classLoader) { + this.proxyClassLoader = classLoader; + } + + public void setBeanClassLoader(ClassLoader classLoader) { + if (this.proxyClassLoader == null) { + this.proxyClassLoader = classLoader; + } + } + + + public void afterPropertiesSet() { + if (this.target == null) { + throw new IllegalArgumentException("Property 'target' is required"); + } + if (this.target instanceof String) { + throw new IllegalArgumentException("'target' needs to be a bean reference, not a bean name as value"); + } + if (this.proxyClassLoader == null) { + this.proxyClassLoader = ClassUtils.getDefaultClassLoader(); + } + + ProxyFactory proxyFactory = new ProxyFactory(); + + if (this.preInterceptors != null) { + for (int i = 0; i < this.preInterceptors.length; i++) { + proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(this.preInterceptors[i])); + } + } + + // Add the main interceptor (typically an Advisor). + proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor())); + + if (this.postInterceptors != null) { + for (int i = 0; i < this.postInterceptors.length; i++) { + proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(this.postInterceptors[i])); + } + } + + proxyFactory.copyFrom(this); + + TargetSource targetSource = createTargetSource(this.target); + proxyFactory.setTargetSource(targetSource); + + if (this.proxyInterfaces != null) { + proxyFactory.setInterfaces(this.proxyInterfaces); + } + else if (!isProxyTargetClass()) { + // Rely on AOP infrastructure to tell us what interfaces to proxy. + proxyFactory.setInterfaces( + ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader)); + } + + this.proxy = getProxy(proxyFactory); + } + + /** + * Determine a TargetSource for the given target (or TargetSource). + * @param target target. If this is an implementation of TargetSource it is + * used as our TargetSource; otherwise it is wrapped in a SingletonTargetSource. + * @return a TargetSource for this object + */ + protected TargetSource createTargetSource(Object target) { + if (target instanceof TargetSource) { + return (TargetSource) target; + } + else { + return new SingletonTargetSource(target); + } + } + + /** + * Return the proxy object to expose. + *

The default implementation uses a getProxy call with + * the factory's bean class loader. Can be overridden to specify a + * custom class loader. + * @param aopProxy the prepared AopProxy instance to get the proxy from + * @return the proxy object to expose + * @see AopProxy#getProxy(ClassLoader) + */ + protected Object getProxy(AopProxy aopProxy) { + return aopProxy.getProxy(this.proxyClassLoader); + } + + + public Object getObject() { + if (this.proxy == null) { + throw new FactoryBeanNotInitializedException(); + } + return this.proxy; + } + + public Class getObjectType() { + if (this.proxy != null) { + return this.proxy.getClass(); + } + if (this.proxyInterfaces != null && this.proxyInterfaces.length == 1) { + return this.proxyInterfaces[0]; + } + if (this.target instanceof TargetSource) { + return ((TargetSource) this.target).getTargetClass(); + } + if (this.target != null) { + return this.target.getClass(); + } + return null; + } + + public final boolean isSingleton() { + return true; + } + + + /** + * Create the "main" interceptor for this proxy factory bean. + * Typically an Advisor, but can also be any type of Advice. + *

Pre-interceptors will be applied before, post-interceptors + * will be applied after this interceptor. + */ + protected abstract Object createMainInterceptor(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/Advised.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/Advised.java new file mode 100644 index 0000000000..397c97b769 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/Advised.java @@ -0,0 +1,228 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Advisor; +import org.springframework.aop.TargetClassAware; +import org.springframework.aop.TargetSource; + +/** + * Interface to be implemented by classes that hold the configuration + * of a factory of AOP proxies. This configuration includes the + * Interceptors and other advice, and Advisors, and the proxied interfaces. + * + *

Any AOP proxy obtained from Spring can be cast to this interface to + * allow manipulation of its AOP advice. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13.03.2003 + * @see org.springframework.aop.framework.AdvisedSupport + */ +public interface Advised extends TargetClassAware { + + /** + * Return whether the Advised configuration is frozen, + * in which case no advice changes can be made. + */ + boolean isFrozen(); + + /** + * Are we proxying the full target class instead of specified interfaces? + */ + boolean isProxyTargetClass(); + + /** + * Return the interfaces proxied by the AOP proxy. Will not + * include the target class, which may also be proxied. + */ + Class[] getProxiedInterfaces(); + + /** + * Determine whether the given interface is proxied. + * @param intf the interface to check + */ + boolean isInterfaceProxied(Class intf); + + + /** + * Change the TargetSource used by this Advised object. + * Only works if the configuration isn't frozen. + * @param targetSource new TargetSource to use + */ + void setTargetSource(TargetSource targetSource); + + /** + * Return the TargetSource used by this Advised object. + */ + TargetSource getTargetSource(); + + /** + * Set whether the proxy should be exposed by the AOP framework as a + * ThreadLocal for retrieval via the AopContext class. This is useful + * if an advised object needs to call another advised method on itself. + * (If it uses this, the invocation will not be advised). + *

Default is "false", for optimal performance. + */ + void setExposeProxy(boolean exposeProxy); + + /** + * Return whether the factory should expose the proxy as a ThreadLocal. + * This can be necessary if a target object needs to invoke a method on itself + * benefitting from advice. (If it invokes a method on this no advice + * will apply.) Getting the proxy is analogous to an EJB calling getEJBObject(). + * @see AopContext + */ + boolean isExposeProxy(); + + /** + * Set whether this proxy configuration is pre-filtered so that it only + * contains applicable advisors (matching this proxy's target class). + *

Default is "false". Set this to "true" if the advisors have been + * pre-filtered already, meaning that the ClassFilter check can be skipped + * when building the actual advisor chain for proxy invocations. + * @see org.springframework.aop.ClassFilter + */ + void setPreFiltered(boolean preFiltered); + + /** + * Return whether this proxy configuration is pre-filtered so that it only + * contains applicable advisors (matching this proxy's target class). + */ + boolean isPreFiltered(); + + + /** + * Return the advisors applying to this proxy. + * @return a list of Advisors applying to this proxy (never null) + */ + Advisor[] getAdvisors(); + + /** + * Add an advisor at the end of the advisor chain. + *

The Advisor may be an {@link org.springframework.aop.IntroductionAdvisor}, + * in which new interfaces will be available when a proxy is next obtained + * from the relevant factory. + * @param advisor the advisor to add to the end of the chain + * @throws AopConfigException in case of invalid advice + */ + void addAdvisor(Advisor advisor) throws AopConfigException; + + /** + * Add an Advisor at the specified position in the chain. + * @param advisor the advisor to add at the specified position in the chain + * @param pos position in chain (0 is head). Must be valid. + * @throws AopConfigException in case of invalid advice + */ + void addAdvisor(int pos, Advisor advisor) throws AopConfigException; + + /** + * Remove the given advisor. + * @param advisor the advisor to remove + * @return true if the advisor was removed; false + * if the advisor was not found and hence could not be removed + */ + boolean removeAdvisor(Advisor advisor); + + /** + * Remove the advisor at the given index. + * @param index index of advisor to remove + * @throws AopConfigException if the index is invalid + */ + void removeAdvisor(int index) throws AopConfigException; + + /** + * Return the index (from 0) of the given advisor, + * or -1 if no such advisor applies to this proxy. + *

The return value of this method can be used to index into the advisors array. + * @param advisor the advisor to search for + * @return index from 0 of this advisor, or -1 if there's no such advisor + */ + int indexOf(Advisor advisor); + + /** + * Replace the given advisor. + *

Note: If the advisor is an {@link org.springframework.aop.IntroductionAdvisor} + * and the replacement is not or implements different interfaces, the proxy will need + * to be re-obtained or the old interfaces won't be supported and the new interface + * won't be implemented. + * @param a the advisor to replace + * @param b the advisor to replace it with + * @return whether it was replaced. If the advisor wasn't found in the + * list of advisors, this method returns false and does nothing. + * @throws AopConfigException in case of invalid advice + */ + boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; + + + /** + * Add the given AOP Alliance advice to the tail of the advice (interceptor) chain. + *

This will be wrapped in a DefaultPointcutAdvisor with a pointcut that always + * applies, and returned from the getAdvisors() method in this wrapped form. + *

Note that the given advice will apply to all invocations on the proxy, + * even to the toString() method! Use appropriate advice implementations + * or specify appropriate pointcuts to apply to a narrower set of methods. + * @param advice advice to add to the tail of the chain + * @throws AopConfigException in case of invalid advice + * @see #addAdvice(int, Advice) + * @see org.springframework.aop.support.DefaultPointcutAdvisor + */ + void addAdvice(Advice advice) throws AopConfigException; + + /** + * Add the given AOP Alliance Advice at the specified position in the advice chain. + *

This will be wrapped in a {@link org.springframework.aop.support.DefaultPointcutAdvisor} + * with a pointcut that always applies, and returned from the {@link #getAdvisors()} + * method in this wrapped form. + *

Note: The given advice will apply to all invocations on the proxy, + * even to the toString() method! Use appropriate advice implementations + * or specify appropriate pointcuts to apply to a narrower set of methods. + * @param pos index from 0 (head) + * @param advice advice to add at the specified position in the advice chain + * @throws AopConfigException in case of invalid advice + */ + void addAdvice(int pos, Advice advice) throws AopConfigException; + + /** + * Remove the Advisor containing the given advice. + * @param advice the advice to remove + * @return true of the advice was found and removed; + * false if there was no such advice + */ + boolean removeAdvice(Advice advice); + + /** + * Return the index (from 0) of the given AOP Alliance Advice, + * or -1 if no such advice is an advice for this proxy. + *

The return value of this method can be used to index into + * the advisors array. + * @param advice AOP Alliance advice to search for + * @return index from 0 of this advice, or -1 if there's no such advice + */ + int indexOf(Advice advice); + + + /** + * As toString() will normally be delegated to the target, + * this returns the equivalent for the AOP proxy. + * @return a string description of the proxy configuration + */ + String toProxyConfigString(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java new file mode 100644 index 0000000000..e1e40466da --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisedSupport.java @@ -0,0 +1,590 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Advisor; +import org.springframework.aop.DynamicIntroductionAdvice; +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.IntroductionInfo; +import org.springframework.aop.TargetSource; +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.target.EmptyTargetSource; +import org.springframework.aop.target.SingletonTargetSource; +import org.springframework.core.CollectionFactory; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Base class for AOP proxy configuration managers. + * These are not themselves AOP proxies, but subclasses of this class are + * normally factories from which AOP proxy instances are obtained directly. + * + *

This class frees subclasses of the housekeeping of Advices + * and Advisors, but doesn't actually implement proxy creation + * methods, which are provided by subclasses. + * + *

This class is serializable; subclasses need not be. + * This class is used to hold snapshots of proxies. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.aop.framework.AopProxy + */ +public class AdvisedSupport extends ProxyConfig implements Advised { + + /** use serialVersionUID from Spring 2.0 for interoperability */ + private static final long serialVersionUID = 2651364800145442165L; + + + /** + * Canonical TargetSource when there's no target, and behavior is + * supplied by the advisors. + */ + public static final TargetSource EMPTY_TARGET_SOURCE = EmptyTargetSource.INSTANCE; + + + /** Package-protected to allow direct access for efficiency */ + TargetSource targetSource = EMPTY_TARGET_SOURCE; + + /** Whether the Advisors are already filtered for the specific target class */ + private boolean preFiltered = false; + + /** The AdvisorChainFactory to use */ + AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory(); + + /** Cache with Method as key and advisor chain List as value */ + private transient Map methodCache; + + /** + * Interfaces to be implemented by the proxy. Held in List to keep the order + * of registration, to create JDK proxy with specified order of interfaces. + */ + private List interfaces = new ArrayList(); + + /** + * List of Advisors. If an Advice is added, it will be wrapped + * in an Advisor before being added to this List. + */ + private List advisors = new LinkedList(); + + /** + * Array updated on changes to the advisors list, which is easier + * to manipulate internally. + */ + private Advisor[] advisorArray = new Advisor[0]; + + + /** + * No-arg constructor for use as a JavaBean. + */ + public AdvisedSupport() { + initMethodCache(); + } + + /** + * Create a AdvisedSupport instance with the given parameters. + * @param interfaces the proxied interfaces + */ + public AdvisedSupport(Class[] interfaces) { + this(); + setInterfaces(interfaces); + } + + /** + * Initialize the method cache. + */ + private void initMethodCache() { + this.methodCache = CollectionFactory.createConcurrentMapIfPossible(32); + } + + + /** + * Set the given object as target. + * Will create a SingletonTargetSource for the object. + * @see #setTargetSource + * @see org.springframework.aop.target.SingletonTargetSource + */ + public void setTarget(Object target) { + setTargetSource(new SingletonTargetSource(target)); + } + + public void setTargetSource(TargetSource targetSource) { + this.targetSource = (targetSource != null ? targetSource : EMPTY_TARGET_SOURCE); + } + + public TargetSource getTargetSource() { + return this.targetSource; + } + + /** + * Set a target class to be proxied, indicating that the proxy + * should be castable to the given class. + *

Internally, an {@link org.springframework.aop.target.EmptyTargetSource} + * for the given target class will be used. The kind of proxy needed + * will be determined on actual creation of the proxy. + *

This is a replacement for setting a "targetSource" or "target", + * for the case where we want a proxy based on a target class + * (which can be an interface or a concrete class) without having + * a fully capable TargetSource available. + * @see #setTargetSource + * @see #setTarget + */ + public void setTargetClass(Class targetClass) { + this.targetSource = EmptyTargetSource.forClass(targetClass); + } + + public Class getTargetClass() { + return this.targetSource.getTargetClass(); + } + + public void setPreFiltered(boolean preFiltered) { + this.preFiltered = preFiltered; + } + + public boolean isPreFiltered() { + return this.preFiltered; + } + + /** + * Set the advisor chain factory to use. + *

Default is a {@link DefaultAdvisorChainFactory}. + */ + public void setAdvisorChainFactory(AdvisorChainFactory advisorChainFactory) { + Assert.notNull(advisorChainFactory, "AdvisorChainFactory must not be null"); + this.advisorChainFactory = advisorChainFactory; + } + + /** + * Return the advisor chain factory to use (never null). + */ + public AdvisorChainFactory getAdvisorChainFactory() { + return this.advisorChainFactory; + } + + + /** + * Set the interfaces to be proxied. + */ + public void setInterfaces(Class[] interfaces) { + Assert.notNull(interfaces, "Interfaces must not be null"); + this.interfaces.clear(); + for (int i = 0; i < interfaces.length; i++) { + addInterface(interfaces[i]); + } + } + + /** + * Add a new proxied interface. + * @param intf the additional interface to proxy + */ + public void addInterface(Class intf) { + Assert.notNull(intf, "Interface must not be null"); + if (!intf.isInterface()) { + throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface"); + } + if (!this.interfaces.contains(intf)) { + this.interfaces.add(intf); + adviceChanged(); + } + } + + /** + * Remove a proxied interface. + *

Does nothing if the given interface isn't proxied. + * @param intf the interface to remove from the proxy + * @return true if the interface was removed; false + * if the interface was not found and hence could not be removed + */ + public boolean removeInterface(Class intf) { + return this.interfaces.remove(intf); + } + + public Class[] getProxiedInterfaces() { + return (Class[]) this.interfaces.toArray(new Class[this.interfaces.size()]); + } + + public boolean isInterfaceProxied(Class intf) { + for (Iterator it = this.interfaces.iterator(); it.hasNext();) { + Class proxyIntf = (Class) it.next(); + if (intf.isAssignableFrom(proxyIntf)) { + return true; + } + } + return false; + } + + + public final Advisor[] getAdvisors() { + return this.advisorArray; + } + + public void addAdvisor(Advisor advisor) { + int pos = this.advisors.size(); + addAdvisor(pos, advisor); + } + + public void addAdvisor(int pos, Advisor advisor) throws AopConfigException { + if (advisor instanceof IntroductionAdvisor) { + validateIntroductionAdvisor((IntroductionAdvisor) advisor); + } + addAdvisorInternal(pos, advisor); + } + + public boolean removeAdvisor(Advisor advisor) { + int index = indexOf(advisor); + if (index == -1) { + return false; + } + else { + removeAdvisor(index); + return true; + } + } + + public void removeAdvisor(int index) throws AopConfigException { + if (isFrozen()) { + throw new AopConfigException("Cannot remove Advisor: Configuration is frozen."); + } + if (index < 0 || index > this.advisors.size() - 1) { + throw new AopConfigException("Advisor index " + index + " is out of bounds: " + + "This configuration only has " + this.advisors.size() + " advisors."); + } + + Advisor advisor = (Advisor) this.advisors.get(index); + if (advisor instanceof IntroductionAdvisor) { + IntroductionAdvisor ia = (IntroductionAdvisor) advisor; + // We need to remove introduction interfaces. + for (int j = 0; j < ia.getInterfaces().length; j++) { + removeInterface(ia.getInterfaces()[j]); + } + } + + this.advisors.remove(index); + updateAdvisorArray(); + adviceChanged(); + } + + public int indexOf(Advisor advisor) { + Assert.notNull(advisor, "Advisor must not be null"); + return this.advisors.indexOf(advisor); + } + + public boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException { + Assert.notNull(a, "Advisor a must not be null"); + Assert.notNull(b, "Advisor b must not be null"); + int index = indexOf(a); + if (index == -1) { + return false; + } + removeAdvisor(index); + addAdvisor(index, b); + return true; + } + + /** + * Add all of the given advisors to this proxy configuration. + * @param advisors the advisors to register + */ + public void addAllAdvisors(Advisor[] advisors) { + if (isFrozen()) { + throw new AopConfigException("Cannot add advisor: Configuration is frozen."); + } + if (!ObjectUtils.isEmpty(advisors)) { + for (int i = 0; i < advisors.length; i++) { + Advisor advisor = advisors[i]; + if (advisor instanceof IntroductionAdvisor) { + validateIntroductionAdvisor((IntroductionAdvisor) advisor); + } + Assert.notNull(advisor, "Advisor must not be null"); + this.advisors.add(advisor); + } + updateAdvisorArray(); + adviceChanged(); + } + } + + private void validateIntroductionAdvisor(IntroductionAdvisor advisor) { + advisor.validateInterfaces(); + // If the advisor passed validation, we can make the change. + Class[] ifcs = advisor.getInterfaces(); + for (int i = 0; i < ifcs.length; i++) { + addInterface(ifcs[i]); + } + } + + private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException { + Assert.notNull(advisor, "Advisor must not be null"); + if (isFrozen()) { + throw new AopConfigException("Cannot add advisor: Configuration is frozen."); + } + if (pos > this.advisors.size()) { + throw new IllegalArgumentException( + "Illegal position " + pos + " in advisor list with size " + this.advisors.size()); + } + this.advisors.add(pos, advisor); + updateAdvisorArray(); + adviceChanged(); + } + + /** + * Bring the array up to date with the list. + */ + protected final void updateAdvisorArray() { + this.advisorArray = (Advisor[]) this.advisors.toArray(new Advisor[this.advisors.size()]); + } + + /** + * Allows uncontrolled access to the {@link List} of {@link Advisor Advisors}. + *

Use with care, and remember to {@link #updateAdvisorArray() refresh the advisor array} + * and {@link #adviceChanged() fire advice changed events} when making any modifications. + */ + protected final List getAdvisorsInternal() { + return this.advisors; + } + + + public void addAdvice(Advice advice) throws AopConfigException { + int pos = this.advisors.size(); + addAdvice(pos, advice); + } + + /** + * Cannot add introductions this way unless the advice implements IntroductionInfo. + */ + public void addAdvice(int pos, Advice advice) throws AopConfigException { + Assert.notNull(advice, "Advice must not be null"); + if (advice instanceof IntroductionInfo) { + // We don't need an IntroductionAdvisor for this kind of introduction: + // It's fully self-describing. + addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice)); + } + else if (advice instanceof DynamicIntroductionAdvice) { + // We need an IntroductionAdvisor for this kind of introduction. + throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor"); + } + else { + addAdvisor(pos, new DefaultPointcutAdvisor(advice)); + } + } + + public boolean removeAdvice(Advice advice) throws AopConfigException { + int index = indexOf(advice); + if (index == -1) { + return false; + } + else { + removeAdvisor(index); + return true; + } + } + + public int indexOf(Advice advice) { + Assert.notNull(advice, "Advice must not be null"); + for (int i = 0; i < this.advisors.size(); i++) { + Advisor advisor = (Advisor) this.advisors.get(i); + if (advisor.getAdvice() == advice) { + return i; + } + } + return -1; + } + + /** + * Is the given advice included in any advisor within this proxy configuration? + * @param advice the advice to check inclusion of + * @return whether this advice instance is included + */ + public boolean adviceIncluded(Advice advice) { + Assert.notNull(advice, "Advice must not be null"); + for (int i = 0; i < this.advisors.size(); i++) { + Advisor advisor = (Advisor) this.advisors.get(i); + if (advisor.getAdvice() == advice) { + return true; + } + } + return false; + } + + /** + * Count advices of the given class. + * @param adviceClass the advice class to check + * @return the count of the interceptors of this class or subclasses + */ + public int countAdvicesOfType(Class adviceClass) { + Assert.notNull(adviceClass, "Advice class must not be null"); + int count = 0; + for (int i = 0; i < this.advisors.size(); i++) { + Advisor advisor = (Advisor) this.advisors.get(i); + if (advisor.getAdvice() != null && + adviceClass.isAssignableFrom(advisor.getAdvice().getClass())) { + count++; + } + } + return count; + } + + + /** + * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects + * for the given method, based on this configuration. + * @param method the proxied method + * @param targetClass the target class + * @return List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers) + */ + public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) { + MethodCacheKey cacheKey = new MethodCacheKey(method); + List cached = (List) this.methodCache.get(cacheKey); + if (cached == null) { + cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( + this, method, targetClass); + this.methodCache.put(cacheKey, cached); + } + return cached; + } + + /** + * Invoked when advice has changed. + */ + protected void adviceChanged() { + synchronized (this.methodCache) { + this.methodCache.clear(); + } + } + + /** + * Call this method on a new instance created by the no-arg constructor + * to create an independent copy of the configuration from the given object. + * @param other the AdvisedSupport object to copy configuration from + */ + protected void copyConfigurationFrom(AdvisedSupport other) { + copyConfigurationFrom(other, other.targetSource, new ArrayList(other.advisors)); + } + + /** + * Copy the AOP configuration from the given AdvisedSupport object, + * but allow substitution of a fresh TargetSource and a given interceptor chain. + * @param other the AdvisedSupport object to take proxy configuration from + * @param targetSource the new TargetSource + * @param advisors the Advisors for the chain + */ + protected void copyConfigurationFrom(AdvisedSupport other, TargetSource targetSource, List advisors) { + copyFrom(other); + this.targetSource = targetSource; + this.advisorChainFactory = other.advisorChainFactory; + this.interfaces = new ArrayList(other.interfaces); + for (Iterator it = advisors.iterator(); it.hasNext();) { + Advisor advisor = (Advisor) it.next(); + if (advisor instanceof IntroductionAdvisor) { + validateIntroductionAdvisor((IntroductionAdvisor) advisor); + } + Assert.notNull(advisor, "Advisor must not be null"); + this.advisors.add(advisor); + } + updateAdvisorArray(); + adviceChanged(); + } + + /** + * Build a configuration-only copy of this AdvisedSupport, + * replacing the TargetSource + */ + AdvisedSupport getConfigurationOnlyCopy() { + AdvisedSupport copy = new AdvisedSupport(); + copy.copyFrom(this); + copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic()); + copy.advisorChainFactory = this.advisorChainFactory; + copy.interfaces = this.interfaces; + copy.advisors = this.advisors; + copy.updateAdvisorArray(); + return copy; + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Initialize transient fields. + initMethodCache(); + } + + + public String toProxyConfigString() { + return toString(); + } + + /** + * For debugging/diagnostic use. + */ + public String toString() { + StringBuffer sb = new StringBuffer(getClass().getName() + ": "); + sb.append(this.interfaces.size()).append(" interfaces "); + sb.append(ClassUtils.classNamesToString(this.interfaces)).append("; "); + sb.append(this.advisors.size()).append(" advisors "); + sb.append(this.advisors).append("; "); + sb.append("targetSource [").append(this.targetSource).append("]; "); + sb.append(super.toString()); + return sb.toString(); + } + + + /** + * Simple wrapper class around a Method. Used as the key when + * caching methods, for efficient equals and hashCode comparisons. + */ + private static class MethodCacheKey { + + private final Method method; + + private final int hashCode; + + public MethodCacheKey(Method method) { + this.method = method; + this.hashCode = method.hashCode(); + } + + public boolean equals(Object other) { + if (other == this) { + return true; + } + MethodCacheKey otherKey = (MethodCacheKey) other; + return (this.method == otherKey.method); + } + + public int hashCode() { + return this.hashCode; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisedSupportListener.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisedSupportListener.java new file mode 100644 index 0000000000..f7c71d5d35 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisedSupportListener.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +/** + * Listener to be registered on {@link ProxyCreatorSupport} objects + * Allows for receiving callbacks on activation and change of advice. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see ProxyCreatorSupport#addListener + */ +public interface AdvisedSupportListener { + + /** + * Invoked when the first proxy is created. + * @param advised the AdvisedSupport object + */ + void activated(AdvisedSupport advised); + + /** + * Invoked when advice is changed after a proxy is created. + * @param advised the AdvisedSupport object + */ + void adviceChanged(AdvisedSupport advised); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java new file mode 100644 index 0000000000..fcaaf7d33e --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AdvisorChainFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * Factory interface for advisor chains. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface AdvisorChainFactory { + + /** + * Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects + * for the given advisor chain configuration. + * @param config the AOP configuration in the form of an Advised object + * @param method the proxied method + * @param targetClass the target class + * @return List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers) + */ + List getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, Class targetClass); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopConfigException.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopConfigException.java new file mode 100644 index 0000000000..46cd1075ac --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopConfigException.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2006 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.aop.framework; + +import org.springframework.core.NestedRuntimeException; + +/** + * Exception that gets thrown on illegal AOP configuration arguments. + * + * @author Rod Johnson + * @since 13.03.2003 + */ +public class AopConfigException extends NestedRuntimeException { + + /** + * Constructor for AopConfigException. + * @param msg the detail message + */ + public AopConfigException(String msg) { + super(msg); + } + + /** + * Constructor for AopConfigException. + * @param msg the detail message + * @param cause the root cause + */ + public AopConfigException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopContext.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopContext.java new file mode 100644 index 0000000000..c5e62ae36c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopContext.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2008 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.aop.framework; + +import org.springframework.core.NamedThreadLocal; + +/** + * Class containing static methods used to obtain information about the current AOP invocation. + * + *

The currentProxy() method is usable if the AOP framework is configured to + * expose the current proxy (not the default). It returns the AOP proxy in use. Target objects + * or advice can use this to make advised calls, in the same way as getEJBObject() + * can be used in EJBs. They can also use it to find advice configuration. + * + *

Spring's AOP framework does not expose proxies by default, as there is a performance cost + * in doing so. + * + *

The functionality in this class might be used by a target object that needed access + * to resources on the invocation. However, this approach should not be used when there is + * a reasonable alternative, as it makes application code dependent on usage under AOP and + * the Spring AOP framework in particular. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 13.03.2003 + */ +public abstract class AopContext { + + /** + * ThreadLocal holder for AOP proxy associated with this thread. + * Will contain null unless the "exposeProxy" property on + * the controlling proxy configuration has been set to "true". + * @see ProxyConfig#setExposeProxy + */ + private static final ThreadLocal currentProxy = new NamedThreadLocal("Current AOP proxy"); + + + /** + * Try to return the current AOP proxy. This method is usable only if the + * calling method has been invoked via AOP, and the AOP framework has been set + * to expose proxies. Otherwise, this method will throw an IllegalStateException. + * @return Object the current AOP proxy (never returns null) + * @throws IllegalStateException if the proxy cannot be found, because the + * method was invoked outside an AOP invocation context, or because the + * AOP framework has not been configured to expose the proxy + */ + public static Object currentProxy() throws IllegalStateException { + Object proxy = currentProxy.get(); + if (proxy == null) { + throw new IllegalStateException( + "Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available."); + } + return proxy; + } + + /** + * Make the given proxy available via the currentProxy() method. + *

Note that the caller should be careful to keep the old value as appropriate. + * @param proxy the proxy to expose (or null to reset it) + * @return the old proxy, which may be null if none was bound + * @see #currentProxy() + */ + static Object setCurrentProxy(Object proxy) { + Object old = currentProxy.get(); + currentProxy.set(proxy); + return old; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopInfrastructureBean.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopInfrastructureBean.java new file mode 100644 index 0000000000..852d05d2ab --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopInfrastructureBean.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +/** + * Marker interface that indicates a bean that is part of Spring's + * AOP infrastructure. In particular, this implies that any such bean + * is not subject to auto-proxying, even if a pointcut would match. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator + * @see org.springframework.aop.scope.ScopedProxyFactoryBean + */ +public interface AopInfrastructureBean { + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxy.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxy.java new file mode 100644 index 0000000000..2fe5af6c35 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +/** + * Delegate interface for a configured AOP proxy, allowing for the creation + * of actual proxy objects. + * + *

Out-of-the-box implementations are available for JDK dynamic proxies + * and for CGLIB proxies, as applied by {@link DefaultAopProxyFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see DefaultAopProxyFactory + */ +public interface AopProxy { + + /** + * Create a new proxy object. + *

Uses the AopProxy's default class loader (if necessary for proxy creation): + * usually, the thread context class loader. + * @return the new proxy object (never null) + * @see java.lang.Thread#getContextClassLoader() + */ + Object getProxy(); + + /** + * Create a new proxy object. + *

Uses the given class loader (if necessary for proxy creation). + * null will simply be passed down and thus lead to the low-level + * proxy facility's default, which is usually different from the default chosen + * by the AopProxy implementation's {@link #getProxy()} method. + * @param classLoader the class loader to create the proxy with + * (or null for the low-level proxy facility's default) + * @return the new proxy object (never null) + */ + Object getProxy(ClassLoader classLoader); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxyFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxyFactory.java new file mode 100644 index 0000000000..d97934f16c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxyFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +/** + * Interface to be implemented by factories that are able to create + * AOP proxies based on {@link AdvisedSupport} configuration objects. + * + *

Proxies should observe the following contract: + *

    + *
  • They should implement all interfaces that the configuration + * indicates should be proxied. + *
  • They should implement the {@link Advised} interface. + *
  • They should implement the equals method to compare proxied + * interfaces, advice, and target. + *
  • They should be serializable if all advisors and target + * are serializable. + *
  • They should be thread-safe if advisors and target + * are thread-safe. + *
+ * + *

Proxies may or may not allow advice changes to be made. + * If they do not permit advice changes (for example, because + * the configuration was frozen) a proxy should throw an + * {@link AopConfigException} on an attempted advice change. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface AopProxyFactory { + + /** + * Create an {@link AopProxy} for the given AOP configuration. + * @param config the AOP configuration in the form of an + * AdvisedSupport object + * @return the corresponding AOP proxy + * @throws AopConfigException if the configuration is invalid + */ + AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java new file mode 100644 index 0000000000..c3b59c511c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/AopProxyUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.util.Arrays; + +import org.springframework.aop.SpringProxy; +import org.springframework.aop.support.AopUtils; +import org.springframework.util.Assert; + +/** + * Utility methods for AOP proxy factories. + * Mainly for internal use within the AOP framework. + * + *

See {@link org.springframework.aop.support.AopUtils} for a collection of + * generic AOP utility methods which do not depend on AOP framework internals. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.aop.support.AopUtils + */ +public abstract class AopProxyUtils { + + /** + * Determine the target class of the given bean instance, + * which might be an AOP proxy. + *

Returns the target class for an AOP proxy and the plain class else. + * @param candidate the instance to check (might be an AOP proxy) + * @return the target class (or the plain class of the given object as fallback) + * @deprecated as of Spring 2.0.3, in favor of AopUtils.getTargetClass + * @see org.springframework.aop.support.AopUtils#getTargetClass(Object) + */ + public static Class getTargetClass(Object candidate) { + Assert.notNull(candidate, "Candidate object must not be null"); + if (AopUtils.isCglibProxy(candidate)) { + return candidate.getClass().getSuperclass(); + } + if (candidate instanceof Advised) { + return ((Advised) candidate).getTargetSource().getTargetClass(); + } + return candidate.getClass(); + } + + /** + * Determine the complete set of interfaces to proxy for the given AOP configuration. + *

This will always add the {@link Advised} interface unless the AdvisedSupport's + * {@link AdvisedSupport#setOpaque "opaque"} flag is on. Always adds the + * {@link org.springframework.aop.SpringProxy} marker interface. + * @return the complete set of interfaces to proxy + * @see Advised + * @see org.springframework.aop.SpringProxy + */ + public static Class[] completeProxiedInterfaces(AdvisedSupport advised) { + Class[] specifiedInterfaces = advised.getProxiedInterfaces(); + if (specifiedInterfaces.length == 0) { + // No user-specified interfaces: check whether target class is an interface. + Class targetClass = advised.getTargetClass(); + if (targetClass != null && targetClass.isInterface()) { + specifiedInterfaces = new Class[] {targetClass}; + } + } + boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); + boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); + int nonUserIfcCount = 0; + if (addSpringProxy) { + nonUserIfcCount++; + } + if (addAdvised) { + nonUserIfcCount++; + } + Class[] proxiedInterfaces = new Class[specifiedInterfaces.length + nonUserIfcCount]; + System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); + if (addSpringProxy) { + proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class; + } + if (addAdvised) { + proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class; + } + return proxiedInterfaces; + } + + /** + * Extract the user-specified interfaces that the given proxy implements, + * i.e. all non-Advised interfaces that the proxy implements. + * @param proxy the proxy to analyze (usually a JDK dynamic proxy) + * @return all user-specified interfaces that the proxy implements, + * in the original order (never null or empty) + * @see Advised + */ + public static Class[] proxiedUserInterfaces(Object proxy) { + Class[] proxyInterfaces = proxy.getClass().getInterfaces(); + int nonUserIfcCount = 0; + if (proxy instanceof SpringProxy) { + nonUserIfcCount++; + } + if (proxy instanceof Advised) { + nonUserIfcCount++; + } + Class[] userInterfaces = new Class[proxyInterfaces.length - nonUserIfcCount]; + System.arraycopy(proxyInterfaces, 0, userInterfaces, 0, userInterfaces.length); + Assert.notEmpty(userInterfaces, "JDK proxy must implement one or more interfaces"); + return userInterfaces; + } + + /** + * Check equality of the proxies behind the given AdvisedSupport objects. + * Not the same as equality of the AdvisedSupport objects: + * rather, equality of interfaces, advisors and target sources. + */ + public static boolean equalsInProxy(AdvisedSupport a, AdvisedSupport b) { + return (a == b || + (equalsProxiedInterfaces(a, b) && equalsAdvisors(a, b) && a.getTargetSource().equals(b.getTargetSource()))); + } + + /** + * Check equality of the proxied interfaces behind the given AdvisedSupport objects. + */ + public static boolean equalsProxiedInterfaces(AdvisedSupport a, AdvisedSupport b) { + return Arrays.equals(a.getProxiedInterfaces(), b.getProxiedInterfaces()); + } + + /** + * Check equality of the advisors behind the given AdvisedSupport objects. + */ + public static boolean equalsAdvisors(AdvisedSupport a, AdvisedSupport b) { + return Arrays.equals(a.getAdvisors(), b.getAdvisors()); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/Cglib2AopProxy.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/Cglib2AopProxy.java new file mode 100644 index 0000000000..8d6caee8a3 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/Cglib2AopProxy.java @@ -0,0 +1,935 @@ +/* + * Copyright 2002-2008 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.aop.framework; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import net.sf.cglib.core.CodeGenerationException; +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.Dispatcher; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.Factory; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import net.sf.cglib.proxy.NoOp; +import net.sf.cglib.transform.impl.UndeclaredThrowableStrategy; +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.Advisor; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.RawTargetAccess; +import org.springframework.aop.TargetSource; +import org.springframework.aop.support.AopUtils; +import org.springframework.core.SmartClassLoader; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * CGLIB2-based {@link AopProxy} implementation for the Spring AOP framework. + * + *

Requires CGLIB 2.1+ on the classpath.. + * As of Spring 2.0, earlier CGLIB versions are not supported anymore. + * + *

Objects of this type should be obtained through proxy factories, + * configured by an {@link AdvisedSupport} object. This class is internal + * to Spring's AOP framework and need not be used directly by client code. + * + *

{@link DefaultAopProxyFactory} will automatically create CGLIB2-based + * proxies if necessary, for example in case of proxying a target class + * (see the {@link DefaultAopProxyFactory attendant javadoc} for details). + * + *

Proxies created using this class are thread-safe if the underlying + * (target) class is thread-safe. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @see net.sf.cglib.proxy.Enhancer + * @see AdvisedSupport#setProxyTargetClass + * @see DefaultAopProxyFactory + */ +final class Cglib2AopProxy implements AopProxy, Serializable { + + // Constants for CGLIB callback array indices + private static final int AOP_PROXY = 0; + private static final int INVOKE_TARGET = 1; + private static final int NO_OVERRIDE = 2; + private static final int DISPATCH_TARGET = 3; + private static final int DISPATCH_ADVISED = 4; + private static final int INVOKE_EQUALS = 5; + private static final int INVOKE_HASHCODE = 6; + + + /** Logger available to subclasses; static to optimize serialization */ + protected final static Log logger = LogFactory.getLog(Cglib2AopProxy.class); + + /** Keeps track of the Classes that we have validated for final methods */ + private static final Map validatedClasses = new WeakHashMap(); + + + /** The configuration used to configure this proxy */ + protected final AdvisedSupport advised; + + private Object[] constructorArgs; + + private Class[] constructorArgTypes; + + /** Dispatcher used for methods on Advised */ + private final transient AdvisedDispatcher advisedDispatcher; + + private transient Map fixedInterceptorMap; + + private transient int fixedInterceptorOffset; + + + /** + * Create a new Cglib2AopProxy for the given AOP configuration. + * @param config the AOP configuration as AdvisedSupport object + * @throws AopConfigException if the config is invalid. We try to throw an informative + * exception in this case, rather than let a mysterious failure happen later. + */ + public Cglib2AopProxy(AdvisedSupport config) throws AopConfigException { + Assert.notNull(config, "AdvisedSupport must not be null"); + if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { + throw new AopConfigException("No advisors and no TargetSource specified"); + } + this.advised = config; + this.advisedDispatcher = new AdvisedDispatcher(this.advised); + } + + /** + * Set constructor arguments to use for creating the proxy. + * @param constructorArgs the constructor argument values + * @param constructorArgTypes the constructor argument types + */ + public void setConstructorArguments(Object[] constructorArgs, Class[] constructorArgTypes) { + if (constructorArgs == null || constructorArgTypes == null) { + throw new IllegalArgumentException("Both 'constructorArgs' and 'constructorArgTypes' need to be specified"); + } + if (constructorArgs.length != constructorArgTypes.length) { + throw new IllegalArgumentException("Number of 'constructorArgs' (" + constructorArgs.length + + ") must match number of 'constructorArgTypes' (" + constructorArgTypes.length + ")"); + } + this.constructorArgs = constructorArgs; + this.constructorArgTypes = constructorArgTypes; + } + + + public Object getProxy() { + return getProxy(null); + } + + public Object getProxy(ClassLoader classLoader) { + if (logger.isDebugEnabled()) { + logger.debug("Creating CGLIB2 proxy: target source is " + this.advised.getTargetSource()); + } + + try { + Class rootClass = this.advised.getTargetClass(); + Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); + + Class proxySuperClass = rootClass; + if (AopUtils.isCglibProxyClass(rootClass)) { + proxySuperClass = rootClass.getSuperclass(); + Class[] additionalInterfaces = rootClass.getInterfaces(); + for (int i = 0; i < additionalInterfaces.length; i++) { + Class additionalInterface = additionalInterfaces[i]; + this.advised.addInterface(additionalInterface); + } + } + + // Validate the class, writing log messages as necessary. + validateClassIfNecessary(proxySuperClass); + + // Configure CGLIB Enhancer... + Enhancer enhancer = createEnhancer(); + if (classLoader != null) { + enhancer.setClassLoader(classLoader); + if (classLoader instanceof SmartClassLoader && + ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) { + enhancer.setUseCache(false); + } + } + enhancer.setSuperclass(proxySuperClass); + enhancer.setStrategy(new UndeclaredThrowableStrategy(UndeclaredThrowableException.class)); + enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); + enhancer.setInterceptDuringConstruction(false); + + Callback[] callbacks = getCallbacks(rootClass); + enhancer.setCallbacks(callbacks); + enhancer.setCallbackFilter(new ProxyCallbackFilter( + this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); + + Class[] types = new Class[callbacks.length]; + for (int x = 0; x < types.length; x++) { + types[x] = callbacks[x].getClass(); + } + enhancer.setCallbackTypes(types); + + // Generate the proxy class and create a proxy instance. + Object proxy; + if (this.constructorArgs != null) { + proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs); + } + else { + proxy = enhancer.create(); + } + + return proxy; + } + catch (CodeGenerationException ex) { + throw new AopConfigException("Could not generate CGLIB subclass of class [" + + this.advised.getTargetClass() + "]: " + + "Common causes of this problem include using a final class or a non-visible class", + ex); + } + catch (IllegalArgumentException ex) { + throw new AopConfigException("Could not generate CGLIB subclass of class [" + + this.advised.getTargetClass() + "]: " + + "Common causes of this problem include using a final class or a non-visible class", + ex); + } + catch (Exception ex) { + // TargetSource.getTarget() failed + throw new AopConfigException("Unexpected AOP exception", ex); + } + } + + /** + * Creates the CGLIB {@link Enhancer}. Subclasses may wish to override this to return a custom + * {@link Enhancer} implementation. + */ + protected Enhancer createEnhancer() { + return new Enhancer(); + } + + /** + * Checks to see whether the supplied Class has already been validated and + * validates it if not. + */ + private void validateClassIfNecessary(Class proxySuperClass) { + if (logger.isWarnEnabled()) { + synchronized (validatedClasses) { + if (!validatedClasses.containsKey(proxySuperClass)) { + doValidateClass(proxySuperClass); + validatedClasses.put(proxySuperClass, Boolean.TRUE); + } + } + } + } + + /** + * Checks for final methods on the Class and writes warnings to the log + * for each one found. + */ + private void doValidateClass(Class proxySuperClass) { + Method[] methods = proxySuperClass.getMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if (!Object.class.equals(method.getDeclaringClass()) && Modifier.isFinal(method.getModifiers())) { + logger.warn("Unable to proxy method [" + method + "] because it is final: " + + "All calls to this method via a proxy will be routed directly to the proxy."); + } + } + } + + private Callback[] getCallbacks(Class rootClass) throws Exception { + // Parameters used for optimisation choices... + boolean exposeProxy = this.advised.isExposeProxy(); + boolean isFrozen = this.advised.isFrozen(); + boolean isStatic = this.advised.getTargetSource().isStatic(); + + // Choose an "aop" interceptor (used for AOP calls). + Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised); + + // Choose a "straight to target" interceptor. (used for calls that are + // unadvised but can return this). May be required to expose the proxy. + Callback targetInterceptor = null; + + if (exposeProxy) { + targetInterceptor = isStatic ? + (Callback) new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : + (Callback) new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()); + } + else { + targetInterceptor = isStatic ? + (Callback) new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : + (Callback) new DynamicUnadvisedInterceptor(this.advised.getTargetSource()); + } + + // Choose a "direct to target" dispatcher (used for + // unadvised calls to static targets that cannot return this). + Callback targetDispatcher = isStatic ? + (Callback) new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp(); + + Callback[] mainCallbacks = new Callback[]{ + aopInterceptor, // for normal advice + targetInterceptor, // invoke target without considering advice, if optimized + new SerializableNoOp(), // no override for methods mapped to this + targetDispatcher, this.advisedDispatcher, + new EqualsInterceptor(this.advised), + new HashCodeInterceptor(this.advised) + }; + + Callback[] callbacks; + + // If the target is a static one and the advice chain is frozen, + // then we can make some optimisations by sending the AOP calls + // direct to the target using the fixed chain for that method. + if (isStatic && isFrozen) { + Method[] methods = rootClass.getMethods(); + Callback[] fixedCallbacks = new Callback[methods.length]; + this.fixedInterceptorMap = new HashMap(methods.length); + + // TODO: small memory optimisation here (can skip creation for + // methods with no advice) + for (int x = 0; x < methods.length; x++) { + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass); + fixedCallbacks[x] = new FixedChainStaticTargetInterceptor( + chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass()); + this.fixedInterceptorMap.put(methods[x].toString(), new Integer(x)); + } + + // Now copy both the callbacks from mainCallbacks + // and fixedCallbacks into the callbacks array. + callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length]; + + for (int x = 0; x < mainCallbacks.length; x++) { + callbacks[x] = mainCallbacks[x]; + } + + for (int x = 0; x < fixedCallbacks.length; x++) { + callbacks[x + mainCallbacks.length] = fixedCallbacks[x]; + } + + this.fixedInterceptorOffset = mainCallbacks.length; + } + else { + callbacks = mainCallbacks; + } + return callbacks; + } + + /** + * Wrap a return of this if necessary to be the proxy + */ + private static Object massageReturnTypeIfNecessary(Object proxy, Object target, Method method, Object retVal) { + // Massage return value if necessary + if (retVal != null && retVal == target && + !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { + // Special case: it returned "this". + // Note that we can't help if the target sets a reference + // to itself in another returned object. + retVal = proxy; + } + return retVal; + } + + + public boolean equals(Object other) { + return (this == other || (other instanceof Cglib2AopProxy && + AopProxyUtils.equalsInProxy(this.advised, ((Cglib2AopProxy) other).advised))); + } + + public int hashCode() { + return Cglib2AopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); + } + + + /** + * Serializable replacement for CGLIB's NoOp interface. + * Public to allow use elsewhere in the framework. + */ + public static class SerializableNoOp implements NoOp, Serializable { + } + + + /** + * Method interceptor used for static targets with no advice chain. The call + * is passed directly back to the target. Used when the proxy needs to be + * exposed and it can't be determined that the method won't return + * this. + */ + private static class StaticUnadvisedInterceptor implements MethodInterceptor, Serializable { + + private final Object target; + + public StaticUnadvisedInterceptor(Object target) { + this.target = target; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Object retVal = methodProxy.invoke(this.target, args); + return massageReturnTypeIfNecessary(proxy, this.target, method, retVal); + } + } + + + /** + * Method interceptor used for static targets with no advice chain, when the + * proxy is to be exposed. + */ + private static class StaticUnadvisedExposedInterceptor implements MethodInterceptor, Serializable { + + private final Object target; + + public StaticUnadvisedExposedInterceptor(Object target) { + this.target = target; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Object oldProxy = null; + try { + oldProxy = AopContext.setCurrentProxy(proxy); + Object retVal = methodProxy.invoke(this.target, args); + return massageReturnTypeIfNecessary(proxy, this.target, method, retVal); + } + finally { + AopContext.setCurrentProxy(oldProxy); + } + } + } + + + /** + * Interceptor used to invoke a dynamic target without creating a method + * invocation or evaluating an advice chain. (We know there was no advice + * for this method.) + */ + private static class DynamicUnadvisedInterceptor implements MethodInterceptor, Serializable { + + private final TargetSource targetSource; + + public DynamicUnadvisedInterceptor(TargetSource targetSource) { + this.targetSource = targetSource; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Object target = this.targetSource.getTarget(); + try { + Object retVal = methodProxy.invoke(target, args); + return massageReturnTypeIfNecessary(proxy, target, method, retVal); + } + finally { + this.targetSource.releaseTarget(target); + } + } + } + + + /** + * Interceptor for unadvised dynamic targets when the proxy needs exposing. + */ + private static class DynamicUnadvisedExposedInterceptor implements MethodInterceptor, Serializable { + + private final TargetSource targetSource; + + public DynamicUnadvisedExposedInterceptor(TargetSource targetSource) { + this.targetSource = targetSource; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Object oldProxy = null; + Object target = this.targetSource.getTarget(); + try { + oldProxy = AopContext.setCurrentProxy(proxy); + Object retVal = methodProxy.invoke(target, args); + return massageReturnTypeIfNecessary(proxy, target, method, retVal); + } + finally { + AopContext.setCurrentProxy(oldProxy); + this.targetSource.releaseTarget(target); + } + } + } + + + /** + * Dispatcher for a static target. Dispatcher is much faster than + * interceptor. This will be used whenever it can be determined that a + * method definitely does not return "this" + */ + private static class StaticDispatcher implements Dispatcher, Serializable { + + private Object target; + + public StaticDispatcher(Object target) { + this.target = target; + } + + public Object loadObject() { + return this.target; + } + } + + + /** + * Dispatcher for any methods declared on the Advised class. + */ + private static class AdvisedDispatcher implements Dispatcher, Serializable { + + private final AdvisedSupport advised; + + public AdvisedDispatcher(AdvisedSupport advised) { + this.advised = advised; + } + + public Object loadObject() throws Exception { + return this.advised; + } + } + + + /** + * Dispatcher for the equals method. + * Ensures that the method call is always handled by this class. + */ + private static class EqualsInterceptor implements MethodInterceptor, Serializable { + + private final AdvisedSupport advised; + + public EqualsInterceptor(AdvisedSupport advised) { + this.advised = advised; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) { + Object other = args[0]; + if (proxy == other) { + return Boolean.TRUE; + } + AdvisedSupport otherAdvised = null; + if (other instanceof Factory) { + Callback callback = ((Factory) other).getCallback(INVOKE_EQUALS); + if (!(callback instanceof EqualsInterceptor)) { + return Boolean.FALSE; + } + otherAdvised = ((EqualsInterceptor) callback).advised; + } + else { + return Boolean.FALSE; + } + return (AopProxyUtils.equalsInProxy(this.advised, otherAdvised) ? Boolean.TRUE : Boolean.FALSE); + } + } + + + /** + * Dispatcher for the hashCode method. + * Ensures that the method call is always handled by this class. + */ + private static class HashCodeInterceptor implements MethodInterceptor, Serializable { + + private final AdvisedSupport advised; + + public HashCodeInterceptor(AdvisedSupport advised) { + this.advised = advised; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) { + return new Integer(Cglib2AopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode()); + } + } + + + /** + * Interceptor used specifically for advised methods on a frozen, static proxy. + */ + private static class FixedChainStaticTargetInterceptor implements MethodInterceptor, Serializable { + + private final List adviceChain; + + private final Object target; + + private final Class targetClass; + + public FixedChainStaticTargetInterceptor(List adviceChain, Object target, Class targetClass) { + this.adviceChain = adviceChain; + this.target = target; + this.targetClass = targetClass; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Object retVal = null; + MethodInvocation invocation = new CglibMethodInvocation(proxy, this.target, method, args, + this.targetClass, this.adviceChain, methodProxy); + // If we get here, we need to create a MethodInvocation. + retVal = invocation.proceed(); + retVal = massageReturnTypeIfNecessary(proxy, this.target, method, retVal); + return retVal; + } + } + + + /** + * General purpose AOP callback. Used when the target is dynamic or when the + * proxy is not frozen. + */ + private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable { + + private AdvisedSupport advised; + + public DynamicAdvisedInterceptor(AdvisedSupport advised) { + this.advised = advised; + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + MethodInvocation invocation = null; + Object oldProxy = null; + boolean setProxyContext = false; + Class targetClass = null; + Object target = null; + try { + Object retVal = null; + if (this.advised.exposeProxy) { + // Make invocation available if necessary. + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + // May be null. Get as late as possible to minimize the time we + // "own" the target, in case it comes from a pool. + target = getTarget(); + if (target != null) { + targetClass = target.getClass(); + } + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + // Check whether we only have one InvokerInterceptor: that is, + // no real advice, but just reflective invocation of the target. + if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { + // We can skip creating a MethodInvocation: just invoke the target directly. + // Note that the final invoker must be an InvokerInterceptor, so we know + // it does nothing but a reflective operation on the target, and no hot + // swapping or fancy proxying. + retVal = methodProxy.invoke(target, args); + } + else { + // We need to create a method invocation... + invocation = new CglibMethodInvocation(proxy, target, method, args, + targetClass, chain, methodProxy); + // If we get here, we need to create a MethodInvocation. + retVal = invocation.proceed(); + } + + retVal = massageReturnTypeIfNecessary(proxy, target, method, retVal); + return retVal; + } + finally { + if (target != null) { + releaseTarget(target); + } + if (setProxyContext) { + // Restore old proxy. + AopContext.setCurrentProxy(oldProxy); + } + } + } + + public boolean equals(Object other) { + return (this == other || + (other instanceof DynamicAdvisedInterceptor && + this.advised.equals(((DynamicAdvisedInterceptor) other).advised))); + } + + /** + * CGLIB uses this to drive proxy creation. + */ + public int hashCode() { + return this.advised.hashCode(); + } + + protected Object getTarget() throws Exception { + return this.advised.getTargetSource().getTarget(); + } + + protected void releaseTarget(Object target) throws Exception { + this.advised.getTargetSource().releaseTarget(target); + } + } + + + /** + * Implementation of AOP Alliance MethodInvocation used by this AOP proxy. + */ + private static class CglibMethodInvocation extends ReflectiveMethodInvocation { + + private final MethodProxy methodProxy; + + private boolean protectedMethod; + + public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, + Class targetClass, List interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) { + super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers); + this.methodProxy = methodProxy; + this.protectedMethod = Modifier.isProtected(method.getModifiers()); + } + + /** + * Gives a marginal performance improvement versus using reflection to + * invoke the target when invoking public methods. + */ + protected Object invokeJoinpoint() throws Throwable { + if (this.protectedMethod) { + return super.invokeJoinpoint(); + } + else { + return this.methodProxy.invoke(this.target, this.arguments); + } + } + } + + + /** + * CallbackFilter to assign Callbacks to methods. + */ + private static class ProxyCallbackFilter implements CallbackFilter { + + private final AdvisedSupport advised; + + private final Map fixedInterceptorMap; + + private final int fixedInterceptorOffset; + + public ProxyCallbackFilter(AdvisedSupport advised, Map fixedInterceptorMap, int fixedInterceptorOffset) { + this.advised = advised; + this.fixedInterceptorMap = fixedInterceptorMap; + this.fixedInterceptorOffset = fixedInterceptorOffset; + } + + /** + * Implementation of CallbackFilter.accept() to return the index of the + * callback we need. + *

The callbacks for each proxy are built up of a set of fixed callbacks + * for general use and then a set of callbacks that are specific to a method + * for use on static targets with a fixed advice chain. + *

The callback used is determined thus: + *

+ *
For exposed proxies
+ *
Exposing the proxy requires code to execute before and after the + * method/chain invocation. This means we must use + * DynamicAdvisedInterceptor, since all other interceptors can avoid the + * need for a try/catch block
+ *
For Object.finalize():
+ *
No override for this method is used.
+ *
For equals():
+ *
The EqualsInterceptor is used to redirect equals() calls to a + * special handler to this proxy.
+ *
For methods on the Advised class:
+ *
the AdvisedDispatcher is used to dispatch the call directly to + * the target
+ *
For advised methods:
+ *
If the target is static and the advice chain is frozen then a + * FixedChainStaticTargetInterceptor specific to the method is used to + * invoke the advice chain. Otherwise a DyanmicAdvisedInterceptor is + * used.
+ *
For non-advised methods:
+ *
Where it can be determined that the method will not return this + * or when ProxyFactory.getExposeProxy() returns false, + * then a Dispatcher is used. For static targets, the StaticDispatcher is used; + * and for dynamic targets, a DynamicUnadvisedInterceptor is used. + * If it possible for the method to return this then a + * StaticUnadvisedInterceptor is used for static targets - the + * DynamicUnadvisedInterceptor already considers this.
+ *
+ */ + public int accept(Method method) { + if (AopUtils.isFinalizeMethod(method)) { + logger.debug("Found finalize() method - using NO_OVERRIDE"); + return NO_OVERRIDE; + } + if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() && + method.getDeclaringClass().isAssignableFrom(Advised.class)) { + if (logger.isDebugEnabled()) { + logger.debug("Method is declared on Advised interface: " + method); + } + return DISPATCH_ADVISED; + } + // We must always proxy equals, to direct calls to this. + if (AopUtils.isEqualsMethod(method)) { + logger.debug("Found 'equals' method: " + method); + return INVOKE_EQUALS; + } + // We must always calculate hashCode based on the proxy. + if (AopUtils.isHashCodeMethod(method)) { + logger.debug("Found 'hashCode' method: " + method); + return INVOKE_HASHCODE; + } + Class targetClass = this.advised.getTargetClass(); + // Proxy is not yet available, but that shouldn't matter. + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + boolean haveAdvice = !chain.isEmpty(); + boolean exposeProxy = this.advised.isExposeProxy(); + boolean isStatic = this.advised.getTargetSource().isStatic(); + boolean isFrozen = this.advised.isFrozen(); + if (haveAdvice || !isFrozen) { + // If exposing the proxy, then AOP_PROXY must be used. + if (exposeProxy) { + if (logger.isDebugEnabled()) { + logger.debug("Must expose proxy on advised method: " + method); + } + return AOP_PROXY; + } + String key = method.toString(); + // Check to see if we have fixed interceptor to serve this method. + // Else use the AOP_PROXY. + if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) { + if (logger.isDebugEnabled()) { + logger.debug("Method has advice and optimisations are enabled: " + method); + } + // We know that we are optimising so we can use the + // FixedStaticChainInterceptors. + int index = ((Integer) this.fixedInterceptorMap.get(key)).intValue(); + return (index + this.fixedInterceptorOffset); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Unable to apply any optimisations to advised method: " + method); + } + return AOP_PROXY; + } + } + else { + // See if the return type of the method is outside the class hierarchy + // of the target type. If so we know it never needs to have return type + // massage and can use a dispatcher. + // If the proxy is being exposed, then must use the interceptor the + // correct one is already configured. If the target is not static cannot + // use a Dispatcher because the target can not then be released. + if (exposeProxy || !isStatic) { + return INVOKE_TARGET; + } + Class returnType = method.getReturnType(); + if (targetClass == returnType) { + if (logger.isDebugEnabled()) { + logger.debug("Method " + method + + "has return type same as target type (may return this) - using INVOKE_TARGET"); + } + return INVOKE_TARGET; + } + else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) { + if (logger.isDebugEnabled()) { + logger.debug("Method " + method + + " has return type that ensures this cannot be returned- using DISPATCH_TARGET"); + } + return DISPATCH_TARGET; + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Method " + method + + "has return type that is assignable from the target type (may return this) - " + + "using INVOKE_TARGET"); + } + return INVOKE_TARGET; + } + } + } + + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ProxyCallbackFilter)) { + return false; + } + ProxyCallbackFilter otherCallbackFilter = (ProxyCallbackFilter) other; + AdvisedSupport otherAdvised = otherCallbackFilter.advised; + if (this.advised == null || otherAdvised == null) { + return false; + } + if (this.advised.isFrozen() != otherAdvised.isFrozen()) { + return false; + } + if (this.advised.isExposeProxy() != otherAdvised.isExposeProxy()) { + return false; + } + if (this.advised.getTargetSource().isStatic() != otherAdvised.getTargetSource().isStatic()) { + return false; + } + if (!AopProxyUtils.equalsProxiedInterfaces(this.advised, otherAdvised)) { + return false; + } + // Advice instance identity is unimportant to the proxy class: + // All that matters is type and ordering. + Advisor[] thisAdvisors = this.advised.getAdvisors(); + Advisor[] thatAdvisors = otherAdvised.getAdvisors(); + if (thisAdvisors.length != thatAdvisors.length) { + return false; + } + for (int i = 0; i < thisAdvisors.length; i++) { + Advisor thisAdvisor = thisAdvisors[i]; + Advisor thatAdvisor = thatAdvisors[i]; + if (!equalsAdviceClasses(thisAdvisor, thatAdvisor)) { + return false; + } + if (!equalsPointcuts(thisAdvisor, thatAdvisor)) { + return false; + } + } + return true; + } + + private boolean equalsAdviceClasses(Advisor a, Advisor b) { + Advice aa = a.getAdvice(); + Advice ba = b.getAdvice(); + if (aa == null || ba == null) { + return (aa == ba); + } + return aa.getClass().equals(ba.getClass()); + } + + private boolean equalsPointcuts(Advisor a, Advisor b) { + // If only one of the advisor (but not both) is PointcutAdvisor, then it is a mismatch. + // Takes care of the situations where an IntroductionAdvisor is used (see SPR-3959). + if (a instanceof PointcutAdvisor ^ b instanceof PointcutAdvisor) { + return false; + } + // If both are PointcutAdvisor, match their pointcuts. + if (a instanceof PointcutAdvisor && b instanceof PointcutAdvisor) { + return ObjectUtils.nullSafeEquals(((PointcutAdvisor) a).getPointcut(), ((PointcutAdvisor) b).getPointcut()); + } + // If neither is PointcutAdvisor, then from the pointcut matching perspective, it is a match. + return true; + } + + public int hashCode() { + int hashCode = 0; + Advisor[] advisors = this.advised.getAdvisors(); + for (int i = 0; i < advisors.length; i++) { + Advice advice = advisors[i].getAdvice(); + if (advice != null) { + hashCode = 13 * hashCode + advice.getClass().hashCode(); + } + } + hashCode = 13 * hashCode + (this.advised.isFrozen() ? 1 : 0); + hashCode = 13 * hashCode + (this.advised.isExposeProxy() ? 1 : 0); + hashCode = 13 * hashCode + (this.advised.isOptimize() ? 1 : 0); + hashCode = 13 * hashCode + (this.advised.isOpaque() ? 1 : 0); + return hashCode; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java new file mode 100644 index 0000000000..84c0496759 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/DefaultAdvisorChainFactory.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.aopalliance.intercept.Interceptor; +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry; +import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry; +import org.springframework.aop.support.MethodMatchers; + +/** + * A simple but definitive way of working out an advice chain for a Method, + * given an {@link Advised} object. Always rebuilds each advice chain; + * caching can be provided by subclasses. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @author Adrian Colyer + * @since 2.0.3 + */ +public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable { + + public List getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, Class targetClass) { + // This is somewhat tricky... we have to process introductions first, + // but we need to preserve order in the ultimate list. + List interceptorList = new ArrayList(config.getAdvisors().length); + boolean hasIntroductions = hasMatchingIntroductions(config, targetClass); + AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); + Advisor[] advisors = config.getAdvisors(); + for (int i = 0; i < advisors.length; i++) { + Advisor advisor = advisors[i]; + if (advisor instanceof PointcutAdvisor) { + // Add it conditionally. + PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor; + if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) { + MethodInterceptor[] interceptors = registry.getInterceptors(advisor); + MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher(); + if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) { + if (mm.isRuntime()) { + // Creating a new object instance in the getInterceptors() method + // isn't a problem as we normally cache created chains. + for (int j = 0; j < interceptors.length; j++) { + interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j], mm)); + } + } + else { + interceptorList.addAll(Arrays.asList(interceptors)); + } + } + } + } + else if (advisor instanceof IntroductionAdvisor) { + IntroductionAdvisor ia = (IntroductionAdvisor) advisor; + if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) { + Interceptor[] interceptors = registry.getInterceptors(advisor); + interceptorList.addAll(Arrays.asList(interceptors)); + } + } + else { + Interceptor[] interceptors = registry.getInterceptors(advisor); + interceptorList.addAll(Arrays.asList(interceptors)); + } + } + return interceptorList; + } + + /** + * Determine whether the Advisors contain matching introductions. + */ + private static boolean hasMatchingIntroductions(Advised config, Class targetClass) { + for (int i = 0; i < config.getAdvisors().length; i++) { + Advisor advisor = config.getAdvisors()[i]; + if (advisor instanceof IntroductionAdvisor) { + IntroductionAdvisor ia = (IntroductionAdvisor) advisor; + if (ia.getClassFilter().matches(targetClass)) { + return true; + } + } + } + return false; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java new file mode 100644 index 0000000000..731565c8d7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.io.Serializable; + +import org.springframework.aop.SpringProxy; +import org.springframework.util.ClassUtils; + +/** + * Default {@link AopProxyFactory} implementation, + * creating either a CGLIB proxy or a JDK dynamic proxy. + * + *

Creates a CGLIB proxy if one the following is true + * for a given {@link AdvisedSupport} instance: + *

    + *
  • the "optimize" flag is set + *
  • the "proxyTargetClass" flag is set + *
  • no proxy interfaces have been specified + *
+ * + *

Note that the CGLIB library classes have to be present on + * the class path if an actual CGLIB proxy needs to be created. + * + *

In general, specify "proxyTargetClass" to enforce a CGLIB proxy, + * or specify one or more interfaces to use a JDK dynamic proxy. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 12.03.2004 + * @see AdvisedSupport#setOptimize + * @see AdvisedSupport#setProxyTargetClass + * @see AdvisedSupport#setInterfaces + */ +public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { + + /** Whether the CGLIB2 library is present on the classpath */ + private static final boolean cglibAvailable = + ClassUtils.isPresent("net.sf.cglib.proxy.Enhancer", DefaultAopProxyFactory.class.getClassLoader()); + + + public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { + if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { + Class targetClass = config.getTargetClass(); + if (targetClass == null) { + throw new AopConfigException("TargetSource cannot determine target class: " + + "Either an interface or a target is required for proxy creation."); + } + if (targetClass.isInterface()) { + return new JdkDynamicAopProxy(config); + } + if (!cglibAvailable) { + throw new AopConfigException( + "Cannot proxy target class because CGLIB2 is not available. " + + "Add CGLIB to the class path or specify proxy interfaces."); + } + return CglibProxyFactory.createCglibProxy(config); + } + else { + return new JdkDynamicAopProxy(config); + } + } + + /** + * Determine whether the supplied {@link AdvisedSupport} has only the + * {@link org.springframework.aop.SpringProxy} interface specified + * (or no proxy interfaces specified at all). + */ + private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { + Class[] interfaces = config.getProxiedInterfaces(); + return (interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.equals(interfaces[0]))); + } + + + /** + * Inner factory class used to just introduce a CGLIB2 dependency + * when actually creating a CGLIB proxy. + */ + private static class CglibProxyFactory { + + public static AopProxy createCglibProxy(AdvisedSupport advisedSupport) { + return new Cglib2AopProxy(advisedSupport); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/InterceptorAndDynamicMethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/InterceptorAndDynamicMethodMatcher.java new file mode 100644 index 0000000000..0a0e123387 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/InterceptorAndDynamicMethodMatcher.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.MethodMatcher; + +/** + * Internal framework class, combining a MethodInterceptor instance + * with a MethodMatcher for use as an element in the advisor chain. + * + * @author Rod Johnson + */ +class InterceptorAndDynamicMethodMatcher { + + final MethodInterceptor interceptor; + + final MethodMatcher methodMatcher; + + public InterceptorAndDynamicMethodMatcher(MethodInterceptor interceptor, MethodMatcher methodMatcher) { + this.interceptor = interceptor; + this.methodMatcher = methodMatcher; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java new file mode 100644 index 0000000000..8aa4376872 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -0,0 +1,270 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.RawTargetAccess; +import org.springframework.aop.TargetSource; +import org.springframework.aop.support.AopUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * JDK-based {@link AopProxy} implementation for the Spring AOP framework, + * based on JDK {@link java.lang.reflect.Proxy dynamic proxies}. + * + *

Creates a dynamic proxy, implementing the interfaces exposed by + * the AopProxy. Dynamic proxies cannot be used to proxy methods + * defined in classes, rather than interfaces. + * + *

Objects of this type should be obtained through proxy factories, + * configured by an {@link AdvisedSupport} class. This class is internal + * to Spring's AOP framework and need not be used directly by client code. + * + *

Proxies created using this class will be thread-safe if the + * underlying (target) class is thread-safe. + * + *

Proxies are serializable so long as all Advisors (including Advices + * and Pointcuts) and the TargetSource are serializable. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @see java.lang.reflect.Proxy + * @see AdvisedSupport + * @see ProxyFactory + */ +final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = 5531744639992436476L; + + + /* + * NOTE: We could avoid the code duplication between this class and the CGLIB + * proxies by refactoring "invoke" into a template method. However, this approach + * adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice + * elegance for performance. (We have a good test suite to ensure that the different + * proxies behave the same :-) + * This way, we can also more easily take advantage of minor optimizations in each class. + */ + + /** We use a static Log to avoid serialization issues */ + private static Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); + + /** Config used to configure this proxy */ + private final AdvisedSupport advised; + + /** + * Is the {@link #equals} method defined on the proxied interfaces? + */ + private boolean equalsDefined; + + /** + * Is the {@link #hashCode} method defined on the proxied interfaces? + */ + private boolean hashCodeDefined; + + + /** + * Construct a new JdkDynamicAopProxy for the given AOP configuration. + * @param config the AOP configuration as AdvisedSupport object + * @throws AopConfigException if the config is invalid. We try to throw an informative + * exception in this case, rather than let a mysterious failure happen later. + */ + public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { + Assert.notNull(config, "AdvisedSupport must not be null"); + if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) { + throw new AopConfigException("No advisors and no TargetSource specified"); + } + this.advised = config; + } + + + public Object getProxy() { + return getProxy(ClassUtils.getDefaultClassLoader()); + } + + public Object getProxy(ClassLoader classLoader) { + if (logger.isDebugEnabled()) { + logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); + } + Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); + findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); + return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); + } + + /** + * Finds any {@link #equals} or {@link #hashCode} method that may be defined + * on the supplied set of interfaces. + * @param proxiedInterfaces the interfaces to introspect + */ + private void findDefinedEqualsAndHashCodeMethods(Class[] proxiedInterfaces) { + for (int i = 0; i < proxiedInterfaces.length; i++) { + Class proxiedInterface = proxiedInterfaces[i]; + Method[] methods = proxiedInterface.getDeclaredMethods(); + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + if (AopUtils.isEqualsMethod(method)) { + this.equalsDefined = true; + } + if (AopUtils.isHashCodeMethod(method)) { + this.hashCodeDefined = true; + } + if (this.equalsDefined && this.hashCodeDefined) { + return; + } + } + } + } + + + /** + * Implementation of InvocationHandler.invoke. + *

Callers will see exactly the exception thrown by the target, + * unless a hook method throws an exception. + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + MethodInvocation invocation = null; + Object oldProxy = null; + boolean setProxyContext = false; + + TargetSource targetSource = this.advised.targetSource; + Class targetClass = null; + Object target = null; + + try { + if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { + // The target does not implement the equals(Object) method itself. + return (equals(args[0]) ? Boolean.TRUE : Boolean.FALSE); + } + if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { + // The target does not implement the hashCode() method itself. + return new Integer(hashCode()); + } + if (!this.advised.opaque && method.getDeclaringClass().isInterface() && + method.getDeclaringClass().isAssignableFrom(Advised.class)) { + // Service invocations on ProxyConfig with the proxy config... + return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); + } + + Object retVal = null; + + if (this.advised.exposeProxy) { + // Make invocation available if necessary. + oldProxy = AopContext.setCurrentProxy(proxy); + setProxyContext = true; + } + + // May be null. Get as late as possible to minimize the time we "own" the target, + // in case it comes from a pool. + target = targetSource.getTarget(); + if (target != null) { + targetClass = target.getClass(); + } + + // Get the interception chain for this method. + List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + + // Check whether we have any advice. If we don't, we can fallback on direct + // reflective invocation of the target, and avoid creating a MethodInvocation. + if (chain.isEmpty()) { + // We can skip creating a MethodInvocation: just invoke the target directly + // Note that the final invoker must be an InvokerInterceptor so we know it does + // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. + retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); + } + else { + // We need to create a method invocation... + invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); + // Proceed to the joinpoint through the interceptor chain. + retVal = invocation.proceed(); + } + + // Massage return value if necessary. + if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) && + !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { + // Special case: it returned "this" and the return type of the method + // is type-compatible. Note that we can't help if the target sets + // a reference to itself in another returned object. + retVal = proxy; + } + return retVal; + } + finally { + if (target != null && !targetSource.isStatic()) { + // Must have come from TargetSource. + targetSource.releaseTarget(target); + } + if (setProxyContext) { + // Restore old proxy. + AopContext.setCurrentProxy(oldProxy); + } + } + } + + + /** + * Equality means interfaces, advisors and TargetSource are equal. + *

The compared object may be a JdkDynamicAopProxy instance itself + * or a dynamic proxy wrapping a JdkDynamicAopProxy instance. + */ + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null) { + return false; + } + + JdkDynamicAopProxy otherProxy = null; + if (other instanceof JdkDynamicAopProxy) { + otherProxy = (JdkDynamicAopProxy) other; + } + else if (Proxy.isProxyClass(other.getClass())) { + InvocationHandler ih = Proxy.getInvocationHandler(other); + if (!(ih instanceof JdkDynamicAopProxy)) { + return false; + } + otherProxy = (JdkDynamicAopProxy) ih; + } + else { + // Not a valid comparison... + return false; + } + + // If we get here, aopr2 is the other AopProxy. + return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised); + } + + /** + * Proxy uses the hash code of the TargetSource. + */ + public int hashCode() { + return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java new file mode 100644 index 0000000000..d26b8a571a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyConfig.java @@ -0,0 +1,171 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.io.Serializable; + +import org.springframework.util.Assert; + +/** + * Convenience superclass for configuration used in creating proxies, + * to ensure that all proxy creators have consistent properties. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see AdvisedSupport + */ +public class ProxyConfig implements Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = -8409359707199703185L; + + + private boolean proxyTargetClass = false; + + private boolean optimize = false; + + boolean opaque = false; + + boolean exposeProxy = false; + + private boolean frozen = false; + + + /** + * Set whether to proxy the target class directly, instead of just proxying + * specific interfaces. Default is "false". + *

Set this to "true" to force proxying for the TargetSource's exposed + * target class. If that target class is an interface, a JDK proxy will be + * created for the given interface. If that target class is any other class, + * a CGLIB proxy will be created for the given class. + *

Note: Depending on the configuration of the concrete proxy factory, + * the proxy-target-class behavior will also be applied if no interfaces + * have been specified (and no interface autodetection is activated). + * @see org.springframework.aop.TargetSource#getTargetClass() + */ + public void setProxyTargetClass(boolean proxyTargetClass) { + this.proxyTargetClass = proxyTargetClass; + } + + /** + * Return whether to proxy the target class directly as well as any interfaces. + */ + public boolean isProxyTargetClass() { + return this.proxyTargetClass; + } + + /** + * Set whether proxies should perform aggressive optimizations. + * The exact meaning of "aggressive optimizations" will differ + * between proxies, but there is usually some tradeoff. + * Default is "false". + *

For example, optimization will usually mean that advice changes won't + * take effect after a proxy has been created. For this reason, optimization + * is disabled by default. An optimize value of "true" may be ignored + * if other settings preclude optimization: for example, if "exposeProxy" + * is set to "true" and that's not compatible with the optimization. + */ + public void setOptimize(boolean optimize) { + this.optimize = optimize; + } + + /** + * Return whether proxies should perform aggressive optimizations. + */ + public boolean isOptimize() { + return this.optimize; + } + + /** + * Set whether proxies created by this configuration should be prevented + * from being cast to {@link Advised} to query proxy status. + *

Default is "false", meaning that any AOP proxy can be cast to + * {@link Advised}. + */ + public void setOpaque(boolean opaque) { + this.opaque = opaque; + } + + /** + * Return whether proxies created by this configuration should be + * prevented from being cast to {@link Advised}. + */ + public boolean isOpaque() { + return this.opaque; + } + + /** + * Set whether the proxy should be exposed by the AOP framework as a + * ThreadLocal for retrieval via the AopContext class. This is useful + * if an advised object needs to call another advised method on itself. + * (If it uses this, the invocation will not be advised). + *

Default is "false", for optimal performance. + */ + public void setExposeProxy(boolean exposeProxy) { + this.exposeProxy = exposeProxy; + } + + /** + * Return whether the AOP proxy will expose the AOP proxy for + * each invocation. + */ + public boolean isExposeProxy() { + return this.exposeProxy; + } + + /** + * Set whether this config should be frozen. + *

When a config is frozen, no advice changes can be made. This is + * useful for optimization, and useful when we don't want callers to + * be able to manipulate configuration after casting to Advised. + */ + public void setFrozen(boolean frozen) { + this.frozen = frozen; + } + + /** + * Return whether the config is frozen, and no advice changes can be made. + */ + public boolean isFrozen() { + return this.frozen; + } + + + /** + * Copy configuration from the other config object. + * @param other object to copy configuration from + */ + public void copyFrom(ProxyConfig other) { + Assert.notNull(other, "Other ProxyConfig object must not be null"); + this.proxyTargetClass = other.proxyTargetClass; + this.optimize = other.optimize; + this.exposeProxy = other.exposeProxy; + this.frozen = other.frozen; + this.opaque = other.opaque; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("proxyTargetClass=").append(this.proxyTargetClass).append("; "); + sb.append("optimize=").append(this.optimize).append("; "); + sb.append("opaque=").append(this.opaque).append("; "); + sb.append("exposeProxy=").append(this.exposeProxy).append("; "); + sb.append("frozen=").append(this.frozen); + return sb.toString(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java new file mode 100644 index 0000000000..495fda07bf --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyCreatorSupport.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.util.LinkedList; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Base class for proxy factories. + * Provides convenient access to a configurable AopProxyFactory. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see #createAopProxy() + */ +public class ProxyCreatorSupport extends AdvisedSupport { + + /** The AopProxyFactory to use */ + private AopProxyFactory aopProxyFactory; + + /** List of AdvisedSupportListener */ + private List listeners = new LinkedList(); + + /** Set to true when the first AOP proxy has been created */ + private boolean active = false; + + + /** + * Create a new ProxyCreatorSupport instance. + */ + public ProxyCreatorSupport() { + this.aopProxyFactory = new DefaultAopProxyFactory(); + } + + /** + * Create a new ProxyCreatorSupport instance. + * @param aopProxyFactory the AopProxyFactory to use + */ + public ProxyCreatorSupport(AopProxyFactory aopProxyFactory) { + Assert.notNull(aopProxyFactory, "AopProxyFactory must not be null"); + this.aopProxyFactory = aopProxyFactory; + } + + + /** + * Customize the AopProxyFactory, allowing different strategies + * to be dropped in without changing the core framework. + *

Default is {@link DefaultAopProxyFactory}, using dynamic JDK + * proxies or CGLIB proxies based on the requirements. + */ + public void setAopProxyFactory(AopProxyFactory aopProxyFactory) { + Assert.notNull(aopProxyFactory, "AopProxyFactory must not be null"); + this.aopProxyFactory = aopProxyFactory; + } + + /** + * Return the AopProxyFactory that this ProxyConfig uses. + */ + public AopProxyFactory getAopProxyFactory() { + return this.aopProxyFactory; + } + + /** + * Add the given AdvisedSupportListener to this proxy configuration. + * @param listener the listener to register + */ + public void addListener(AdvisedSupportListener listener) { + Assert.notNull(listener, "AdvisedSupportListener must not be null"); + this.listeners.add(listener); + } + + /** + * Remove the given AdvisedSupportListener from this proxy configuration. + * @param listener the listener to deregister + */ + public void removeListener(AdvisedSupportListener listener) { + Assert.notNull(listener, "AdvisedSupportListener must not be null"); + this.listeners.remove(listener); + } + + + /** + * Subclasses should call this to get a new AOP proxy. They should not + * create an AOP proxy with this as an argument. + */ + protected final synchronized AopProxy createAopProxy() { + if (!this.active) { + activate(); + } + return getAopProxyFactory().createAopProxy(this); + } + + /** + * Activate this proxy configuration. + * @see AdvisedSupportListener#activated + */ + private void activate() { + this.active = true; + for (int i = 0; i < this.listeners.size(); i++) { + ((AdvisedSupportListener) this.listeners.get(i)).activated(this); + } + } + + /** + * Propagate advice change event to all AdvisedSupportListeners. + * @see AdvisedSupportListener#adviceChanged + */ + protected void adviceChanged() { + super.adviceChanged(); + synchronized (this) { + if (this.active) { + for (int i = 0; i < this.listeners.size(); i++) { + ((AdvisedSupportListener) this.listeners.get(i)).adviceChanged(this); + } + } + } + } + + /** + * Subclasses can call this to check whether any AOP proxies have been created yet. + */ + protected final synchronized boolean isActive() { + return this.active; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java new file mode 100644 index 0000000000..522774e29a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import org.aopalliance.intercept.Interceptor; + +import org.springframework.aop.TargetSource; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Factory for AOP proxies for programmatic use, rather than via a bean + * factory. This class provides a simple way of obtaining and configuring + * AOP proxies in code. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 14.03.2003 + */ +public class ProxyFactory extends ProxyCreatorSupport implements AopProxy { + + /** + * Create a new ProxyFactory. + */ + public ProxyFactory() { + } + + /** + * Create a new ProxyFactory. + *

Will proxy all interfaces that the given target implements. + * @param target the target object to be proxied + */ + public ProxyFactory(Object target) { + Assert.notNull(target, "Target object must not be null"); + setInterfaces(ClassUtils.getAllInterfaces(target)); + setTarget(target); + } + + /** + * Create a new ProxyFactory. + *

No target, only interfaces. Must add interceptors. + * @param proxyInterfaces the interfaces that the proxy should implement + */ + public ProxyFactory(Class[] proxyInterfaces) { + setInterfaces(proxyInterfaces); + } + + /** + * Create a new ProxyFactory for the given interface and interceptor. + *

Convenience method for creating a proxy for a single interceptor, + * assuming that the interceptor handles all calls itself rather than + * delegating to a target, like in the case of remoting proxies. + * @param proxyInterface the interface that the proxy should implement + * @param interceptor the interceptor that the proxy should invoke + */ + public ProxyFactory(Class proxyInterface, Interceptor interceptor) { + addInterface(proxyInterface); + addAdvice(interceptor); + } + + /** + * Create a ProxyFactory for the specified TargetSource, + * making the proxy implement the specified interface. + * @param proxyInterface the interface that the proxy should implement + * @param targetSource the TargetSource that the proxy should invoke + */ + public ProxyFactory(Class proxyInterface, TargetSource targetSource) { + addInterface(proxyInterface); + setTargetSource(targetSource); + } + + + /** + * Create a new proxy according to the settings in this factory. + *

Can be called repeatedly. Effect will vary if we've added + * or removed interfaces. Can add and remove interceptors. + *

Uses a default class loader: Usually, the thread context class loader + * (if necessary for proxy creation). + * @return the proxy object + */ + public Object getProxy() { + return createAopProxy().getProxy(); + } + + /** + * Create a new proxy according to the settings in this factory. + *

Can be called repeatedly. Effect will vary if we've added + * or removed interfaces. Can add and remove interceptors. + *

Uses the given class loader (if necessary for proxy creation). + * @param classLoader the class loader to create the proxy with + * (or null for the low-level proxy facility's default) + * @return the proxy object + */ + public Object getProxy(ClassLoader classLoader) { + return createAopProxy().getProxy(classLoader); + } + + + /** + * Create a new proxy for the given interface and interceptor. + *

Convenience method for creating a proxy for a single interceptor, + * assuming that the interceptor handles all calls itself rather than + * delegating to a target, like in the case of remoting proxies. + * @param proxyInterface the interface that the proxy should implement + * @param interceptor the interceptor that the proxy should invoke + * @return the proxy object + * @see #ProxyFactory(Class, org.aopalliance.intercept.Interceptor) + */ + public static Object getProxy(Class proxyInterface, Interceptor interceptor) { + return new ProxyFactory(proxyInterface, interceptor).getProxy(); + } + + /** + * Create a proxy for the specified TargetSource, + * implementing the specified interface. + * @param proxyInterface the interface that the proxy should implement + * @param targetSource the TargetSource that the proxy should invoke + * @return the proxy object + * @see #ProxyFactory(Class, org.springframework.aop.TargetSource) + */ + public static Object getProxy(Class proxyInterface, TargetSource targetSource) { + return new ProxyFactory(proxyInterface, targetSource).getProxy(); + } + + /** + * Create a proxy for the specified TargetSource that extends + * the target class of the TargetSource. + * @param targetSource the TargetSource that the proxy should invoke + * @return the proxy object + */ + public static Object getProxy(TargetSource targetSource) { + if (targetSource.getTargetClass() == null) { + throw new IllegalArgumentException("Cannot create class proxy for TargetSource with null target class"); + } + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.setTargetSource(targetSource); + proxyFactory.setProxyTargetClass(true); + return proxyFactory.getProxy(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java new file mode 100644 index 0000000000..9b3b9136f5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ProxyFactoryBean.java @@ -0,0 +1,658 @@ +/* + * Copyright 2002-2008 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.aop.framework; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.Interceptor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.Advisor; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry; +import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry; +import org.springframework.aop.framework.adapter.UnknownAdviceTypeException; +import org.springframework.aop.target.SingletonTargetSource; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.core.OrderComparator; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * {@link org.springframework.beans.factory.FactoryBean} implementation that builds an + * AOP proxy based on beans in Spring {@link org.springframework.beans.factory.BeanFactory}. + * + *

{@link org.aopalliance.intercept.MethodInterceptor MethodInterceptors} and + * {@link org.springframework.aop.Advisor Advisors} are identified by a list of bean + * names in the current bean factory, specified through the "interceptorNames" property. + * The last entry in the list can be the name of a target bean or a + * {@link org.springframework.aop.TargetSource}; however, it is normally preferable + * to use the "targetName"/"target"/"targetSource" properties instead. + * + *

Global interceptors and advisors can be added at the factory level. The specified + * ones are expanded in an interceptor list where an "xxx*" entry is included in the + * list, matching the given prefix with the bean names (e.g. "global*" would match + * both "globalBean1" and "globalBean2", "*" all defined interceptors). The matching + * interceptors get applied according to their returned order value, if they implement + * the {@link org.springframework.core.Ordered} interface. + * + *

Creates a JDK proxy when proxy interfaces are given, and a CGLIB proxy for the + * actual target class if not. Note that the latter will only work if the target class + * does not have final methods, as a dynamic subclass will be created at runtime. + * + *

It's possible to cast a proxy obtained from this factory to {@link Advised}, + * or to obtain the ProxyFactoryBean reference and programmatically manipulate it. + * This won't work for existing prototype references, which are independent. However, + * it will work for prototypes subsequently obtained from the factory. Changes to + * interception will work immediately on singletons (including existing references). + * However, to change interfaces or target it's necessary to obtain a new instance + * from the factory. This means that singleton instances obtained from the factory + * do not have the same object identity. However, they do have the same interceptors + * and target, and changing any reference will change all objects. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setInterceptorNames + * @see #setProxyInterfaces + * @see org.aopalliance.intercept.MethodInterceptor + * @see org.springframework.aop.Advisor + * @see Advised + */ +public class ProxyFactoryBean extends ProxyCreatorSupport + implements FactoryBean, BeanClassLoaderAware, BeanFactoryAware { + + /** + * This suffix in a value in an interceptor list indicates to expand globals. + */ + public static final String GLOBAL_SUFFIX = "*"; + + + protected final Log logger = LogFactory.getLog(getClass()); + + private String[] interceptorNames; + + private String targetName; + + private boolean autodetectInterfaces = true; + + private boolean singleton = true; + + private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); + + private boolean freezeProxy = false; + + private transient ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader(); + + private transient boolean classLoaderConfigured = false; + + private transient BeanFactory beanFactory; + + /** Whether the advisor chain has already been initialized */ + private boolean advisorChainInitialized = false; + + /** If this is a singleton, the cached singleton proxy instance */ + private Object singletonInstance; + + + /** + * Set the names of the interfaces we're proxying. If no interface + * is given, a CGLIB for the actual class will be created. + *

This is essentially equivalent to the "setInterfaces" method, + * but mirrors TransactionProxyFactoryBean's "setProxyInterfaces". + * @see #setInterfaces + * @see AbstractSingletonProxyFactoryBean#setProxyInterfaces + */ + public void setProxyInterfaces(Class[] proxyInterfaces) throws ClassNotFoundException { + setInterfaces(proxyInterfaces); + } + + /** + * Set the list of Advice/Advisor bean names. This must always be set + * to use this factory bean in a bean factory. + *

The referenced beans should be of type Interceptor, Advisor or Advice + * The last entry in the list can be the name of any bean in the factory. + * If it's neither an Advice nor an Advisor, a new SingletonTargetSource + * is added to wrap it. Such a target bean cannot be used if the "target" + * or "targetSource" or "targetName" property is set, in which case the + * "interceptorNames" array must contain only Advice/Advisor bean names. + *

NOTE: Specifying a target bean as final name in the "interceptorNames" + * list is deprecated and will be removed in a future Spring version. + * Use the {@link #setTargetName "targetName"} property instead. + * @see org.aopalliance.intercept.MethodInterceptor + * @see org.springframework.aop.Advisor + * @see org.aopalliance.aop.Advice + * @see org.springframework.aop.target.SingletonTargetSource + */ + public void setInterceptorNames(String[] interceptorNames) { + this.interceptorNames = interceptorNames; + } + + /** + * Set the name of the target bean. This is an alternative to specifying + * the target name at the end of the "interceptorNames" array. + *

You can also specify a target object or a TargetSource object + * directly, via the "target"/"targetSource" property, respectively. + * @see #setInterceptorNames(String[]) + * @see #setTarget(Object) + * @see #setTargetSource(org.springframework.aop.TargetSource) + */ + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + /** + * Set whether to autodetect proxy interfaces if none specified. + *

Default is "true". Turn this flag off to create a CGLIB + * proxy for the full target class if no interfaces specified. + * @see #setProxyTargetClass + */ + public void setAutodetectInterfaces(boolean autodetectInterfaces) { + this.autodetectInterfaces = autodetectInterfaces; + } + + /** + * Set the value of the singleton property. Governs whether this factory + * should always return the same proxy instance (which implies the same target) + * or whether it should return a new prototype instance, which implies that + * the target and interceptors may be new instances also, if they are obtained + * from prototype bean definitions. This allows for fine control of + * independence/uniqueness in the object graph. + */ + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } + + /** + * Specify the AdvisorAdapterRegistry to use. + * Default is the global AdvisorAdapterRegistry. + * @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry + */ + public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { + this.advisorAdapterRegistry = advisorAdapterRegistry; + } + + public void setFrozen(boolean frozen) { + this.freezeProxy = frozen; + } + + /** + * Set the ClassLoader to generate the proxy class in. + *

Default is the bean ClassLoader, i.e. the ClassLoader used by the + * containing BeanFactory for loading all bean classes. This can be + * overridden here for specific proxies. + */ + public void setProxyClassLoader(ClassLoader classLoader) { + this.proxyClassLoader = classLoader; + this.classLoaderConfigured = (classLoader != null); + } + + public void setBeanClassLoader(ClassLoader classLoader) { + if (!this.classLoaderConfigured) { + this.proxyClassLoader = classLoader; + } + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + checkInterceptorNames(); + } + + + /** + * Return a proxy. Invoked when clients obtain beans from this factory bean. + * Create an instance of the AOP proxy to be returned by this factory. + * The instance will be cached for a singleton, and create on each call to + * getObject() for a proxy. + * @return a fresh AOP proxy reflecting the current state of this factory + */ + public Object getObject() throws BeansException { + initializeAdvisorChain(); + if (isSingleton()) { + return getSingletonInstance(); + } + else { + if (this.targetName == null) { + logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + + "Enable prototype proxies by setting the 'targetName' property."); + } + return newPrototypeInstance(); + } + } + + /** + * Return the type of the proxy. Will check the singleton instance if + * already created, else fall back to the proxy interface (in case of just + * a single one), the target bean type, or the TargetSource's target class. + * @see org.springframework.aop.TargetSource#getTargetClass + */ + public Class getObjectType() { + synchronized (this) { + if (this.singletonInstance != null) { + return this.singletonInstance.getClass(); + } + } + Class[] ifcs = getProxiedInterfaces(); + if (ifcs.length == 1) { + return ifcs[0]; + } + else if (ifcs.length > 1) { + return createCompositeInterface(ifcs); + } + else if (this.targetName != null && this.beanFactory != null) { + return this.beanFactory.getType(this.targetName); + } + else { + return getTargetClass(); + } + } + + public boolean isSingleton() { + return this.singleton; + } + + + /** + * Create a composite interface Class for the given interfaces, + * implementing the given interfaces in one single Class. + *

The default implementation builds a JDK proxy class for the + * given interfaces. + * @param interfaces the interfaces to merge + * @return the merged interface as Class + * @see java.lang.reflect.Proxy#getProxyClass + */ + protected Class createCompositeInterface(Class[] interfaces) { + return ClassUtils.createCompositeInterface(interfaces, this.proxyClassLoader); + } + + /** + * Return the singleton instance of this class's proxy object, + * lazily creating it if it hasn't been created already. + * @return the shared singleton proxy + */ + private synchronized Object getSingletonInstance() { + if (this.singletonInstance == null) { + this.targetSource = freshTargetSource(); + if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { + // Rely on AOP infrastructure to tell us what interfaces to proxy. + Class targetClass = getTargetClass(); + if (targetClass == null) { + throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); + } + setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); + } + // Initialize the shared singleton instance. + super.setFrozen(this.freezeProxy); + this.singletonInstance = getProxy(createAopProxy()); + } + return this.singletonInstance; + } + + /** + * Create a new prototype instance of this class's created proxy object, + * backed by an independent AdvisedSupport configuration. + * @return a totally independent proxy, whose advice we may manipulate in isolation + */ + private synchronized Object newPrototypeInstance() { + // In the case of a prototype, we need to give the proxy + // an independent instance of the configuration. + // In this case, no proxy will have an instance of this object's configuration, + // but will have an independent copy. + if (logger.isTraceEnabled()) { + logger.trace("Creating copy of prototype ProxyFactoryBean config: " + this); + } + + ProxyCreatorSupport copy = new ProxyCreatorSupport(getAopProxyFactory()); + // The copy needs a fresh advisor chain, and a fresh TargetSource. + TargetSource targetSource = freshTargetSource(); + copy.copyConfigurationFrom(this, targetSource, freshAdvisorChain()); + if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { + // Rely on AOP infrastructure to tell us what interfaces to proxy. + copy.setInterfaces( + ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader)); + } + copy.setFrozen(this.freezeProxy); + + if (logger.isTraceEnabled()) { + logger.trace("Using ProxyCreatorSupport copy: " + copy); + } + return getProxy(copy.createAopProxy()); + } + + /** + * Return the proxy object to expose. + *

The default implementation uses a getProxy call with + * the factory's bean class loader. Can be overridden to specify a + * custom class loader. + * @param aopProxy the prepared AopProxy instance to get the proxy from + * @return the proxy object to expose + * @see AopProxy#getProxy(ClassLoader) + */ + protected Object getProxy(AopProxy aopProxy) { + return aopProxy.getProxy(this.proxyClassLoader); + } + + /** + * Check the interceptorNames list whether it contains a target name as final element. + * If found, remove the final name from the list and set it as targetName. + */ + private void checkInterceptorNames() { + if (!ObjectUtils.isEmpty(this.interceptorNames)) { + String finalName = this.interceptorNames[this.interceptorNames.length - 1]; + if (this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { + // The last name in the chain may be an Advisor/Advice or a target/TargetSource. + // Unfortunately we don't know; we must look at type of the bean. + if (!finalName.endsWith(GLOBAL_SUFFIX) && !isNamedBeanAnAdvisorOrAdvice(finalName)) { + // The target isn't an interceptor. + this.targetName = finalName; + if (logger.isDebugEnabled()) { + logger.debug("Bean with name '" + finalName + "' concluding interceptor chain " + + "is not an advisor class: treating it as a target or TargetSource"); + } + String[] newNames = new String[this.interceptorNames.length - 1]; + System.arraycopy(this.interceptorNames, 0, newNames, 0, newNames.length); + this.interceptorNames = newNames; + } + } + } + } + + /** + * Look at bean factory metadata to work out whether this bean name, + * which concludes the interceptorNames list, is an Advisor or Advice, + * or may be a target. + * @param beanName bean name to check + * @return true if it's an Advisor or Advice + */ + private boolean isNamedBeanAnAdvisorOrAdvice(String beanName) { + Class namedBeanClass = this.beanFactory.getType(beanName); + if (namedBeanClass != null) { + return (Advisor.class.isAssignableFrom(namedBeanClass) || Advice.class.isAssignableFrom(namedBeanClass)); + } + // Treat it as an target bean if we can't tell. + if (logger.isDebugEnabled()) { + logger.debug("Could not determine type of bean with name '" + beanName + + "' - assuming it is neither an Advisor nor an Advice"); + } + return false; + } + + /** + * Create the advisor (interceptor) chain. Aadvisors that are sourced + * from a BeanFactory will be refreshed each time a new prototype instance + * is added. Interceptors added programmatically through the factory API + * are unaffected by such changes. + */ + private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException { + if (this.advisorChainInitialized) { + return; + } + + if (!ObjectUtils.isEmpty(this.interceptorNames)) { + if (this.beanFactory == null) { + throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); + } + + // Globals can't be last unless we specified a targetSource using the property... + if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) && + this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { + throw new AopConfigException("Target required after globals"); + } + + // Materialize interceptor chain from bean names. + for (int i = 0; i < this.interceptorNames.length; i++) { + String name = this.interceptorNames[i]; + if (logger.isTraceEnabled()) { + logger.trace("Configuring advisor or advice '" + name + "'"); + } + + if (name.endsWith(GLOBAL_SUFFIX)) { + if (!(this.beanFactory instanceof ListableBeanFactory)) { + throw new AopConfigException( + "Can only use global advisors or interceptors with a ListableBeanFactory"); + } + addGlobalAdvisor((ListableBeanFactory) this.beanFactory, + name.substring(0, name.length() - GLOBAL_SUFFIX.length())); + } + + else { + // If we get here, we need to add a named interceptor. + // We must check if it's a singleton or prototype. + Object advice = null; + if (this.singleton || this.beanFactory.isSingleton(this.interceptorNames[i])) { + // Add the real Advisor/Advice to the chain. + advice = this.beanFactory.getBean(this.interceptorNames[i]); + } + else { + // It's a prototype Advice or Advisor: replace with a prototype. + // Avoid unnecessary creation of prototype bean just for advisor chain initialization. + advice = new PrototypePlaceholderAdvisor(this.interceptorNames[i]); + } + addAdvisorOnChainCreation(advice, this.interceptorNames[i]); + } + } + } + + this.advisorChainInitialized = true; + } + + + /** + * Return an independent advisor chain. + * We need to do this every time a new prototype instance is returned, + * to return distinct instances of prototype Advisors and Advices. + */ + private List freshAdvisorChain() { + Advisor[] advisors = getAdvisors(); + List freshAdvisors = new ArrayList(advisors.length); + + for (int i = 0; i < advisors.length; i++) { + if (advisors[i] instanceof PrototypePlaceholderAdvisor) { + PrototypePlaceholderAdvisor pa = (PrototypePlaceholderAdvisor) advisors[i]; + if (logger.isDebugEnabled()) { + logger.debug("Refreshing bean named '" + pa.getBeanName() + "'"); + } + // Replace the placeholder with a fresh prototype instance resulting + // from a getBean() lookup + if (this.beanFactory == null) { + throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + + "- cannot resolve prototype advisor '" + pa.getBeanName() + "'"); + } + Object bean = this.beanFactory.getBean(pa.getBeanName()); + Advisor refreshedAdvisor = namedBeanToAdvisor(bean); + freshAdvisors.add(refreshedAdvisor); + } + else { + // Add the shared instance. + freshAdvisors.add(advisors[i]); + } + } + return freshAdvisors; + } + + /** + * Add all global interceptors and pointcuts. + */ + private void addGlobalAdvisor(ListableBeanFactory beanFactory, String prefix) { + String[] globalAdvisorNames = + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class); + String[] globalInterceptorNames = + BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class); + List beans = new ArrayList(globalAdvisorNames.length + globalInterceptorNames.length); + Map names = new HashMap(); + for (int i = 0; i < globalAdvisorNames.length; i++) { + String name = globalAdvisorNames[i]; + Object bean = beanFactory.getBean(name); + beans.add(bean); + names.put(bean, name); + } + for (int i = 0; i < globalInterceptorNames.length; i++) { + String name = globalInterceptorNames[i]; + Object bean = beanFactory.getBean(name); + beans.add(bean); + names.put(bean, name); + } + Collections.sort(beans, new OrderComparator()); + for (Iterator it = beans.iterator(); it.hasNext();) { + Object bean = it.next(); + String name = (String) names.get(bean); + if (name.startsWith(prefix)) { + addAdvisorOnChainCreation(bean, name); + } + } + } + + /** + * Invoked when advice chain is created. + *

Add the given advice, advisor or object to the interceptor list. + * Because of these three possibilities, we can't type the signature + * more strongly. + * @param next advice, advisor or target object + * @param name bean name from which we obtained this object in our owning + * bean factory + */ + private void addAdvisorOnChainCreation(Object next, String name) { + // We need to convert to an Advisor if necessary so that our source reference + // matches what we find from superclass interceptors. + Advisor advisor = namedBeanToAdvisor(next); + if (logger.isTraceEnabled()) { + logger.trace("Adding advisor with name '" + name + "'"); + } + addAdvisor((Advisor) advisor); + } + + /** + * Return a TargetSource to use when creating a proxy. If the target was not + * specified at the end of the interceptorNames list, the TargetSource will be + * this class's TargetSource member. Otherwise, we get the target bean and wrap + * it in a TargetSource if necessary. + */ + private TargetSource freshTargetSource() { + if (this.targetName == null) { + if (logger.isTraceEnabled()) { + logger.trace("Not refreshing target: Bean name not specified in 'interceptorNames'."); + } + return this.targetSource; + } + else { + if (this.beanFactory == null) { + throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + + "- cannot resolve target with name '" + this.targetName + "'"); + } + if (logger.isDebugEnabled()) { + logger.debug("Refreshing target with name '" + this.targetName + "'"); + } + Object target = this.beanFactory.getBean(this.targetName); + return (target instanceof TargetSource ? (TargetSource) target : new SingletonTargetSource(target)); + } + } + + /** + * Convert the following object sourced from calling getBean() on a name in the + * interceptorNames array to an Advisor or TargetSource. + */ + private Advisor namedBeanToAdvisor(Object next) { + try { + return this.advisorAdapterRegistry.wrap(next); + } + catch (UnknownAdviceTypeException ex) { + // We expected this to be an Advisor or Advice, + // but it wasn't. This is a configuration error. + throw new AopConfigException("Unknown advisor type " + next.getClass() + + "; Can only include Advisor or Advice type beans in interceptorNames chain except for last entry," + + "which may also be target or TargetSource", ex); + } + } + + /** + * Blow away and recache singleton on an advice change. + */ + protected void adviceChanged() { + super.adviceChanged(); + if (this.singleton) { + logger.debug("Advice has changed; recaching singleton instance"); + synchronized (this) { + this.singletonInstance = null; + } + } + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Initialize transient fields. + this.proxyClassLoader = ClassUtils.getDefaultClassLoader(); + } + + + /** + * Used in the interceptor chain where we need to replace a bean with a prototype + * on creating a proxy. + */ + private static class PrototypePlaceholderAdvisor implements Advisor, Serializable { + + private final String beanName; + + private final String message; + + public PrototypePlaceholderAdvisor(String beanName) { + this.beanName = beanName; + this.message = "Placeholder for prototype Advisor/Advice with bean name '" + beanName + "'"; + } + + public String getBeanName() { + return beanName; + } + + public Advice getAdvice() { + throw new UnsupportedOperationException("Cannot invoke methods: " + this.message); + } + + public boolean isPerInstance() { + throw new UnsupportedOperationException("Cannot invoke methods: " + this.message); + } + + public String toString() { + return this.message; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java new file mode 100644 index 0000000000..4900ba410b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java @@ -0,0 +1,278 @@ +/* + * Copyright 2002-2007 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.aop.framework; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.aop.support.AopUtils; + +/** + * Spring's implementation of the AOP Alliance + * {@link org.aopalliance.intercept.MethodInvocation} interface, + * implementing the extended + * {@link org.springframework.aop.ProxyMethodInvocation} interface. + * + *

Invokes the target object using reflection. Subclasses can override the + * {@link #invokeJoinpoint()} method to change this behavior, so this is also + * a useful base class for more specialized MethodInvocation implementations. + * + *

It is possible to clone an invocation, to invoke {@link #proceed()} + * repeatedly (once per clone), using the {@link #invocableClone()} method. + * It is also possible to attach custom attributes to the invocation, + * using the {@link #setUserAttribute} / {@link #getUserAttribute} methods. + * + *

NOTE: This class is considered internal and should not be + * directly accessed. The sole reason for it being public is compatibility + * with existing framework integrations (e.g. Pitchfork). For any other + * purposes, use the {@link ProxyMethodInvocation} interface instead. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Adrian Colyer + * @see #invokeJoinpoint + * @see #proceed + * @see #invocableClone + * @see #setUserAttribute + * @see #getUserAttribute + */ +public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable { + + protected final Object proxy; + + protected final Object target; + + protected final Method method; + + protected Object[] arguments; + + private final Class targetClass; + + /** + * Lazily initialized map of user-specific attributes for this invocation. + */ + private Map userAttributes; + + /** + * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher + * that need dynamic checks. + */ + protected final List interceptorsAndDynamicMethodMatchers; + + /** + * Index from 0 of the current interceptor we're invoking. + * -1 until we invoke: then the current interceptor. + */ + private int currentInterceptorIndex = -1; + + + /** + * Construct a new ReflectiveMethodInvocation with the given arguments. + * @param proxy the proxy object that the invocation was made on + * @param target the target object to invoke + * @param method the method to invoke + * @param arguments the arguments to invoke the method with + * @param targetClass the target class, for MethodMatcher invocations + * @param interceptorsAndDynamicMethodMatchers interceptors that should be applied, + * along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime. + * MethodMatchers included in this struct must already have been found to have matched + * as far as was possibly statically. Passing an array might be about 10% faster, + * but would complicate the code. And it would work only for static pointcuts. + */ + protected ReflectiveMethodInvocation( + Object proxy, Object target, Method method, Object[] arguments, + Class targetClass, List interceptorsAndDynamicMethodMatchers) { + + this.proxy = proxy; + this.target = target; + this.targetClass = targetClass; + this.method = method; + this.arguments = arguments; + this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; + } + + + public final Object getProxy() { + return this.proxy; + } + + public final Object getThis() { + return this.target; + } + + public final AccessibleObject getStaticPart() { + return this.method; + } + + /** + * Return the method invoked on the proxied interface. + * May or may not correspond with a method invoked on an underlying + * implementation of that interface. + */ + public final Method getMethod() { + return this.method; + } + + public final Object[] getArguments() { + return (this.arguments != null ? this.arguments : new Object[0]); + } + + public void setArguments(Object[] arguments) { + this.arguments = arguments; + } + + + public Object proceed() throws Throwable { + // We start with an index of -1 and increment early. + if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { + return invokeJoinpoint(); + } + + Object interceptorOrInterceptionAdvice = + this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); + if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { + // Evaluate dynamic method matcher here: static part will already have + // been evaluated and found to match. + InterceptorAndDynamicMethodMatcher dm = + (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; + if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { + return dm.interceptor.invoke(this); + } + else { + // Dynamic matching failed. + // Skip this interceptor and invoke the next in the chain. + return proceed(); + } + } + else { + // It's an interceptor, so we just invoke it: The pointcut will have + // been evaluated statically before this object was constructed. + return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); + } + } + + /** + * Invoke the joinpoint using reflection. + * Subclasses can override this to use custom invocation. + * @return the return value of the joinpoint + * @throws Throwable if invoking the joinpoint resulted in an exception + */ + protected Object invokeJoinpoint() throws Throwable { + return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments); + } + + + /** + * This implementation returns a shallow copy of this invocation object, + * including an independent copy of the original arguments array. + *

We want a shallow copy in this case: We want to use the same interceptor + * chain and other object references, but we want an independent value for the + * current interceptor index. + * @see java.lang.Object#clone() + */ + public MethodInvocation invocableClone() { + Object[] cloneArguments = null; + if (this.arguments != null) { + // Build an independent copy of the arguments array. + cloneArguments = new Object[this.arguments.length]; + System.arraycopy(this.arguments, 0, cloneArguments, 0, this.arguments.length); + } + return invocableClone(cloneArguments); + } + + /** + * This implementation returns a shallow copy of this invocation object, + * using the given arguments array for the clone. + *

We want a shallow copy in this case: We want to use the same interceptor + * chain and other object references, but we want an independent value for the + * current interceptor index. + * @see java.lang.Object#clone() + */ + public MethodInvocation invocableClone(Object[] arguments) { + // Force initialization of the user attributes Map, + // for having a shared Map reference in the clone. + if (this.userAttributes == null) { + this.userAttributes = new HashMap(); + } + + // Create the MethodInvocation clone. + try { + ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone(); + clone.arguments = arguments; + return clone; + } + catch (CloneNotSupportedException ex) { + throw new IllegalStateException( + "Should be able to clone object of type [" + getClass() + "]: " + ex); + } + } + + + public void setUserAttribute(String key, Object value) { + if (value != null) { + if (this.userAttributes == null) { + this.userAttributes = new HashMap(); + } + this.userAttributes.put(key, value); + } + else { + if (this.userAttributes != null) { + this.userAttributes.remove(key); + } + } + } + + public Object getUserAttribute(String key) { + return (this.userAttributes != null ? this.userAttributes.get(key) : null); + } + + /** + * Return user attributes associated with this invocation. + * This method provides an invocation-bound alternative to a ThreadLocal. + *

This map is initialized lazily and is not used in the AOP framework itself. + * @return any user attributes associated with this invocation + * (never null) + */ + public Map getUserAttributes() { + if (this.userAttributes == null) { + this.userAttributes = new HashMap(); + } + return this.userAttributes; + } + + + public String toString() { + // Don't do toString on target, it may be proxied. + StringBuffer sb = new StringBuffer("ReflectiveMethodInvocation: "); + sb.append(this.method).append("; "); + if (this.target == null) { + sb.append("target is null"); + } + else { + sb.append("target is of class [").append(this.target.getClass().getName()).append(']'); + } + return sb.toString(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapter.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapter.java new file mode 100644 index 0000000000..5acc518359 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; + +/** + * Interface allowing extension to the Spring AOP framework to allow + * handling of new Advisors and Advice types. + * + *

Implementing objects can create AOP Alliance Interceptors from + * custom advice types, enabling these advice types to be used + * in the Spring AOP framework, which uses interception under the covers. + * + *

There is no need for most Spring users to implement this interface; + * do so only if you need to introduce more Advisor or Advice types to Spring. + * + * @author Rod Johnson + */ +public interface AdvisorAdapter { + + /** + * Does this adapter understand this advice object? Is it valid to + * invoke the getInterceptors method with an Advisor that + * contains this advice as an argument? + * @param advice an Advice such as a BeforeAdvice + * @return whether this adapter understands the given advice object + * @see #getInterceptor(org.springframework.aop.Advisor) + * @see org.springframework.aop.BeforeAdvice + */ + boolean supportsAdvice(Advice advice); + + /** + * Return an AOP Alliance MethodInterceptor exposing the behavior of + * the given advice to an interception-based AOP framework. + *

Don't worry about any Pointcut contained in the Advisor; + * the AOP framework will take care of checking the pointcut. + * @param advisor the Advisor. The supportsAdvice() method must have + * returned true on this object + * @return an AOP Alliance interceptor for this Advisor. There's + * no need to cache instances for efficiency, as the AOP framework + * caches advice chains. + */ + MethodInterceptor getInterceptor(Advisor advisor); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java new file mode 100644 index 0000000000..bd3ebe7d57 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistrationManager.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * BeanPostProcessor that registers {@link AdvisorAdapter} beans in the BeanFactory with + * an {@link AdvisorAdapterRegistry} (by default the {@link GlobalAdvisorAdapterRegistry}). + * + *

The only requirement for it to work is that it needs to be defined + * in application context along with "non-native" Spring AdvisorAdapters + * that need to be "recognized" by Spring's AOP framework. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + * @since 27.02.2004 + * @see #setAdvisorAdapterRegistry + * @see AdvisorAdapter + */ +public class AdvisorAdapterRegistrationManager implements BeanPostProcessor { + + private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); + + + /** + * Specify the AdvisorAdapterRegistry to register AdvisorAdapter beans with. + * Default is the global AdvisorAdapterRegistry. + * @see GlobalAdvisorAdapterRegistry + */ + public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { + this.advisorAdapterRegistry = advisorAdapterRegistry; + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AdvisorAdapter){ + this.advisorAdapterRegistry.registerAdvisorAdapter((AdvisorAdapter) bean); + } + return bean; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java new file mode 100644 index 0000000000..f0b38d0d67 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AdvisorAdapterRegistry.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; + +/** + * Interface for registries of Advisor adapters. + * + *

This is an SPI interface, not to be implemented by any Spring user. + * + * @author Rod Johnson + * @author Rob Harrop + */ +public interface AdvisorAdapterRegistry { + + /** + * Return an Advisor wrapping the given advice. + *

Should by default at least support + * {@link org.aopalliance.intercept.MethodInterceptor}, + * {@link org.springframework.aop.MethodBeforeAdvice}, + * {@link org.springframework.aop.AfterReturningAdvice}, + * {@link org.springframework.aop.ThrowsAdvice}. + * @param advice object that should be an advice + * @return an Advisor wrapping the given advice. Never returns null. + * If the advice parameter is an Advisor, return it. + * @throws UnknownAdviceTypeException if no registered advisor adapter + * can wrap the supposed advice + */ + Advisor wrap(Object advice) throws UnknownAdviceTypeException; + + /** + * Return an array of AOP Alliance MethodInterceptors to allow use of the + * given Advisor in an interception-based framework. + *

Don't worry about the pointcut associated with the Advisor, + * if it's a PointcutAdvisor: just return an interceptor. + * @param advisor Advisor to find an interceptor for + * @return an array of MethodInterceptors to expose this Advisor's behavior + * @throws UnknownAdviceTypeException if the Advisor type is + * not understood by any registered AdvisorAdapter. + */ + MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException; + + /** + * Register the given AdvisorAdapter. Note that it is not necessary to register + * adapters for an AOP Alliance Interceptors or Spring Advices: these must be + * automatically recognized by an AdvisorAdapterRegistry implementation. + * @param adapter AdvisorAdapter that understands a particular Advisor + * or Advice types + */ + void registerAdvisorAdapter(AdvisorAdapter adapter); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceAdapter.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceAdapter.java new file mode 100644 index 0000000000..10f8613300 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceAdapter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.io.Serializable; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; +import org.springframework.aop.AfterReturningAdvice; + +/** + * Adapter to enable {@link org.springframework.aop.AfterReturningAdvice} + * to be used in the Spring AOP framework. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable { + + public boolean supportsAdvice(Advice advice) { + return (advice instanceof AfterReturningAdvice); + } + + public MethodInterceptor getInterceptor(Advisor advisor) { + AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice(); + return new AfterReturningAdviceInterceptor(advice); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java new file mode 100644 index 0000000000..1e0b0b6417 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/AfterReturningAdviceInterceptor.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.io.Serializable; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.AfterAdvice; +import org.springframework.aop.AfterReturningAdvice; +import org.springframework.util.Assert; + +/** + * Interceptor to wrap am {@link org.springframework.aop.AfterReturningAdvice}. + * Used internally by the AOP framework; application developers should not need + * to use this class directly. + * + * @author Rod Johnson + */ +public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable { + + private final AfterReturningAdvice advice; + + + /** + * Create a new AfterReturningAdviceInterceptor for the given advice. + * @param advice the AfterReturningAdvice to wrap + */ + public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + public Object invoke(MethodInvocation mi) throws Throwable { + Object retVal = mi.proceed(); + this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); + return retVal; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java new file mode 100644 index 0000000000..4f9bf03ea1 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/DefaultAdvisorAdapterRegistry.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; +import org.springframework.aop.support.DefaultPointcutAdvisor; + +/** + * Default implementation of the {@link AdvisorAdapterRegistry} interface. + * Supports {@link org.aopalliance.intercept.MethodInterceptor}, + * {@link org.springframework.aop.MethodBeforeAdvice}, + * {@link org.springframework.aop.AfterReturningAdvice}, + * {@link org.springframework.aop.ThrowsAdvice}. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + */ +public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { + + private final List adapters = new ArrayList(3); + + + /** + * Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters. + */ + public DefaultAdvisorAdapterRegistry() { + registerAdvisorAdapter(new MethodBeforeAdviceAdapter()); + registerAdvisorAdapter(new AfterReturningAdviceAdapter()); + registerAdvisorAdapter(new ThrowsAdviceAdapter()); + } + + + public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { + if (adviceObject instanceof Advisor) { + return (Advisor) adviceObject; + } + if (!(adviceObject instanceof Advice)) { + throw new UnknownAdviceTypeException(adviceObject); + } + Advice advice = (Advice) adviceObject; + if (advice instanceof MethodInterceptor) { + // So well-known it doesn't even need an adapter. + return new DefaultPointcutAdvisor(advice); + } + for (int i = 0; i < this.adapters.size(); i++) { + // Check that it is supported. + AdvisorAdapter adapter = (AdvisorAdapter) this.adapters.get(i); + if (adapter.supportsAdvice(advice)) { + return new DefaultPointcutAdvisor(advice); + } + } + throw new UnknownAdviceTypeException(advice); + } + + public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { + List interceptors = new ArrayList(3); + Advice advice = advisor.getAdvice(); + if (advice instanceof MethodInterceptor) { + interceptors.add(advice); + } + for (int i = 0; i < this.adapters.size(); i++) { + AdvisorAdapter adapter = (AdvisorAdapter) this.adapters.get(i); + if (adapter.supportsAdvice(advice)) { + interceptors.add(adapter.getInterceptor(advisor)); + } + } + if (interceptors.isEmpty()) { + throw new UnknownAdviceTypeException(advisor.getAdvice()); + } + return (MethodInterceptor[]) interceptors.toArray(new MethodInterceptor[interceptors.size()]); + } + + public void registerAdvisorAdapter(AdvisorAdapter adapter) { + this.adapters.add(adapter); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/GlobalAdvisorAdapterRegistry.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/GlobalAdvisorAdapterRegistry.java new file mode 100644 index 0000000000..6662c3411b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/GlobalAdvisorAdapterRegistry.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2005 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.aop.framework.adapter; + +/** + * Singleton to publish a shared DefaultAdvisorAdapterRegistry instance. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see DefaultAdvisorAdapterRegistry + */ +public abstract class GlobalAdvisorAdapterRegistry { + + /** + * Keep track of a single instance so we can return it to classes that request it. + */ + private static final AdvisorAdapterRegistry instance = new DefaultAdvisorAdapterRegistry(); + + /** + * Return the singleton DefaultAdvisorAdapterRegistry instance. + */ + public static AdvisorAdapterRegistry getInstance() { + return instance; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceAdapter.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceAdapter.java new file mode 100644 index 0000000000..6757ab65f5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceAdapter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.io.Serializable; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; +import org.springframework.aop.MethodBeforeAdvice; + +/** + * Adapter to enable {@link org.springframework.aop.MethodBeforeAdvice} + * to be used in the Spring AOP framework. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { + + public boolean supportsAdvice(Advice advice) { + return (advice instanceof MethodBeforeAdvice); + } + + public MethodInterceptor getInterceptor(Advisor advisor) { + MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); + return new MethodBeforeAdviceInterceptor(advice); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java new file mode 100644 index 0000000000..82f339b3f6 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/MethodBeforeAdviceInterceptor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.io.Serializable; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.MethodBeforeAdvice; +import org.springframework.util.Assert; + +/** + * Interceptor to wrap am {@link org.springframework.aop.MethodBeforeAdvice}. + * Used internally by the AOP framework; application developers should not need + * to use this class directly. + * + * @author Rod Johnson + */ +public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { + + private MethodBeforeAdvice advice; + + + /** + * Create a new MethodBeforeAdviceInterceptor for the given advice. + * @param advice the MethodBeforeAdvice to wrap + */ + public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + public Object invoke(MethodInvocation mi) throws Throwable { + this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() ); + return mi.proceed(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java new file mode 100644 index 0000000000..0803088e8b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceAdapter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.io.Serializable; + +import org.aopalliance.aop.Advice; +import org.aopalliance.intercept.MethodInterceptor; + +import org.springframework.aop.Advisor; +import org.springframework.aop.ThrowsAdvice; + +/** + * Adapter to enable {@link org.springframework.aop.MethodBeforeAdvice} + * to be used in the Spring AOP framework. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable { + + public boolean supportsAdvice(Advice advice) { + return (advice instanceof ThrowsAdvice); + } + + public MethodInterceptor getInterceptor(Advisor advisor) { + return new ThrowsAdviceInterceptor(advisor.getAdvice()); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java new file mode 100644 index 0000000000..320f7fe5b5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/ThrowsAdviceInterceptor.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2007 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.aop.framework.adapter; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.AfterAdvice; +import org.springframework.util.Assert; + +/** + * Interceptor to wrap an after-throwing advice. + * + *

The signatures on handler methods on the ThrowsAdvice + * implementation method argument must be of the form:
+ * + * void afterThrowing([Method, args, target], ThrowableSubclass); + * + *

Only the last argument is required. + * + *

Some examples of valid methods would be: + * + *

public void afterThrowing(Exception ex)
+ *
public void afterThrowing(RemoteException)
+ *
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
+ *
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)
+ * + *

This is a framework class that need not be used directly by Spring users. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { + + private static final String AFTER_THROWING = "afterThrowing"; + + private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class); + + + private final Object throwsAdvice; + + /** Methods on throws advice, keyed by exception class */ + private final Map exceptionHandlerMap = new HashMap(); + + + /** + * Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice. + * @param throwsAdvice the advice object that defines the exception + * handler methods (usually a {@link org.springframework.aop.ThrowsAdvice} + * implementation) + */ + public ThrowsAdviceInterceptor(Object throwsAdvice) { + Assert.notNull(throwsAdvice, "Advice must not be null"); + this.throwsAdvice = throwsAdvice; + + Method[] methods = throwsAdvice.getClass().getMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if (method.getName().equals(AFTER_THROWING) && + //m.getReturnType() == null && + (method.getParameterTypes().length == 1 || method.getParameterTypes().length == 4) && + Throwable.class.isAssignableFrom(method.getParameterTypes()[method.getParameterTypes().length - 1]) + ) { + // Have an exception handler + this.exceptionHandlerMap.put(method.getParameterTypes()[method.getParameterTypes().length - 1], method); + if (logger.isDebugEnabled()) { + logger.debug("Found exception handler method: " + method); + } + } + } + + if (this.exceptionHandlerMap.isEmpty()) { + throw new IllegalArgumentException( + "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]"); + } + } + + public int getHandlerMethodCount() { + return this.exceptionHandlerMap.size(); + } + + /** + * Determine the exception handle method. Can return null if not found. + * @param exception the exception thrown + * @return a handler for the given exception type + */ + private Method getExceptionHandler(Throwable exception) { + Class exceptionClass = exception.getClass(); + if (logger.isTraceEnabled()) { + logger.trace("Trying to find handler for exception of type [" + exceptionClass.getName() + "]"); + } + Method handler = (Method) this.exceptionHandlerMap.get(exceptionClass); + while (handler == null && !exceptionClass.equals(Throwable.class)) { + exceptionClass = exceptionClass.getSuperclass(); + handler = (Method) this.exceptionHandlerMap.get(exceptionClass); + } + if (handler != null && logger.isDebugEnabled()) { + logger.debug("Found handler for exception of type [" + exceptionClass.getName() + "]: " + handler); + } + return handler; + } + + public Object invoke(MethodInvocation mi) throws Throwable { + try { + return mi.proceed(); + } + catch (Throwable ex) { + Method handlerMethod = getExceptionHandler(ex); + if (handlerMethod != null) { + invokeHandlerMethod(mi, ex, handlerMethod); + } + throw ex; + } + } + + private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable { + Object[] handlerArgs; + if (method.getParameterTypes().length == 1) { + handlerArgs = new Object[] { ex }; + } + else { + handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex}; + } + try { + method.invoke(this.throwsAdvice, handlerArgs); + } + catch (InvocationTargetException targetEx) { + throw targetEx.getTargetException(); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/UnknownAdviceTypeException.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/UnknownAdviceTypeException.java new file mode 100644 index 0000000000..5d216e9b6b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/UnknownAdviceTypeException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2005 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.aop.framework.adapter; + +/** + * Exception thrown when an attempt is made to use an unsupported + * Advisor or Advice type. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.aopalliance.aop.Advice + * @see org.springframework.aop.Advisor + */ +public class UnknownAdviceTypeException extends IllegalArgumentException { + + /** + * Create a new UnknownAdviceTypeException for the given advice object. + * Will create a message text that says that the object is neither a + * subinterface of Advice nor an Advisor. + * @param advice the advice object of unknown type + */ + public UnknownAdviceTypeException(Object advice) { + super("Advice object [" + advice + "] is neither a supported subinterface of " + + "[org.aopalliance.aop.Advice] nor an [org.springframework.aop.Advisor]"); + } + + /** + * Create a new UnknownAdviceTypeException with the given message. + * @param message the message text + */ + public UnknownAdviceTypeException(String message) { + super(message); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/package.html new file mode 100644 index 0000000000..f7bd84e4b6 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/adapter/package.html @@ -0,0 +1,17 @@ + + + +SPI package allowing Spring AOP framework to handle arbitrary advice types. +
+Users who want merely to use the Spring AOP framework, rather than extend +its capabilities, don't need to concern themselves with this package. +
+ You may wish to use these adapters to wrap Spring-specific advices, such as MethodBeforeAdvice, + in MethodInterceptor, to allow their use in another AOP framework supporting the AOP Alliance interfaces. +
+
+ These adapters do not depend on any other Spring framework classes to allow such usage. +
+ + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java new file mode 100644 index 0000000000..4df045c18c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java @@ -0,0 +1,178 @@ +/* + * Copyright 2002-2007 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.aop.framework.autoproxy; + +import java.util.Collections; +import java.util.List; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.OrderComparator; + +/** + * Generic auto proxy creator that builds AOP proxies for specific beans + * based on detected Advisors for each bean. + * + *

Subclasses must implement the abstract {@link #findCandidateAdvisors()} + * method to return a list of Advisors applying to any object. Subclasses can + * also override the inherited {@link #shouldSkip} method to exclude certain + * objects from auto-proxying. + * + *

Advisors or advices requiring ordering should implement the + * {@link org.springframework.core.Ordered} interface. This class sorts + * Advisors by Ordered order value. Advisors that don't implement the + * Ordered interface will be considered as unordered; they will appear + * at the end of the advisor chain in undefined order. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #findCandidateAdvisors + */ +public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator { + + private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper; + + + public void setBeanFactory(BeanFactory beanFactory) { + super.setBeanFactory(beanFactory); + if (!(beanFactory instanceof ConfigurableListableBeanFactory)) { + throw new IllegalStateException("Cannot use AdvisorAutoProxyCreator without a ConfigurableListableBeanFactory"); + } + initBeanFactory((ConfigurableListableBeanFactory) beanFactory); + } + + protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) { + this.advisorRetrievalHelper = new BeanFactoryAdvisorRetrievalHelperAdapter(beanFactory); + } + + + protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) { + List advisors = findEligibleAdvisors(beanClass, beanName); + if (advisors.isEmpty()) { + return DO_NOT_PROXY; + } + return advisors.toArray(); + } + + /** + * Find all eligible Advisors for auto-proxying this class. + * @param beanClass the clazz to find advisors for + * @param beanName the name of the currently proxied bean + * @return the empty List, not null, + * if there are no pointcuts or interceptors + * @see #findCandidateAdvisors + * @see #sortAdvisors + * @see #extendAdvisors + */ + protected List findEligibleAdvisors(Class beanClass, String beanName) { + List candidateAdvisors = findCandidateAdvisors(); + List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); + if (!eligibleAdvisors.isEmpty()) { + eligibleAdvisors = sortAdvisors(eligibleAdvisors); + } + extendAdvisors(eligibleAdvisors); + return eligibleAdvisors; + } + + /** + * Find all candidate Advisors to use in auto-proxying. + * @return the List of candidate Advisors + */ + protected List findCandidateAdvisors() { + return this.advisorRetrievalHelper.findAdvisorBeans(); + } + + /** + * Search the given candidate Advisors to find all Advisors that + * can apply to the specified bean. + * @param candidateAdvisors the candidate Advisors + * @param beanClass the target's bean class + * @param beanName the target's bean name + * @return the List of applicable Advisors + * @see ProxyCreationContext#getCurrentProxiedBeanName() + */ + protected List findAdvisorsThatCanApply(List candidateAdvisors, Class beanClass, String beanName) { + ProxyCreationContext.setCurrentProxiedBeanName(beanName); + try { + return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); + } + finally { + ProxyCreationContext.setCurrentProxiedBeanName(null); + } + } + + /** + * Return whether the Advisor bean with the given name is eligible + * for proxying in the first place. + * @param beanName the name of the Advisor bean + * @return whether the bean is eligible + */ + protected boolean isEligibleAdvisorBean(String beanName) { + return true; + } + + /** + * Sort advisors based on ordering. Subclasses may choose to override this + * method to customize the sorting strategy. + * @param advisors the source List of Advisors + * @return the sorted List of Advisors + * @see org.springframework.core.Ordered + * @see org.springframework.core.OrderComparator + */ + protected List sortAdvisors(List advisors) { + Collections.sort(advisors, new OrderComparator()); + return advisors; + } + + /** + * Extension hook that subclasses can override to register additional Advisors, + * given the sorted Advisors obtained to date. + *

The default implementation is empty. + *

Typically used to add Advisors that expose contextual information + * required by some of the later advisors. + * @param candidateAdvisors Advisors that have already been identified as + * applying to a given bean + */ + protected void extendAdvisors(List candidateAdvisors) { + } + + /** + * This auto-proxy creator always returns pre-filtered Advisors. + */ + protected boolean advisorsPreFiltered() { + return true; + } + + + /** + * Subclass of BeanFactoryAdvisorRetrievalHelper that delegates to + * surrounding AbstractAdvisorAutoProxyCreator facilities. + */ + private class BeanFactoryAdvisorRetrievalHelperAdapter extends BeanFactoryAdvisorRetrievalHelper { + + public BeanFactoryAdvisorRetrievalHelperAdapter(ConfigurableListableBeanFactory beanFactory) { + super(beanFactory); + } + + protected boolean isEligibleBean(String beanName) { + return AbstractAdvisorAutoProxyCreator.this.isEligibleAdvisorBean(beanName); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java new file mode 100644 index 0000000000..49dd1f80e6 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -0,0 +1,611 @@ +/* + * Copyright 2002-2008 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.aop.framework.autoproxy; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.aopalliance.aop.Advice; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.Advisor; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.aop.framework.ProxyConfig; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry; +import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry; +import org.springframework.aop.target.SingletonTargetSource; +import org.springframework.beans.BeansException; +import org.springframework.beans.PropertyValues; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; +import org.springframework.core.CollectionFactory; +import org.springframework.core.Ordered; +import org.springframework.util.ClassUtils; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation + * that wraps each eligible bean with an AOP proxy, delegating to specified interceptors + * before invoking the bean itself. + * + *

This class distinguishes between "common" interceptors: shared for all proxies it + * creates, and "specific" interceptors: unique per bean instance. There need not + * be any common interceptors. If there are, they are set using the interceptorNames + * property. As with ProxyFactoryBean, interceptors names in the current factory + * are used rather than bean references to allow correct handling of prototype + * advisors and interceptors: for example, to support stateful mixins. + * Any advice type is supported for "interceptorNames" entries. + * + *

Such auto-proxying is particularly useful if there's a large number of beans that + * need to be wrapped with similar proxies, i.e. delegating to the same interceptors. + * Instead of x repetitive proxy definitions for x target beans, you can register + * one single such post processor with the bean factory to achieve the same effect. + * + *

Subclasses can apply any strategy to decide if a bean is to be proxied, + * e.g. by type, by name, by definition details, etc. They can also return + * additional interceptors that should just be applied to the specific bean + * instance. The default concrete implementation is BeanNameAutoProxyCreator, + * identifying the beans to be proxied via a list of bean names. + * + *

Any number of {@link TargetSourceCreator} implementations can be used to create + * a custom target source - for example, to pool prototype objects. Auto-proxying will + * occur even if there is no advice, as long as a TargetSourceCreator specifies a custom + * {@link org.springframework.aop.TargetSource}. If there are no TargetSourceCreators set, + * or if none matches, a {@link org.springframework.aop.target.SingletonTargetSource} + * will be used by default to wrap the target bean instance. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @author Rob Harrop + * @since 13.10.2003 + * @see #setInterceptorNames + * @see #getAdvicesAndAdvisorsForBean + * @see BeanNameAutoProxyCreator + * @see DefaultAdvisorAutoProxyCreator + */ +public abstract class AbstractAutoProxyCreator extends ProxyConfig + implements SmartInstantiationAwareBeanPostProcessor, BeanClassLoaderAware, BeanFactoryAware, + Ordered, AopInfrastructureBean { + + /** + * Convenience constant for subclasses: Return value for "do not proxy". + * @see #getAdvicesAndAdvisorsForBean + */ + protected static final Object[] DO_NOT_PROXY = null; + + /** + * Convenience constant for subclasses: Return value for + * "proxy without additional interceptors, just the common ones". + * @see #getAdvicesAndAdvisorsForBean + */ + protected static final Object[] PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS = new Object[0]; + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** Default value is same as non-ordered */ + private int order = Integer.MAX_VALUE; + + /** Default is global AdvisorAdapterRegistry */ + private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); + + /** + * Indicates whether or not the proxy should be frozen. Overridden from super + * to prevent the configuration from becoming frozen too early. + */ + private boolean freezeProxy = false; + + /** Default is no common interceptors */ + private String[] interceptorNames = new String[0]; + + private boolean applyCommonInterceptorsFirst = true; + + private TargetSourceCreator[] customTargetSourceCreators; + + private ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader(); + + private boolean classLoaderConfigured = false; + + private BeanFactory beanFactory; + + /** + * Set of bean name Strings, referring to all beans that this auto-proxy creator + * created a custom TargetSource for. Used to detect own pre-built proxies (from + * "postProcessBeforeInstantiation") in the "postProcessAfterInitialization" method. + */ + private final Set targetSourcedBeans = Collections.synchronizedSet(new HashSet()); + + private final Set earlyProxyReferences = Collections.synchronizedSet(new HashSet()); + + private final Set advisedBeans = Collections.synchronizedSet(new HashSet()); + + private final Set nonAdvisedBeans = Collections.synchronizedSet(new HashSet()); + + private final Map proxyTypes = CollectionFactory.createConcurrentMapIfPossible(16); + + + /** + * Set the ordering which will apply to this class's implementation + * of Ordered, used when applying multiple BeanPostProcessors. + *

Default value is Integer.MAX_VALUE, meaning that it's non-ordered. + * @param order ordering value + */ + public final void setOrder(int order) { + this.order = order; + } + + public final int getOrder() { + return this.order; + } + + /** + * Set whether or not the proxy should be frozen, preventing advice + * from being added to it once it is created. + *

Overridden from the super class to prevent the proxy configuration + * from being frozen before the proxy is created. + */ + public void setFrozen(boolean frozen) { + this.freezeProxy = frozen; + } + + public boolean isFrozen() { + return this.freezeProxy; + } + + /** + * Specify the AdvisorAdapterRegistry to use. + * Default is the global AdvisorAdapterRegistry. + * @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry + */ + public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { + this.advisorAdapterRegistry = advisorAdapterRegistry; + } + + /** + * Set custom TargetSourceCreators to be applied in this order. + * If the list is empty, or they all return null, a SingletonTargetSource + * will be created for each bean. + *

Note that TargetSourceCreators will kick in even for target beans + * where no advices or advisors have been found. If a TargetSourceCreator + * returns a TargetSource for a specific bean, that bean will be proxied + * in any case. + *

TargetSourceCreators can only be invoked if this post processor is used + * in a BeanFactory, and its BeanFactoryAware callback is used. + * @param targetSourceCreators list of TargetSourceCreator. + * Ordering is significant: The TargetSource returned from the first matching + * TargetSourceCreator (that is, the first that returns non-null) will be used. + */ + public void setCustomTargetSourceCreators(TargetSourceCreator[] targetSourceCreators) { + this.customTargetSourceCreators = targetSourceCreators; + } + + /** + * Set the common interceptors. These must be bean names in the current factory. + * They can be of any advice or advisor type Spring supports. + *

If this property isn't set, there will be zero common interceptors. + * This is perfectly valid, if "specific" interceptors such as matching + * Advisors are all we want. + */ + public void setInterceptorNames(String[] interceptorNames) { + this.interceptorNames = interceptorNames; + } + + /** + * Set whether the common interceptors should be applied before bean-specific ones. + * Default is "true"; else, bean-specific interceptors will get applied first. + */ + public void setApplyCommonInterceptorsFirst(boolean applyCommonInterceptorsFirst) { + this.applyCommonInterceptorsFirst = applyCommonInterceptorsFirst; + } + + /** + * Set the ClassLoader to generate the proxy class in. + *

Default is the bean ClassLoader, i.e. the ClassLoader used by the + * containing BeanFactory for loading all bean classes. This can be + * overridden here for specific proxies. + */ + public void setProxyClassLoader(ClassLoader classLoader) { + this.proxyClassLoader = classLoader; + this.classLoaderConfigured = (classLoader != null); + } + + public void setBeanClassLoader(ClassLoader classLoader) { + if (!this.classLoaderConfigured) { + this.proxyClassLoader = classLoader; + } + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + /** + * Return the owning BeanFactory. + * May be null, as this object doesn't need to belong to a bean factory. + */ + protected BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + public Class predictBeanType(Class beanClass, String beanName) { + Object cacheKey = getCacheKey(beanClass, beanName); + return (Class) this.proxyTypes.get(cacheKey); + } + + public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException { + return null; + } + + public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { + Object cacheKey = getCacheKey(bean.getClass(), beanName); + this.earlyProxyReferences.add(cacheKey); + return wrapIfNecessary(bean, beanName, cacheKey); + } + + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + Object cacheKey = getCacheKey(beanClass, beanName); + + if (!this.targetSourcedBeans.contains(cacheKey)) { + if (this.advisedBeans.contains(cacheKey) || this.nonAdvisedBeans.contains(cacheKey)) { + return null; + } + if (isInfrastructureClass(beanClass, beanName) || shouldSkip(beanClass, beanName)) { + this.nonAdvisedBeans.add(cacheKey); + return null; + } + } + + // Create proxy here if we have a custom TargetSource. + // Suppresses unnecessary default instantiation of the target bean: + // The TargetSource will handle target instances in a custom fashion. + TargetSource targetSource = getCustomTargetSource(beanClass, beanName); + if (targetSource != null) { + this.targetSourcedBeans.add(beanName); + Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); + Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); + this.proxyTypes.put(cacheKey, proxy.getClass()); + return proxy; + } + + return null; + } + + public boolean postProcessAfterInstantiation(Object bean, String beanName) { + return true; + } + + public PropertyValues postProcessPropertyValues( + PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) { + + return pvs; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) { + return bean; + } + + /** + * Create a proxy with the configured interceptors if the bean is + * identified as one to proxy by the subclass. + * @see #getAdvicesAndAdvisorsForBean + */ + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean != null) { + Object cacheKey = getCacheKey(bean.getClass(), beanName); + if (!this.earlyProxyReferences.contains(cacheKey)) { + return wrapIfNecessary(bean, beanName, cacheKey); + } + } + return bean; + } + + + /** + * Build a cache key for the given bean class and bean name. + * @param beanClass the bean class + * @param beanName the bean name + * @return the cache key for the given class and name + */ + protected Object getCacheKey(Class beanClass, String beanName) { + return beanClass.getName() + "_" + beanName; + } + + /** + * Wrap the given bean if necessary, i.e. if it is eligible for being proxied. + * @param bean the raw bean instance + * @param beanName the name of the bean + * @param cacheKey the cache key for metadata access + * @return a proxy wrapping the bean, or the raw bean instance as-is + */ + protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { + if (this.targetSourcedBeans.contains(beanName)) { + return bean; + } + if (this.nonAdvisedBeans.contains(cacheKey)) { + return bean; + } + if (isInfrastructureClass(bean.getClass(), beanName) || shouldSkip(bean.getClass(), beanName)) { + this.nonAdvisedBeans.add(cacheKey); + return bean; + } + + // Create proxy if we have advice. + Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); + if (specificInterceptors != DO_NOT_PROXY) { + this.advisedBeans.add(cacheKey); + Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); + this.proxyTypes.put(cacheKey, proxy.getClass()); + return proxy; + } + + this.nonAdvisedBeans.add(cacheKey); + return bean; + } + + /** + * Return whether the given bean class and bean name represents an + * infrastructure class that should never be proxied. + * @deprecated in favor of isInfrastructureClass(beanClass) + * @see #isInfrastructureClass(Class) + */ + protected boolean isInfrastructureClass(Class beanClass, String beanName) { + return isInfrastructureClass(beanClass); + } + + /** + * Return whether the given bean class represents an infrastructure class + * that should never be proxied. + *

Default implementation considers Advisors, Advices and + * AbstractAutoProxyCreators as infrastructure classes. + * @param beanClass the class of the bean + * @return whether the bean represents an infrastructure class + * @see org.springframework.aop.Advisor + * @see org.aopalliance.intercept.MethodInterceptor + * @see #shouldSkip + */ + protected boolean isInfrastructureClass(Class beanClass) { + boolean retVal = Advisor.class.isAssignableFrom(beanClass) || + Advice.class.isAssignableFrom(beanClass) || + AopInfrastructureBean.class.isAssignableFrom(beanClass); + if (retVal && logger.isTraceEnabled()) { + logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]"); + } + return retVal; + } + + /** + * Subclasses should override this method to return true if the + * given bean should not be considered for auto-proxying by this post-processor. + *

Sometimes we need to be able to avoid this happening if it will lead to + * a circular reference. This implementation returns false. + * @param beanClass the class of the bean + * @param beanName the name of the bean + * @return whether to skip the given bean + */ + protected boolean shouldSkip(Class beanClass, String beanName) { + return false; + } + + /** + * Create a target source for bean instances. Uses any TargetSourceCreators if set. + * Returns null if no custom TargetSource should be used. + *

This implementation uses the "customTargetSourceCreators" property. + * Subclasses can override this method to use a different mechanism. + * @param beanClass the class of the bean to create a TargetSource for + * @param beanName the name of the bean + * @return a TargetSource for this bean + * @see #setCustomTargetSourceCreators + */ + protected TargetSource getCustomTargetSource(Class beanClass, String beanName) { + // We can't create fancy target sources for directly registered singletons. + if (this.customTargetSourceCreators != null && + this.beanFactory != null && this.beanFactory.containsBean(beanName)) { + for (int i = 0; i < this.customTargetSourceCreators.length; i++) { + TargetSourceCreator tsc = this.customTargetSourceCreators[i]; + TargetSource ts = tsc.getTargetSource(beanClass, beanName); + if (ts != null) { + // Found a matching TargetSource. + if (logger.isDebugEnabled()) { + logger.debug("TargetSourceCreator [" + tsc + + " found custom TargetSource for bean with name '" + beanName + "'"); + } + return ts; + } + } + } + + // No custom TargetSource found. + return null; + } + + /** + * Create an AOP proxy for the given bean. + * @param beanClass the class of the bean + * @param beanName the name of the bean + * @param specificInterceptors the set of interceptors that is + * specific to this bean (may be empty, but not null) + * @param targetSource the TargetSource for the proxy, + * already pre-configured to access the bean + * @return the AOP proxy for the bean + * @see #buildAdvisors + */ + protected Object createProxy( + Class beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { + + ProxyFactory proxyFactory = new ProxyFactory(); + // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig. + proxyFactory.copyFrom(this); + + if (!shouldProxyTargetClass(beanClass, beanName)) { + // Must allow for introductions; can't just set interfaces to + // the target's interfaces only. + Class[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader); + for (int i = 0; i < targetInterfaces.length; i++) { + proxyFactory.addInterface(targetInterfaces[i]); + } + } + + Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); + for (int i = 0; i < advisors.length; i++) { + proxyFactory.addAdvisor(advisors[i]); + } + + proxyFactory.setTargetSource(targetSource); + customizeProxyFactory(proxyFactory); + + proxyFactory.setFrozen(this.freezeProxy); + if (advisorsPreFiltered()) { + proxyFactory.setPreFiltered(true); + } + + return proxyFactory.getProxy(this.proxyClassLoader); + } + + /** + * Determine whether the given bean should be proxied with its target + * class rather than its interfaces. Checks the + * {@link #setProxyTargetClass "proxyTargetClass" setting} as well as the + * {@link AutoProxyUtils#PRESERVE_TARGET_CLASS_ATTRIBUTE "preserveTargetClass" attribute} + * of the corresponding bean definition. + * @param beanClass the class of the bean + * @param beanName the name of the bean + * @return whether the given bean should be proxied with its target class + * @see AutoProxyUtils#shouldProxyTargetClass + */ + protected boolean shouldProxyTargetClass(Class beanClass, String beanName) { + return (isProxyTargetClass() || + (this.beanFactory instanceof ConfigurableListableBeanFactory && + AutoProxyUtils.shouldProxyTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName))); + } + + /** + * Return whether the Advisors returned by the subclass are pre-filtered + * to match the bean's target class already, allowing the ClassFilter check + * to be skipped when building advisors chains for AOP invocations. + *

Default is false. Subclasses may override this if they + * will always return pre-filtered Advisors. + * @return whether the Advisors are pre-filtered + * @see #getAdvicesAndAdvisorsForBean + * @see org.springframework.aop.framework.Advised#setPreFiltered + */ + protected boolean advisorsPreFiltered() { + return false; + } + + /** + * Determine the advisors for the given bean, including the specific interceptors + * as well as the common interceptor, all adapted to the Advisor interface. + * @param beanName the name of the bean + * @param specificInterceptors the set of interceptors that is + * specific to this bean (may be empty, but not null) + * @return the list of Advisors for the given bean + */ + protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) { + // Handle prototypes correctly... + Advisor[] commonInterceptors = resolveInterceptorNames(); + + List allInterceptors = new ArrayList(); + if (specificInterceptors != null) { + allInterceptors.addAll(Arrays.asList(specificInterceptors)); + if (commonInterceptors != null) { + if (this.applyCommonInterceptorsFirst) { + allInterceptors.addAll(0, Arrays.asList(commonInterceptors)); + } + else { + allInterceptors.addAll(Arrays.asList(commonInterceptors)); + } + } + } + if (logger.isDebugEnabled()) { + int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0); + int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0); + logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors + + " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors"); + } + + Advisor[] advisors = new Advisor[allInterceptors.size()]; + for (int i = 0; i < allInterceptors.size(); i++) { + advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i)); + } + return advisors; + } + + /** + * Resolves the specified interceptor names to Advisor objects. + * @see #setInterceptorNames + */ + private Advisor[] resolveInterceptorNames() { + ConfigurableBeanFactory cbf = + (this.beanFactory instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) this.beanFactory : null); + List advisors = new ArrayList(); + for (int i = 0; i < this.interceptorNames.length; i++) { + String beanName = this.interceptorNames[i]; + if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) { + Object next = this.beanFactory.getBean(beanName); + advisors.add(this.advisorAdapterRegistry.wrap(next)); + } + } + return (Advisor[]) advisors.toArray(new Advisor[advisors.size()]); + } + + /** + * Subclasses may choose to implement this: for example, + * to change the interfaces exposed. + *

The default implementation is empty. + * @param proxyFactory ProxyFactory that is already configured with + * TargetSource and interfaces and will be used to create the proxy + * immediably after this method returns + */ + protected void customizeProxyFactory(ProxyFactory proxyFactory) { + } + + + /** + * Return whether the given bean is to be proxied, what additional + * advices (e.g. AOP Alliance interceptors) and advisors to apply. + * @param beanClass the class of the bean to advise + * @param beanName the name of the bean + * @param customTargetSource the TargetSource returned by the + * {@link #getCustomTargetSource} method: may be ignored. + * Will be null if no custom target source is in use. + * @return an array of additional interceptors for the particular bean; + * or an empty array if no additional interceptors but just the common ones; + * or null if no proxy at all, not even with the common interceptors. + * See constants DO_NOT_PROXY and PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS. + * @throws BeansException in case of errors + * @see #DO_NOT_PROXY + * @see #PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS + */ + protected abstract Object[] getAdvicesAndAdvisorsForBean( + Class beanClass, String beanName, TargetSource customTargetSource) throws BeansException; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java new file mode 100644 index 0000000000..4d0fe9d69b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/AutoProxyUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2007 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.aop.framework.autoproxy; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.Conventions; + +/** + * Utilities for auto-proxy aware components. + * Mainly for internal use within the framework. + * + * @author Juergen Hoeller + * @since 2.0.3 + * @see AbstractAutoProxyCreator + */ +public abstract class AutoProxyUtils { + + /** + * Bean definition attribute that may indicate whether a given bean is supposed + * to be proxied with its target class (in case of it getting proxied in the first + * place). The value is Boolean.TRUE or Boolean.FALSE. + *

Proxy factories can set this attribute if they built a target class proxy + * for a specific bean, and want to enforce that that bean can always be cast + * to its target class (even if AOP advices get applied through auto-proxying). + */ + public static final String PRESERVE_TARGET_CLASS_ATTRIBUTE = + Conventions.getQualifiedAttributeName(AutoProxyUtils.class, "preserveTargetClass"); + + + /** + * Determine whether the given bean should be proxied with its target + * class rather than its interfaces. Checks the + * {@link #PRESERVE_TARGET_CLASS_ATTRIBUTE "preserveTargetClass" attribute} + * of the corresponding bean definition. + * @param beanFactory the containing ConfigurableListableBeanFactory + * @param beanName the name of the bean + * @return whether the given bean should be proxied with its target class + */ + public static boolean shouldProxyTargetClass(ConfigurableListableBeanFactory beanFactory, String beanName) { + if (beanFactory.containsBeanDefinition(beanName)) { + BeanDefinition bd = beanFactory.getBeanDefinition(beanName); + return Boolean.TRUE.equals(bd.getAttribute(PRESERVE_TARGET_CLASS_ATTRIBUTE)); + } + return false; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java new file mode 100644 index 0000000000..40ed9e85e7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanFactoryAdvisorRetrievalHelper.java @@ -0,0 +1,119 @@ +/* + * Copyright 2002-2007 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.aop.framework.autoproxy; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.Advisor; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.Assert; + +/** + * Helper for retrieving standard Spring Advisors from a BeanFactory, + * for use with auto-proxying. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see AbstractAdvisorAutoProxyCreator + */ +public class BeanFactoryAdvisorRetrievalHelper { + + private static final Log logger = LogFactory.getLog(BeanFactoryAdvisorRetrievalHelper.class); + + private final ConfigurableListableBeanFactory beanFactory; + + private String[] cachedAdvisorBeanNames; + + + /** + * Create a new BeanFactoryAdvisorRetrievalHelper for the given BeanFactory. + * @param beanFactory the ListableBeanFactory to scan + */ + public BeanFactoryAdvisorRetrievalHelper(ConfigurableListableBeanFactory beanFactory) { + Assert.notNull(beanFactory, "ListableBeanFactory must not be null"); + this.beanFactory = beanFactory; + } + + + /** + * Find all eligible Advisor beans in the current bean factory, + * ignoring FactoryBeans and excluding beans that are currently in creation. + * @return the list of {@link org.springframework.aop.Advisor} beans + * @see #isEligibleBean + */ + public List findAdvisorBeans() { + // Determine list of advisor bean names, if not cached already. + String[] advisorNames = null; + synchronized (this) { + advisorNames = this.cachedAdvisorBeanNames; + if (advisorNames == null) { + // Do not initialize FactoryBeans here: We need to leave all regular beans + // uninitialized to let the auto-proxy creator apply to them! + advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + this.beanFactory, Advisor.class, true, false); + this.cachedAdvisorBeanNames = advisorNames; + } + } + if (advisorNames.length == 0) { + return new LinkedList(); + } + + List advisors = new LinkedList(); + for (int i = 0; i < advisorNames.length; i++) { + String name = advisorNames[i]; + if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) { + try { + advisors.add(this.beanFactory.getBean(name)); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { + if (logger.isDebugEnabled()) { + logger.debug("Ignoring currently created advisor '" + name + "': " + ex.getMessage()); + } + // Ignore: indicates a reference back to the bean we're trying to advise. + // We want to find advisors other than the currently created bean itself. + continue; + } + } + throw ex; + } + } + } + return advisors; + } + + /** + * Determine whether the aspect bean with the given name is eligible. + *

The default implementation always returns true. + * @param beanName the name of the aspect bean + * @return whether the bean is eligible + */ + protected boolean isEligibleBean(String beanName) { + return true; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java new file mode 100644 index 0000000000..c5f33ff400 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/BeanNameAutoProxyCreator.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2006 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.aop.framework.autoproxy; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.springframework.aop.TargetSource; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.util.Assert; +import org.springframework.util.PatternMatchUtils; +import org.springframework.util.StringUtils; + +/** + * Auto proxy creator that identifies beans to proxy via a list of names. + * Checks for direct, "xxx*", and "*xxx" matches. + * + *

For configuration details, see the javadoc of the parent class + * AbstractAutoProxyCreator. Typically, you will specify a list of + * interceptor names to apply to all identified beans, via the + * "interceptorNames" property. + * + * @author Juergen Hoeller + * @since 10.10.2003 + * @see #setBeanNames + * @see #isMatch + * @see #setInterceptorNames + * @see AbstractAutoProxyCreator + */ +public class BeanNameAutoProxyCreator extends AbstractAutoProxyCreator { + + private List beanNames; + + + /** + * Set the names of the beans that should automatically get wrapped with proxies. + * A name can specify a prefix to match by ending with "*", e.g. "myBean,tx*" + * will match the bean named "myBean" and all beans whose name start with "tx". + *

NOTE: In case of a FactoryBean, only the objects created by the + * FactoryBean will get proxied. This default behavior applies as of Spring 2.0. + * If you intend to proxy a FactoryBean instance itself (a rare use case, but + * Spring 1.2's default behavior), specify the bean name of the FactoryBean + * including the factory-bean prefix "&": e.g. "&myFactoryBean". + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.BeanFactory#FACTORY_BEAN_PREFIX + */ + public void setBeanNames(String[] beanNames) { + Assert.notEmpty(beanNames, "'beanNames' must not be empty"); + this.beanNames = new ArrayList(beanNames.length); + for (int i = 0; i < beanNames.length; i++) { + this.beanNames.add(StringUtils.trimWhitespace(beanNames[i])); + } + } + + + /** + * Identify as bean to proxy if the bean name is in the configured list of names. + */ + protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) { + if (this.beanNames != null) { + for (Iterator it = this.beanNames.iterator(); it.hasNext();) { + String mappedName = (String) it.next(); + if (FactoryBean.class.isAssignableFrom(beanClass)) { + if (!mappedName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { + continue; + } + mappedName = mappedName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); + } + if (isMatch(beanName, mappedName)) { + return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS; + } + } + } + return DO_NOT_PROXY; + } + + /** + * Return if the given bean name matches the mapped name. + *

The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, + * as well as direct equality. Can be overridden in subclasses. + * @param beanName the bean name to check + * @param mappedName the name in the configured list of names + * @return if the names match + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) + */ + protected boolean isMatch(String beanName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, beanName); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java new file mode 100644 index 0000000000..aa05c646bb --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/DefaultAdvisorAutoProxyCreator.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2006 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.aop.framework.autoproxy; + +import org.springframework.beans.factory.BeanNameAware; + +/** + * BeanPostProcessor implementation that creates AOP proxies based on all candidate + * Advisors in the current BeanFactory. This class is completely generic; it contains + * no special code to handle any particular aspects, such as pooling aspects. + * + *

It's possible to filter out advisors - for example, to use multiple post processors + * of this type in the same factory - by setting the usePrefix property + * to true, in which case only advisors beginning with the DefaultAdvisorAutoProxyCreator's + * bean name followed by a dot (like "aapc.") will be used. This default prefix can be + * changed from the bean name by setting the advisorBeanNamePrefix property. + * The separator (.) will also be used in this case. + * + * @author Rod Johnson + * @author Rob Harrop + */ +public class DefaultAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator implements BeanNameAware { + + /** Separator between prefix and remainder of bean name */ + public final static String SEPARATOR = "."; + + + private boolean usePrefix; + + private String advisorBeanNamePrefix; + + + /** + * Set whether to exclude advisors with a certain prefix + * in the bean name. + */ + public void setUsePrefix(boolean usePrefix) { + this.usePrefix = usePrefix; + } + + /** + * Return whether to exclude advisors with a certain prefix + * in the bean name. + */ + public boolean isUsePrefix() { + return this.usePrefix; + } + + /** + * Set the prefix for bean names that will cause them to be included for + * auto-proxying by this object. This prefix should be set to avoid circular + * references. Default value is the bean name of this object + a dot. + * @param advisorBeanNamePrefix the exclusion prefix + */ + public void setAdvisorBeanNamePrefix(String advisorBeanNamePrefix) { + this.advisorBeanNamePrefix = advisorBeanNamePrefix; + } + + /** + * Return the prefix for bean names that will cause them to be included + * for auto-proxying by this object. + */ + public String getAdvisorBeanNamePrefix() { + return this.advisorBeanNamePrefix; + } + + public void setBeanName(String name) { + // If no infrastructure bean name prefix has been set, override it. + if (this.advisorBeanNamePrefix == null) { + this.advisorBeanNamePrefix = name + SEPARATOR; + } + } + + + /** + * Consider Advisor beans with the specified prefix as eligible, if activated. + * @see #setUsePrefix + * @see #setAdvisorBeanNamePrefix + */ + protected boolean isEligibleAdvisorBean(String beanName) { + return (!isUsePrefix() || beanName.startsWith(getAdvisorBeanNamePrefix())); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/InfrastructureAdvisorAutoProxyCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/InfrastructureAdvisorAutoProxyCreator.java new file mode 100644 index 0000000000..d616f46cc9 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/InfrastructureAdvisorAutoProxyCreator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2007 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.aop.framework.autoproxy; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Auto-proxy creator that considers infrastructure Advisor beans only, + * ignoring any application-defined Advisors. + * + * @author Juergen Hoeller + * @since 2.0.7 + */ +public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator { + + private ConfigurableListableBeanFactory beanFactory; + + + protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.initBeanFactory(beanFactory); + this.beanFactory = beanFactory; + } + + protected boolean isEligibleAdvisorBean(String beanName) { + return (this.beanFactory.containsBeanDefinition(beanName) && + this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java new file mode 100644 index 0000000000..3a7d6d73e0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2008 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.aop.framework.autoproxy; + +import org.springframework.core.NamedThreadLocal; + +/** + * Holder for the current proxy creation context, as exposed by auto-proxy creators + * such as {@link AbstractAdvisorAutoProxyCreator}. + * + * @author Juergen Hoeller + * @author Ramnivas Laddad + * @since 2.5 + */ +public class ProxyCreationContext { + + /** ThreadLocal holding the current proxied bean name during Advisor matching */ + private static final ThreadLocal currentProxiedBeanName = + new NamedThreadLocal("Name of currently proxied bean"); + + + /** + * Return the name of the currently proxied bean instance. + * @return the name of the bean, or null if none available + */ + public static String getCurrentProxiedBeanName() { + return (String) currentProxiedBeanName.get(); + } + + /** + * Set the name of the currently proxied bean instance. + * @param beanName the name of the bean, or null to reset it + */ + static void setCurrentProxiedBeanName(String beanName) { + currentProxiedBeanName.set(beanName); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/TargetSourceCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/TargetSourceCreator.java new file mode 100644 index 0000000000..43182c68fc --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/TargetSourceCreator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2005 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.aop.framework.autoproxy; + +import org.springframework.aop.TargetSource; + +/** + * Implementations can create special target sources, such as pooling target + * sources, for particular beans. For example, they may base their choice + * on attributes, such as a pooling attribute, on the target class. + * + *

AbstractAutoProxyCreator can support a number of TargetSourceCreators, + * which will be applied in order. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface TargetSourceCreator { + + /** + * Create a special TargetSource for the given bean, if any. + * @param beanClass the class of the bean to create a TargetSource for + * @param beanName the name of the bean + * @return a special TargetSource or null if this TargetSourceCreator isn't + * interested in the particular bean + */ + TargetSource getTargetSource(Class beanClass, String beanName); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/package.html new file mode 100644 index 0000000000..4700c8be05 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/package.html @@ -0,0 +1,15 @@ + + + +Bean post-processors for use in ApplicationContexts to simplify AOP usage +by automatically creating AOP proxies without the need to use a ProxyFactoryBean. + +

The various post-processors in this package need only be added to an ApplicationContext +(typically in an XML bean definition document) to automatically proxy selected beans. + +

NB: Automatic auto-proxying is not supported for BeanFactory implementations, +as post-processors beans are only automatically detected in application contexts. +Post-processors can be explicitly registered on a ConfigurableBeanFactory instead. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java new file mode 100644 index 0000000000..9a6b81d15b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/AbstractBeanFactoryBasedTargetSourceCreator.java @@ -0,0 +1,200 @@ +/* + * Copyright 2002-2008 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.aop.framework.autoproxy.target; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.aop.framework.autoproxy.TargetSourceCreator; +import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.GenericBeanDefinition; + +/** + * Convenient superclass for + * {@link org.springframework.aop.framework.autoproxy.TargetSourceCreator} + * implementations that require creating multiple instances of a prototype bean. + * + *

Uses an internal BeanFactory to manage the target instances, + * copying the original bean definition to this internal factory. + * This is necessary because the original BeanFactory will just + * contain the proxy instance created through auto-proxying. + * + *

Requires running in an + * {@link org.springframework.beans.factory.support.AbstractBeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource + * @see org.springframework.beans.factory.support.AbstractBeanFactory + */ +public abstract class AbstractBeanFactoryBasedTargetSourceCreator + implements TargetSourceCreator, BeanFactoryAware, DisposableBean { + + protected final Log logger = LogFactory.getLog(getClass()); + + private ConfigurableBeanFactory beanFactory; + + /** Internally used DefaultListableBeanFactory instances, keyed by bean name */ + private final Map internalBeanFactories = new HashMap(); + + + public final void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ConfigurableBeanFactory)) { + throw new IllegalStateException("Cannot do auto-TargetSource creation with a BeanFactory " + + "that doesn't implement ConfigurableBeanFactory: " + beanFactory.getClass()); + } + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + } + + /** + * Return the BeanFactory that this TargetSourceCreators runs in. + */ + protected final BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + //--------------------------------------------------------------------- + // Implementation of the TargetSourceCreator interface + //--------------------------------------------------------------------- + + public final TargetSource getTargetSource(Class beanClass, String beanName) { + AbstractBeanFactoryBasedTargetSource targetSource = + createBeanFactoryBasedTargetSource(beanClass, beanName); + if (targetSource == null) { + return null; + } + + if (logger.isDebugEnabled()) { + logger.debug("Configuring AbstractBeanFactoryBasedTargetSource: " + targetSource); + } + + DefaultListableBeanFactory internalBeanFactory = getInternalBeanFactoryForBean(beanName); + + // We need to override just this bean definition, as it may reference other beans + // and we're happy to take the parent's definition for those. + // Always use prototype scope if demanded. + BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName); + GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd); + if (isPrototypeBased()) { + bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE); + } + internalBeanFactory.registerBeanDefinition(beanName, bdCopy); + + // Complete configuring the PrototypeTargetSource. + targetSource.setTargetBeanName(beanName); + targetSource.setBeanFactory(internalBeanFactory); + + return targetSource; + } + + /** + * Return the internal BeanFactory to be used for the specified bean. + * @param beanName the name of the target bean + * @return the internal BeanFactory to be used + */ + protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) { + DefaultListableBeanFactory internalBeanFactory = null; + synchronized (this.internalBeanFactories) { + internalBeanFactory = (DefaultListableBeanFactory) this.internalBeanFactories.get(beanName); + if (internalBeanFactory == null) { + internalBeanFactory = buildInternalBeanFactory(this.beanFactory); + this.internalBeanFactories.put(beanName, internalBeanFactory); + } + } + return internalBeanFactory; + } + + /** + * Build an internal BeanFactory for resolving target beans. + * @param containingFactory the containing BeanFactory that originally defines the beans + * @return an independent internal BeanFactory to hold copies of some target beans + */ + protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFactory containingFactory) { + // Set parent so that references (up container hierarchies) are correctly resolved. + DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory(containingFactory); + + // Required so that all BeanPostProcessors, Scopes, etc become available. + internalBeanFactory.copyConfigurationFrom(containingFactory); + + // Filter out BeanPostProcessors that are part of the AOP infrastructure, + // since those are only meant to apply to beans defined in the original factory. + for (Iterator it = internalBeanFactory.getBeanPostProcessors().iterator(); it.hasNext();) { + BeanPostProcessor postProcessor = (BeanPostProcessor) it.next(); + if (postProcessor instanceof AopInfrastructureBean) { + it.remove(); + } + } + + return internalBeanFactory; + } + + /** + * Destroys the internal BeanFactory on shutdown of the TargetSourceCreator. + * @see #getInternalBeanFactoryForBean + */ + public void destroy() { + synchronized (this.internalBeanFactories) { + for (Iterator it = this.internalBeanFactories.values().iterator(); it.hasNext();) { + ((DefaultListableBeanFactory) it.next()).destroySingletons(); + } + } + } + + + //--------------------------------------------------------------------- + // Template methods to be implemented by subclasses + //--------------------------------------------------------------------- + + /** + * Return whether this TargetSourceCreator is prototype-based. + * The scope of the target bean definition will be set accordingly. + *

Default is "true". + * @see org.springframework.beans.factory.config.BeanDefinition#isSingleton() + */ + protected boolean isPrototypeBased() { + return true; + } + + /** + * Subclasses must implement this method to return a new AbstractPrototypeBasedTargetSource + * if they wish to create a custom TargetSource for this bean, or null if they are + * not interested it in, in which case no special target source will be created. + * Subclasses should not call setTargetBeanName or setBeanFactory + * on the AbstractPrototypeBasedTargetSource: This class' implementation of + * getTargetSource() will do that. + * @param beanClass the class of the bean to create a TargetSource for + * @param beanName the name of the bean + * @return the AbstractPrototypeBasedTargetSource, or null if we don't match this + */ + protected abstract AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( + Class beanClass, String beanName); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java new file mode 100644 index 0000000000..df89b52e28 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/LazyInitTargetSourceCreator.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2006 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.aop.framework.autoproxy.target; + +import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource; +import org.springframework.aop.target.LazyInitTargetSource; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * TargetSourceCreator that enforces a LazyInitTargetSource for each bean + * that is defined as "lazy-init". This will lead to a proxy created for + * each of those beans, allowing to fetch a reference to such a bean + * without actually initialized the target bean instance. + * + *

To be registered as custom TargetSourceCreator for an auto-proxy creator, + * in combination with custom interceptors for specific beans or for the + * creation of lazy-init proxies only. For example, as autodetected + * infrastructure bean in an XML application context definition: + * + *

+ * <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
+ *   <property name="customTargetSourceCreators">
+ *     <list>
+ *       <bean class="org.springframework.aop.framework.autoproxy.target.LazyInitTargetSourceCreator"/>
+ *     </list>
+ *   </property>
+ * </bean>
+ *
+ * <bean id="myLazyInitBean" class="mypackage.MyBeanClass" lazy-init="true">
+ *   ...
+ * </bean>
+ * + * @author Juergen Hoeller + * @since 1.2 + * @see org.springframework.beans.factory.config.BeanDefinition#isLazyInit + * @see org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#setCustomTargetSourceCreators + * @see org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator + */ +public class LazyInitTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator { + + protected boolean isPrototypeBased() { + return false; + } + + protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( + Class beanClass, String beanName) { + + if (getBeanFactory() instanceof ConfigurableListableBeanFactory) { + BeanDefinition definition = + ((ConfigurableListableBeanFactory) getBeanFactory()).getBeanDefinition(beanName); + if (definition.isLazyInit()) { + return new LazyInitTargetSource(); + } + } + return null; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java new file mode 100644 index 0000000000..00d5013836 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/QuickTargetSourceCreator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2006 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.aop.framework.autoproxy.target; + +import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource; +import org.springframework.aop.target.CommonsPoolTargetSource; +import org.springframework.aop.target.PrototypeTargetSource; +import org.springframework.aop.target.ThreadLocalTargetSource; + +/** + * Convenient TargetSourceCreator using bean name prefixes to create one of three + * well-known TargetSource types: + *
  • : CommonsPoolTargetSource + *
  • % ThreadLocalTargetSource + *
  • ! PrototypeTargetSource + * + * @author Rod Johnson + * @see org.springframework.aop.target.CommonsPoolTargetSource + * @see org.springframework.aop.target.ThreadLocalTargetSource + * @see org.springframework.aop.target.PrototypeTargetSource + */ +public class QuickTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator { + + public static final String PREFIX_COMMONS_POOL = ":"; + public static final String PREFIX_THREAD_LOCAL = "%"; + public static final String PREFIX_PROTOTYPE = "!"; + + protected final AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource( + Class beanClass, String beanName) { + + if (beanName.startsWith(PREFIX_COMMONS_POOL)) { + CommonsPoolTargetSource cpts = new CommonsPoolTargetSource(); + cpts.setMaxSize(25); + return cpts; + } + else if (beanName.startsWith(PREFIX_THREAD_LOCAL)) { + return new ThreadLocalTargetSource(); + } + else if (beanName.startsWith(PREFIX_PROTOTYPE)) { + return new PrototypeTargetSource(); + } + else { + // No match. Don't create a custom target source. + return null; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/package.html new file mode 100644 index 0000000000..264fe4aa56 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/target/package.html @@ -0,0 +1,7 @@ + + + +Generic support classes for target source creation. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/framework/package.html new file mode 100644 index 0000000000..bb10ed22d5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/package.html @@ -0,0 +1,18 @@ + + + +Package containing Spring's basic AOP infrastructure, compliant with the +AOP Alliance interfaces. + +

    Spring AOP supports proxying interfaces or classes, introductions, and offers +static and dynamic pointcuts. + +

    Any Spring AOP proxy can be cast to the ProxyConfig AOP configuration interface +in this package to add or remove interceptors. + +

    The ProxyFactoryBean is a convenient way to create AOP proxies in a BeanFactory +or ApplicationContext. However, proxies can be created programmatically using the +ProxyFactory class. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java new file mode 100644 index 0000000000..3ce05a0681 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AbstractMonitoringInterceptor.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2007 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.aop.interceptor; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; + +/** + * Base class for monitoring interceptors, such as performance monitors. + * Provides prefix and suffix properties + * that help to classify/group performance monitoring results. + * + *

    Subclasses should call the createInvocationTraceName(MethodInvocation) + * method to create a name for the given trace that includes information about the + * method invocation under trace along with the prefix and suffix added as appropriate. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 1.2.7 + * @see #setPrefix + * @see #setSuffix + * @see #createInvocationTraceName + */ +public abstract class AbstractMonitoringInterceptor extends AbstractTraceInterceptor { + + private String prefix = ""; + + private String suffix = ""; + + private boolean logTargetClassInvocation = false; + + + /** + * Set the text that will get appended to the trace data. + *

    Default is none. + */ + public void setPrefix(String prefix) { + this.prefix = (prefix != null ? prefix : ""); + } + + /** + * Return the text that will get appended to the trace data. + */ + protected String getPrefix() { + return this.prefix; + } + + /** + * Set the text that will get prepended to the trace data. + *

    Default is none. + */ + public void setSuffix(String suffix) { + this.suffix = (suffix != null ? suffix : ""); + } + + /** + * Return the text that will get prepended to the trace data. + */ + protected String getSuffix() { + return this.suffix; + } + + /** + * Set whether to log the invocation on the target class, if applicable + * (i.e. if the method is actually delegated to the target class). + *

    Default is "false", logging the invocation based on the proxy + * interface/class name. + */ + public void setLogTargetClassInvocation(boolean logTargetClassInvocation) { + this.logTargetClassInvocation = logTargetClassInvocation; + } + + + /** + * Create a String name for the given MethodInvocation + * that can be used for trace/logging purposes. This name is made up of the + * configured prefix, followed by the fully-qualified name of the method being + * invoked, followed by the configured suffix. + * @see #setPrefix + * @see #setSuffix + */ + protected String createInvocationTraceName(MethodInvocation invocation) { + StringBuffer sb = new StringBuffer(getPrefix()); + Method method = invocation.getMethod(); + Class clazz = method.getDeclaringClass(); + if (this.logTargetClassInvocation && clazz.isInstance(invocation.getThis())) { + clazz = invocation.getThis().getClass(); + } + sb.append(clazz.getName()); + sb.append('.').append(method.getName()); + sb.append(getSuffix()); + return sb.toString(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java new file mode 100644 index 0000000000..2c367c20c0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/AbstractTraceInterceptor.java @@ -0,0 +1,191 @@ +/* + * Copyright 2002-2008 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.aop.interceptor; + +import java.io.Serializable; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.support.AopUtils; + +/** + * Base MethodInterceptor implementation for tracing. + * + *

    By default, log messages are written to the log for the interceptor class, + * not the class which is being intercepted. Setting the useDynamicLogger + * bean property to true causes all log messages to be written to + * the Log for the target class being intercepted. + * + *

    Subclasses must implement the invokeUnderTrace method, which + * is invoked by this class ONLY when a particular invocation SHOULD be traced. + * Subclasses should write to the Log instance provided. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 1.2 + * @see #setUseDynamicLogger + * @see #invokeUnderTrace(org.aopalliance.intercept.MethodInvocation, org.apache.commons.logging.Log) + */ +public abstract class AbstractTraceInterceptor implements MethodInterceptor, Serializable { + + /** + * The default Log instance used to write trace messages. + * This instance is mapped to the implementing Class. + */ + protected transient Log defaultLogger = LogFactory.getLog(getClass()); + + /** + * Indicates whether or not proxy class names should be hidden when using dynamic loggers. + * @see #setUseDynamicLogger + */ + private boolean hideProxyClassNames = false; + + + /** + * Set whether to use a dynamic logger or a static logger. + * Default is a static logger for this trace interceptor. + *

    Used to determine which Log instance should be used to write + * log messages for a particular method invocation: a dynamic one for the + * Class getting called, or a static one for the Class + * of the trace interceptor. + *

    NOTE: Specify either this property or "loggerName", not both. + * @see #getLoggerForInvocation(org.aopalliance.intercept.MethodInvocation) + */ + public void setUseDynamicLogger(boolean useDynamicLogger) { + // Release default logger if it is not being used. + this.defaultLogger = (useDynamicLogger ? null : LogFactory.getLog(getClass())); + } + + /** + * Set the name of the logger to use. The name will be passed to the + * underlying logger implementation through Commons Logging, getting + * interpreted as log category according to the logger's configuration. + *

    This can be specified to not log into the category of a class + * (whether this interceptor's class or the class getting called) + * but rather into a specific named category. + *

    NOTE: Specify either this property or "useDynamicLogger", not both. + * @see org.apache.commons.logging.LogFactory#getLog(String) + * @see org.apache.log4j.Logger#getLogger(String) + * @see java.util.logging.Logger#getLogger(String) + */ + public void setLoggerName(String loggerName) { + this.defaultLogger = LogFactory.getLog(loggerName); + } + + /** + * Set to "true" to have {@link #setUseDynamicLogger dynamic loggers} hide + * proxy class names wherever possible. Default is "false". + */ + public void setHideProxyClassNames(boolean hideProxyClassNames) { + this.hideProxyClassNames = hideProxyClassNames; + } + + + /** + * Determines whether or not logging is enabled for the particular MethodInvocation. + * If not, the method invocation proceeds as normal, otherwise the method invocation is passed + * to the invokeUnderTrace method for handling. + * @see #invokeUnderTrace(org.aopalliance.intercept.MethodInvocation, org.apache.commons.logging.Log) + */ + public Object invoke(MethodInvocation invocation) throws Throwable { + Log logger = getLoggerForInvocation(invocation); + if (isInterceptorEnabled(invocation, logger)) { + return invokeUnderTrace(invocation, logger); + } + else { + return invocation.proceed(); + } + } + + /** + * Return the appropriate Log instance to use for the given + * MethodInvocation. If the useDynamicLogger flag + * is set, the Log instance will be for the target class of the + * MethodInvocation, otherwise the Log will be the + * default static logger. + * @param invocation the MethodInvocation being traced + * @return the Log instance to use + * @see #setUseDynamicLogger + */ + protected Log getLoggerForInvocation(MethodInvocation invocation) { + if (this.defaultLogger != null) { + return this.defaultLogger; + } + else { + Object target = invocation.getThis(); + return LogFactory.getLog(getClassForLogging(target)); + } + } + + /** + * Determine the class to use for logging purposes. + * @param target the target object to introspect + * @return the target class for the given object + * @see #setHideProxyClassNames + */ + protected Class getClassForLogging(Object target) { + return (this.hideProxyClassNames ? AopUtils.getTargetClass(target) : target.getClass()); + } + + /** + * Determine whether the interceptor should kick in, that is, + * whether the invokeUnderTrace method should be called. + *

    Default behavior is to check whether the given Log + * instance is enabled. Subclasses can override this to apply the + * interceptor in other cases as well. + * @param invocation the MethodInvocation being traced + * @param logger the Log instance to check + * @see #invokeUnderTrace + * @see #isLogEnabled + */ + protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger) { + return isLogEnabled(logger); + } + + /** + * Determine whether the given {@link Log} instance is enabled. + *

    Default is true when the "trace" level is enabled. + * Subclasses can override this to change the level under which 'tracing' occurs. + * @param logger the Log instance to check + */ + protected boolean isLogEnabled(Log logger) { + return logger.isTraceEnabled(); + } + + + /** + * Subclasses must override this method to perform any tracing around the + * supplied MethodInvocation. Subclasses are responsible for + * ensuring that the MethodInvocation actually executes by + * calling MethodInvocation.proceed(). + *

    By default, the passed-in Log instance will have log level + * "trace" enabled. Subclasses do not have to check for this again, unless + * they overwrite the isInterceptorEnabled method to modify + * the default behavior. + * @param logger the Log to write trace messages to + * @return the result of the call to MethodInvocation.proceed() + * @throws Throwable if the call to MethodInvocation.proceed() + * encountered any errors + * @see #isInterceptorEnabled + * @see #isLogEnabled + */ + protected abstract Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ClassLoaderAnalyzerInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ClassLoaderAnalyzerInterceptor.java new file mode 100644 index 0000000000..2391634ea7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ClassLoaderAnalyzerInterceptor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2008 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.aop.interceptor; + +import java.io.Serializable; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.ClassLoaderUtils; + +/** + * Trivial classloader analyzer interceptor. + * + * @author Rod Johnson + * @author Dmitriy Kopylenko + * @deprecated as of Spring 2.5, to be removed in Spring 3.0 + * @see org.springframework.util.ClassLoaderUtils + */ +public class ClassLoaderAnalyzerInterceptor implements MethodInterceptor, Serializable { + + /** Static to avoid serializing the logger */ + protected static final Log logger = LogFactory.getLog(ClassLoaderAnalyzerInterceptor.class); + + public Object invoke(MethodInvocation invocation) throws Throwable { + if (logger.isInfoEnabled()) { + logger.info( + ClassLoaderUtils.showClassLoaderHierarchy( + invocation.getThis(), + invocation.getThis().getClass().getName(), + "\n", + "-")); + } + return invocation.proceed(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptor.java new file mode 100644 index 0000000000..bf14ab6430 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ConcurrencyThrottleInterceptor.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2005 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.aop.interceptor; + +import java.io.Serializable; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.util.ConcurrencyThrottleSupport; + +/** + * Interceptor that throttles concurrent access, blocking invocations + * if a specified concurrency limit is reached. + * + *

    Can be applied to methods of local services that involve heavy use + * of system resources, in a scenario where it is more efficient to + * throttle concurrency for a specific service rather than restricting + * the entire thread pool (e.g. the web container's thread pool). + * + *

    The default concurrency limit of this interceptor is 1. + * Specify the "concurrencyLimit" bean property to change this value. + * + * @author Juergen Hoeller + * @since 11.02.2004 + * @see #setConcurrencyLimit + */ +public class ConcurrencyThrottleInterceptor extends ConcurrencyThrottleSupport + implements MethodInterceptor, Serializable { + + public ConcurrencyThrottleInterceptor() { + setConcurrencyLimit(1); + } + + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + beforeAccess(); + try { + return methodInvocation.proceed(); + } + finally { + afterAccess(); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java new file mode 100644 index 0000000000..804349ec57 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/CustomizableTraceInterceptor.java @@ -0,0 +1,441 @@ +/* + * Copyright 2002-2008 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.aop.interceptor; + +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; + +import org.springframework.core.Constants; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StopWatch; +import org.springframework.util.StringUtils; + +/** + * MethodInterceptor implementation that allows for highly customizable + * method-level tracing, using placeholders. + * + *

    Trace messages are written on method entry, and if the method invocation succeeds + * on method exit. If an invocation results in an exception, then an exception message + * is written. The contents of these trace messages is fully customizable and special + * placeholders are available to allow you to include runtime information in your log + * messages. The placeholders available are: + * + *

      + *
    • $[methodName] - replaced with the name of the method being invoked
    • + *
    • $[targetClassName] - replaced with the name of the class that is + * the target of the invocation
    • + *
    • $[targetClassShortName] - replaced with the short name of the class + * that is the target of the invocation
    • + *
    • $[returnValue] - replaced with the value returned by the invocation
    • + *
    • $[argumentTypes] - replaced with a comma-separated list of the + * short class names of the method arguments
    • + *
    • $[arguments] - replaced with a comma-separated list of the + * String representation of the method arguments
    • + *
    • $[exception] - replaced with the String representation + * of any Throwable raised during the invocation
    • + *
    • $[invocationTime] - replaced with the time, in milliseconds, + * taken by the method invocation
    • + *
    + * + *

    There are restrictions on which placeholders can be used in which messages: + * see the individual message properties for details on the valid placeholders. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 1.2 + * @see #setEnterMessage + * @see #setExitMessage + * @see #setExceptionMessage + * @see SimpleTraceInterceptor + */ +public class CustomizableTraceInterceptor extends AbstractTraceInterceptor { + + /** + * The $[methodName] placeholder. + * Replaced with the name of the method being invoked. + */ + public static final String PLACEHOLDER_METHOD_NAME = "$[methodName]"; + + /** + * The $[targetClassName] placeholder. + * Replaced with the fully-qualifed name of the Class + * of the method invocation target. + */ + public static final String PLACEHOLDER_TARGET_CLASS_NAME = "$[targetClassName]"; + + /** + * The $[targetClassShortName] placeholder. + * Replaced with the short name of the Class of the + * method invocation target. + */ + public static final String PLACEHOLDER_TARGET_CLASS_SHORT_NAME = "$[targetClassShortName]"; + + /** + * The $[returnValue] placeholder. + * Replaced with the String representation of the value + * returned by the method invocation. + */ + public static final String PLACEHOLDER_RETURN_VALUE = "$[returnValue]"; + + /** + * The $[argumentTypes] placeholder. + * Replaced with a comma-separated list of the argument types for the + * method invocation. Argument types are written as short class names. + */ + public static final String PLACEHOLDER_ARGUMENT_TYPES = "$[argumentTypes]"; + + /** + * The $[arguments] placeholder. + * Replaced with a comma separated list of the argument values for the + * method invocation. Relies on the toString() method of + * each argument type. + */ + public static final String PLACEHOLDER_ARGUMENTS = "$[arguments]"; + + /** + * The $[exception] placeholder. + * Replaced with the String representation of any + * Throwable raised during method invocation. + */ + public static final String PLACEHOLDER_EXCEPTION = "$[exception]"; + + /** + * The $[invocationTime] placeholder. + * Replaced with the time taken by the invocation (in milliseconds). + */ + public static final String PLACEHOLDER_INVOCATION_TIME = "$[invocationTime]"; + + /** + * The default message used for writing method entry messages. + */ + private static final String DEFAULT_ENTER_MESSAGE = + "Entering method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; + + /** + * The default message used for writing method exit messages. + */ + private static final String DEFAULT_EXIT_MESSAGE = + "Exiting method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; + + /** + * The default message used for writing exception messages. + */ + private static final String DEFAULT_EXCEPTION_MESSAGE = + "Exception thrown in method '" + PLACEHOLDER_METHOD_NAME + "' of class [" + PLACEHOLDER_TARGET_CLASS_NAME + "]"; + + /** + * The Pattern used to match placeholders. + */ + private static final Pattern PATTERN = Pattern.compile("\\$\\[\\p{Alpha}+\\]"); + + /** + * The Set of allowed placeholders. + */ + private static final Set ALLOWED_PLACEHOLDERS = + new Constants(CustomizableTraceInterceptor.class).getValues("PLACEHOLDER_"); + + + /** + * The message for method entry. + */ + private String enterMessage = DEFAULT_ENTER_MESSAGE; + + /** + * The message for method exit. + */ + private String exitMessage = DEFAULT_EXIT_MESSAGE; + + /** + * The message for exceptions during method execution. + */ + private String exceptionMessage = DEFAULT_EXCEPTION_MESSAGE; + + + /** + * Set the template used for method entry log messages. + * This template can contain any of the following placeholders: + *

      + *
    • $[targetClassName]
    • + *
    • $[targetClassShortName]
    • + *
    • $[argumentTypes]
    • + *
    • $[arguments]
    • + *
    + */ + public void setEnterMessage(String enterMessage) throws IllegalArgumentException { + Assert.hasText(enterMessage, "'enterMessage' must not be empty"); + checkForInvalidPlaceholders(enterMessage); + Assert.doesNotContain(enterMessage, PLACEHOLDER_RETURN_VALUE, + "enterMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]"); + Assert.doesNotContain(enterMessage, PLACEHOLDER_EXCEPTION, + "enterMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]"); + Assert.doesNotContain(enterMessage, PLACEHOLDER_INVOCATION_TIME, + "enterMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]"); + this.enterMessage = enterMessage; + } + + /** + * Set the template used for method exit log messages. + * This template can contain any of the following placeholders: + *
      + *
    • $[targetClassName]
    • + *
    • $[targetClassShortName]
    • + *
    • $[argumentTypes]
    • + *
    • $[arguments]
    • + *
    • $[returnValue]
    • + *
    • $[invocationTime]
    • + *
    + */ + public void setExitMessage(String exitMessage) { + Assert.hasText(exitMessage, "'exitMessage' must not be empty"); + checkForInvalidPlaceholders(exitMessage); + Assert.doesNotContain(exitMessage, PLACEHOLDER_EXCEPTION, + "exitMessage cannot contain placeholder [" + PLACEHOLDER_EXCEPTION + "]"); + this.exitMessage = exitMessage; + } + + /** + * Set the template used for method exception log messages. + * This template can contain any of the following placeholders: + *
      + *
    • $[targetClassName]
    • + *
    • $[targetClassShortName]
    • + *
    • $[argumentTypes]
    • + *
    • $[arguments]
    • + *
    • $[exception]
    • + *
    + */ + public void setExceptionMessage(String exceptionMessage) { + Assert.hasText(exceptionMessage, "'exceptionMessage' must not be empty"); + checkForInvalidPlaceholders(exceptionMessage); + Assert.doesNotContain(exceptionMessage, PLACEHOLDER_RETURN_VALUE, + "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_RETURN_VALUE + "]"); + Assert.doesNotContain(exceptionMessage, PLACEHOLDER_INVOCATION_TIME, + "exceptionMessage cannot contain placeholder [" + PLACEHOLDER_INVOCATION_TIME + "]"); + this.exceptionMessage = exceptionMessage; + } + + + /** + * Writes a log message before the invocation based on the value of enterMessage. + * If the invocation succeeds, then a log message is written on exit based on the value + * exitMessage. If an exception occurs during invocation, then a message is + * written based on the value of exceptionMessage. + * @see #setEnterMessage + * @see #setExitMessage + * @see #setExceptionMessage + */ + protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { + String name = invocation.getMethod().getDeclaringClass().getName() + "." + invocation.getMethod().getName(); + StopWatch stopWatch = new StopWatch(name); + Object returnValue = null; + boolean exitThroughException = false; + try { + stopWatch.start(name); + writeToLog(logger, + replacePlaceholders(this.enterMessage, invocation, null, null, -1)); + returnValue = invocation.proceed(); + return returnValue; + } + catch (Throwable ex) { + if(stopWatch.isRunning()) { + stopWatch.stop(); + } + exitThroughException = true; + writeToLog(logger, + replacePlaceholders(this.exceptionMessage, invocation, null, ex, stopWatch.getTotalTimeMillis()), ex); + throw ex; + } + finally { + if (!exitThroughException) { + if(stopWatch.isRunning()) { + stopWatch.stop(); + } + writeToLog(logger, + replacePlaceholders(this.exitMessage, invocation, returnValue, null, stopWatch.getTotalTimeMillis())); + } + } + } + + /** + * Writes the supplied message to the supplied Log instance. + * @see #writeToLog(org.apache.commons.logging.Log, String, Throwable) + */ + protected void writeToLog(Log logger, String message) { + writeToLog(logger, message, null); + } + + /** + * Writes the supplied message and {@link Throwable} to the + * supplied Log instance. By default messages are written + * at TRACE level. Sub-classes can override this method + * to control which level the message is written at. + */ + protected void writeToLog(Log logger, String message, Throwable ex) { + if (ex != null) { + logger.trace(message, ex); + } + else { + logger.trace(message); + } + } + + /** + * Replace the placeholders in the given message with the supplied values, + * or values derived from those supplied. + * @param message the message template containing the placeholders to be replaced + * @param methodInvocation the MethodInvocation being logged. + * Used to derive values for all placeholders except $[exception] + * and $[returnValue]. + * @param returnValue any value returned by the invocation. + * Used to replace the $[returnValue] placeholder. May be null. + * @param throwable any Throwable raised during the invocation. + * The value of Throwable.toString() is replaced for the + * $[exception] placeholder. May be null. + * @param invocationTime the value to write in place of the + * $[invocationTime] placeholder + * @return the formatted output to write to the log + */ + protected String replacePlaceholders(String message, MethodInvocation methodInvocation, + Object returnValue, Throwable throwable, long invocationTime) { + + Matcher matcher = PATTERN.matcher(message); + + StringBuffer output = new StringBuffer(); + while (matcher.find()) { + String match = matcher.group(); + if (PLACEHOLDER_METHOD_NAME.equals(match)) { + matcher.appendReplacement(output, escape(methodInvocation.getMethod().getName())); + } + else if (PLACEHOLDER_TARGET_CLASS_NAME.equals(match)) { + String className = getClassForLogging(methodInvocation.getThis()).getName(); + matcher.appendReplacement(output, escape(className)); + } + else if (PLACEHOLDER_TARGET_CLASS_SHORT_NAME.equals(match)) { + String shortName = ClassUtils.getShortName(getClassForLogging(methodInvocation.getThis())); + matcher.appendReplacement(output, escape(shortName)); + } + else if (PLACEHOLDER_ARGUMENTS.equals(match)) { + matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(methodInvocation.getArguments()))); + } + else if (PLACEHOLDER_ARGUMENT_TYPES.equals(match)) { + appendArgumentTypes(methodInvocation, matcher, output); + } + else if (PLACEHOLDER_RETURN_VALUE.equals(match)) { + appendReturnValue(methodInvocation, matcher, output, returnValue); + } + else if (throwable != null && PLACEHOLDER_EXCEPTION.equals(match)) { + matcher.appendReplacement(output, escape(throwable.toString())); + } + else if (PLACEHOLDER_INVOCATION_TIME.equals(match)) { + matcher.appendReplacement(output, Long.toString(invocationTime)); + } + else { + // Should not happen since placeholders are checked earlier. + throw new IllegalArgumentException("Unknown placeholder [" + match + "]"); + } + } + matcher.appendTail(output); + + return output.toString(); + } + + /** + * Adds the String representation of the method return value + * to the supplied StringBuffer. Correctly handles + * null and void results. + * @param methodInvocation the MethodInvocation that returned the value + * @param matcher the Matcher containing the matched placeholder + * @param output the StringBuffer to write output to + * @param returnValue the value returned by the method invocation. + */ + private void appendReturnValue( + MethodInvocation methodInvocation, Matcher matcher, StringBuffer output, Object returnValue) { + + if (methodInvocation.getMethod().getReturnType() == void.class) { + matcher.appendReplacement(output, "void"); + } + else if (returnValue == null) { + matcher.appendReplacement(output, "null"); + } + else { + matcher.appendReplacement(output, escape(returnValue.toString())); + } + } + + /** + * Adds a comma-separated list of the short Class names of the + * method argument types to the output. For example, if a method has signature + * put(java.lang.String, java.lang.Object) then the value returned + * will be String, Object. + * @param methodInvocation the MethodInvocation being logged. + * Arguments will be retrieved from the corresponding Method. + * @param matcher the Matcher containing the state of the output + * @param output the StringBuffer containing the output + */ + private void appendArgumentTypes(MethodInvocation methodInvocation, Matcher matcher, StringBuffer output) { + Class[] argumentTypes = methodInvocation.getMethod().getParameterTypes(); + String[] argumentTypeShortNames = new String[argumentTypes.length]; + for (int i = 0; i < argumentTypeShortNames.length; i++) { + argumentTypeShortNames[i] = ClassUtils.getShortName(argumentTypes[i]); + } + matcher.appendReplacement(output, escape(StringUtils.arrayToCommaDelimitedString(argumentTypeShortNames))); + } + + /** + * Checks to see if the supplied String has any placeholders + * that are not specified as constants on this class and throws an + * IllegalArgumentException if so. + */ + private void checkForInvalidPlaceholders(String message) throws IllegalArgumentException { + Matcher matcher = PATTERN.matcher(message); + while (matcher.find()) { + String match = matcher.group(); + if (!ALLOWED_PLACEHOLDERS.contains(match)) { + throw new IllegalArgumentException("Placeholder [" + match + "] is not valid"); + } + } + } + + /** + * Replaces $ in inner class names with \$. + *

    This code is equivalent to JDK 1.5's quoteReplacement + * method in the Matcher class itself. We're keeping our own version + * here for JDK 1.4 compliance reasons only. + */ + private String escape(String input) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c == '\\') { + sb.append("\\\\"); + } + else if (c == '$') { + sb.append("\\$"); + } + else { + sb.append(c); + } + } + return sb.toString(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/DebugInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/DebugInterceptor.java new file mode 100644 index 0000000000..598bf89f16 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/DebugInterceptor.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2006 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.aop.interceptor; + +import org.aopalliance.intercept.MethodInvocation; + +/** + * AOP Alliance MethodInterceptor that can be introduced in a chain + * to display verbose information about intercepted invocations to the logger. + * + *

    Logs full invocation details on method entry and method exit, + * including invocation arguments and invocation count. This is only + * intended for debugging purposes; use SimpleTraceInterceptor + * or CustomizableTraceInterceptor for pure tracing purposes. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see SimpleTraceInterceptor + * @see CustomizableTraceInterceptor + */ +public class DebugInterceptor extends SimpleTraceInterceptor { + + private volatile long count; + + + /** + * Create a new DebugInterceptor with a static logger. + */ + public DebugInterceptor() { + } + + /** + * Create a new DebugInterceptor with dynamic or static logger, + * according to the given flag. + * @param useDynamicLogger whether to use a dynamic logger or a static logger + * @see #setUseDynamicLogger + */ + public DebugInterceptor(boolean useDynamicLogger) { + setUseDynamicLogger(useDynamicLogger); + } + + + public Object invoke(MethodInvocation invocation) throws Throwable { + synchronized (this) { + this.count++; + } + return super.invoke(invocation); + } + + protected String getInvocationDescription(MethodInvocation invocation) { + return invocation + "; count=" + this.count; + } + + + /** + * Return the number of times this interceptor has been invoked. + */ + public long getCount() { + return this.count; + } + + /** + * Reset the invocation count to zero. + */ + public synchronized void resetCount() { + this.count = 0; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ExposeBeanNameAdvisors.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ExposeBeanNameAdvisors.java new file mode 100644 index 0000000000..80674e70ca --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ExposeBeanNameAdvisors.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2007 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.aop.interceptor; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.Advisor; +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.beans.factory.NamedBean; + +/** + * Convenient methods for creating advisors that may be used when autoproxying beans + * created with the Spring IoC container, binding the bean name to the current + * invocation. May support a bean() pointcut designator with AspectJ. + * + *

    Typically used in Spring auto-proxying, where the bean name is known + * at proxy creation time. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.NamedBean + */ +public abstract class ExposeBeanNameAdvisors { + + /** + * Binding for the bean name of the bean which is currently being invoked + * in the ReflectiveMethodInvocation userAttributes Map. + */ + private static final String BEAN_NAME_ATTRIBUTE = ExposeBeanNameAdvisors.class.getName() + ".beanName"; + + + /** + * Find the bean name for the current invocation. Assumes that an ExposeBeanNameAdvisor + * has been included in the interceptor chain, and that the invocation is exposed + * with ExposeInvocationInterceptor. + * @return the bean name (never null) + * @throws IllegalStateException if the bean name has not been exposed + */ + public static String getBeanName() throws IllegalStateException { + return getBeanName(ExposeInvocationInterceptor.currentInvocation()); + } + + /** + * Find the bean name for the given invocation. Assumes that an ExposeBeanNameAdvisor + * has been included in the interceptor chain. + * @param mi MethodInvocation that should contain the bean name as an attribute + * @return the bean name (never null) + * @throws IllegalStateException if the bean name has not been exposed + */ + public static String getBeanName(MethodInvocation mi) throws IllegalStateException { + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalArgumentException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; + String beanName = (String) pmi.getUserAttribute(BEAN_NAME_ATTRIBUTE); + if (beanName == null) { + throw new IllegalStateException("Cannot get bean name; not set on MethodInvocation: " + mi); + } + return beanName; + } + + /** + * Create a new advisor that will expose the given bean name, + * with no introduction + * @param beanName bean name to expose + */ + public static Advisor createAdvisorWithoutIntroduction(String beanName) { + return new DefaultPointcutAdvisor(new ExposeBeanNameInterceptor(beanName)); + } + + /** + * Create a new advisor that will expose the given bean name, introducing + * the NamedBean interface to make the bean name accessible without forcing + * the target object to be aware of this Spring IoC concept. + * @param beanName the bean name to expose + */ + public static Advisor createAdvisorIntroducingNamedBean(String beanName) { + return new DefaultIntroductionAdvisor(new ExposeBeanNameIntroduction(beanName)); + } + + + /** + * Interceptor that exposes the specified bean name as invocation attribute. + */ + private static class ExposeBeanNameInterceptor implements MethodInterceptor { + + private final String beanName; + + public ExposeBeanNameInterceptor(String beanName) { + this.beanName = beanName; + } + + public Object invoke(MethodInvocation mi) throws Throwable { + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; + pmi.setUserAttribute(BEAN_NAME_ATTRIBUTE, beanName); + return mi.proceed(); + } + } + + + /** + * Introduction that exposes the specified bean name as invocation attribute. + */ + private static class ExposeBeanNameIntroduction extends DelegatingIntroductionInterceptor implements NamedBean { + + private final String beanName; + + public ExposeBeanNameIntroduction(String beanName) { + this.beanName = beanName; + } + + public Object invoke(MethodInvocation mi) throws Throwable { + if (!(mi instanceof ProxyMethodInvocation)) { + throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); + } + ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; + pmi.setUserAttribute(BEAN_NAME_ATTRIBUTE, beanName); + return super.invoke(mi); + } + + public String getBeanName() { + return this.beanName; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java new file mode 100644 index 0000000000..16c5bc3d21 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java @@ -0,0 +1,105 @@ +/* + * Copyright 2002-2008 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.aop.interceptor; + +import java.io.Serializable; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.Advisor; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.core.NamedThreadLocal; + +/** + * Interceptor that exposes the current {@link org.aopalliance.intercept.MethodInvocation} + * as a thread-local object. We occasionally need to do this; for example, when a pointcut + * (e.g. an AspectJ expression pointcut) needs to know the full invocation context. + * + *

    Don't use this interceptor unless this is really necessary. Target objects should + * not normally know about Spring AOP, as this creates a dependency on Spring API. + * Target objects should be plain POJOs as far as possible. + * + *

    If used, this interceptor will normally be the first in the interceptor chain. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class ExposeInvocationInterceptor implements MethodInterceptor, Serializable { + + /** Singleton instance of this class */ + public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor(); + + /** + * Singleton advisor for this class. Use in preference to INSTANCE when using + * Spring AOP, as it prevents the need to create a new Advisor to wrap the instance. + */ + public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) { + public int getOrder() { + return Integer.MIN_VALUE; + } + public String toString() { + return ExposeInvocationInterceptor.class.getName() +".ADVISOR"; + } + }; + + private static final ThreadLocal invocation = new NamedThreadLocal("Current AOP method invocation"); + + + /** + * Return the AOP Alliance MethodInvocation object associated with the current invocation. + * @return the invocation object associated with the current invocation + * @throws IllegalStateException if there is no AOP invocation in progress, + * or if the ExposeInvocationInterceptor was not added to this interceptor chain + */ + public static MethodInvocation currentInvocation() throws IllegalStateException { + MethodInvocation mi = (MethodInvocation) invocation.get(); + if (mi == null) + throw new IllegalStateException( + "No MethodInvocation found: Check that an AOP invocation is in progress, " + + "and that the ExposeInvocationInterceptor is in the interceptor chain."); + return mi; + } + + + /** + * Ensures that only the canonical instance can be created. + */ + private ExposeInvocationInterceptor() { + } + + public Object invoke(MethodInvocation mi) throws Throwable { + Object old = invocation.get(); + invocation.set(mi); + try { + return mi.proceed(); + } + finally { + invocation.set(old); + } + } + + /** + * Required to support serialization. Replaces with canonical instance + * on deserialization, protecting Singleton pattern. + *

    Alternative to overriding the equals method. + */ + private Object readResolve() { + return INSTANCE; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java new file mode 100644 index 0000000000..1c460db34d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/JamonPerformanceMonitorInterceptor.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2006 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.aop.interceptor; + +import com.jamonapi.Monitor; +import com.jamonapi.MonitorFactory; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; + +/** + * Performance monitor interceptor that uses JAMon library + * to perform the performance measurement on the intercepted method + * and output the stats. + * + *

    This code is inspired by Thierry Templier's blog. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1.3 + * @see com.jamonapi.MonitorFactory + * @see PerformanceMonitorInterceptor + */ +public class JamonPerformanceMonitorInterceptor extends AbstractMonitoringInterceptor { + + private boolean trackAllInvocations = false; + + + /** + * Create a new JamonPerformanceMonitorInterceptor with a static logger. + */ + public JamonPerformanceMonitorInterceptor() { + } + + /** + * Create a new JamonPerformanceMonitorInterceptor with a dynamic or static logger, + * according to the given flag. + * @param useDynamicLogger whether to use a dynamic logger or a static logger + * @see #setUseDynamicLogger + */ + public JamonPerformanceMonitorInterceptor(boolean useDynamicLogger) { + setUseDynamicLogger(useDynamicLogger); + } + + /** + * Create a new JamonPerformanceMonitorInterceptor with a dynamic or static logger, + * according to the given flag. + * @param useDynamicLogger whether to use a dynamic logger or a static logger + * @param trackAllInvocations whether to track all invocations that go through + * this interceptor, or just invocations with trace logging enabled + * @see #setUseDynamicLogger + */ + public JamonPerformanceMonitorInterceptor(boolean useDynamicLogger, boolean trackAllInvocations) { + setUseDynamicLogger(useDynamicLogger); + setTrackAllInvocations(trackAllInvocations); + } + + + /** + * Set whether to track all invocations that go through this interceptor, + * or just invocations with trace logging enabled. + *

    Default is "false": Only invocations with trace logging enabled will + * be monitored. Specify "true" to let JAMon track all invocations, + * gathering statistics even when trace logging is disabled. + */ + public void setTrackAllInvocations(boolean trackAllInvocations) { + this.trackAllInvocations = trackAllInvocations; + } + + + /** + * Always applies the interceptor if the "trackAllInvocations" flag has been set; + * else just kicks in if the log is enabled. + * @see #setTrackAllInvocations + * @see #isLogEnabled + */ + protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger) { + return (this.trackAllInvocations || isLogEnabled(logger)); + } + + /** + * Wraps the invocation with a JAMon Monitor and writes the current + * performance statistics to the log (if enabled). + * @see com.jamonapi.MonitorFactory#start + * @see com.jamonapi.Monitor#stop + */ + protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { + String name = createInvocationTraceName(invocation); + Monitor monitor = MonitorFactory.start(name); + try { + return invocation.proceed(); + } + finally { + monitor.stop(); + if (!this.trackAllInvocations || isLogEnabled(logger)) { + logger.trace("JAMon performance statistics for method [" + name + "]:\n" + monitor); + } + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java new file mode 100644 index 0000000000..b643267af7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/PerformanceMonitorInterceptor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2006 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.aop.interceptor; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; + +import org.springframework.util.StopWatch; + +/** + * Simple AOP Alliance MethodInterceptor for performance monitoring. + * This interceptor has no effect on the intercepted method call. + * + *

    Uses a StopWatch for the actual performance measuring. + * + * @author Rod Johnson + * @author Dmitriy Kopylenko + * @author Rob Harrop + * @see org.springframework.util.StopWatch + * @see JamonPerformanceMonitorInterceptor + */ +public class PerformanceMonitorInterceptor extends AbstractMonitoringInterceptor { + + /** + * Create a new PerformanceMonitorInterceptor with a static logger. + */ + public PerformanceMonitorInterceptor() { + } + + /** + * Create a new PerformanceMonitorInterceptor with a dynamic or static logger, + * according to the given flag. + * @param useDynamicLogger whether to use a dynamic logger or a static logger + * @see #setUseDynamicLogger + */ + public PerformanceMonitorInterceptor(boolean useDynamicLogger) { + setUseDynamicLogger(useDynamicLogger); + } + + + protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { + String name = createInvocationTraceName(invocation); + StopWatch stopWatch = new StopWatch(name); + stopWatch.start(name); + try { + return invocation.proceed(); + } + finally { + stopWatch.stop(); + logger.trace(stopWatch.shortSummary()); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java new file mode 100644 index 0000000000..49239dbc8c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/SimpleTraceInterceptor.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2007 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.aop.interceptor; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; + +/** + * Simple AOP Alliance MethodInterceptor that can be introduced + * in a chain to display verbose trace information about intercepted method + * invocations, with method entry and method exit info. + * + *

    Consider using CustomizableTraceInterceptor for more + * advanced needs. + * + * @author Dmitriy Kopylenko + * @author Juergen Hoeller + * @since 1.2 + * @see CustomizableTraceInterceptor + */ +public class SimpleTraceInterceptor extends AbstractTraceInterceptor { + + /** + * Create a new SimpleTraceInterceptor with a static logger. + */ + public SimpleTraceInterceptor() { + } + + /** + * Create a new SimpleTraceInterceptor with dynamic or static logger, + * according to the given flag. + * @param useDynamicLogger whether to use a dynamic logger or a static logger + * @see #setUseDynamicLogger + */ + public SimpleTraceInterceptor(boolean useDynamicLogger) { + setUseDynamicLogger(useDynamicLogger); + } + + + protected Object invokeUnderTrace(MethodInvocation invocation, Log logger) throws Throwable { + String invocationDescription = getInvocationDescription(invocation); + logger.trace("Entering " + invocationDescription); + try { + Object rval = invocation.proceed(); + logger.trace("Exiting " + invocationDescription); + return rval; + } + catch (Throwable ex) { + logger.trace("Exception thrown in " + invocationDescription, ex); + throw ex; + } + } + + /** + * Return a description for the given method invocation. + * @param invocation the invocation to describe + * @return the description + */ + protected String getInvocationDescription(MethodInvocation invocation) { + return "method '" + invocation.getMethod().getName() + "' of class [" + + invocation.getThis().getClass().getName() + "]"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/package.html new file mode 100644 index 0000000000..a2dd6675a2 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/interceptor/package.html @@ -0,0 +1,9 @@ + + + +Provides miscellaneous interceptor implementations. +More specific interceptors can be found in corresponding +functionality packages, like "transaction" and "orm". + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/package.html new file mode 100644 index 0000000000..e1fec4fa00 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/package.html @@ -0,0 +1,24 @@ + + + +Core Spring AOP interfaces, built on AOP Alliance AOP interoperability interfaces. + +
    Any AOP Alliance MethodInterceptor is usable in Spring. + +
    Spring AOP also offers: +

      +
    • Introduction support +
    • A Pointcut abstraction, supporting "static" pointcuts + (class and method-based) and "dynamic" pointcuts (also considering method arguments). + There are currently no AOP Alliance interfaces for pointcuts. +
    • A full range of advice types, including around, before, after returning and throws advice. +
    • Extensibility allowing arbitrary custom advice types to + be plugged in without modifying the core framework. +
    + +
    +Spring AOP can be used programmatically or (preferably) +integrated with the Spring IoC container. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java b/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java new file mode 100644 index 0000000000..ea541fb0a7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/scope/DefaultScopedObject.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2006 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.aop.scope; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.util.Assert; + +/** + * Default implementation of the {@link ScopedObject} interface. + * + *

    Simply delegates the calls to the underlying + * {@link ConfigurableBeanFactory bean factory} + * ({@link ConfigurableBeanFactory#getBean(String)}/ + * {@link ConfigurableBeanFactory#destroyScopedBean(String)}). + * + * @author Juergen Hoeller + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroyScopedBean + */ +public class DefaultScopedObject implements ScopedObject { + + private final ConfigurableBeanFactory beanFactory; + + private final String targetBeanName; + + + /** + * Creates a new instance of the {@link DefaultScopedObject} class. + * @param beanFactory the {@link ConfigurableBeanFactory} that holds the scoped target object + * @param targetBeanName the name of the target bean + * @throws IllegalArgumentException if either of the parameters is null; or + * if the targetBeanName consists wholly of whitespace + */ + public DefaultScopedObject(ConfigurableBeanFactory beanFactory, String targetBeanName) { + Assert.notNull(beanFactory, "BeanFactory must not be null"); + Assert.hasText(targetBeanName, "'targetBeanName' must not be empty"); + this.beanFactory = beanFactory; + this.targetBeanName = targetBeanName; + } + + + public Object getTargetObject() { + return this.beanFactory.getBean(this.targetBeanName); + } + + public void removeFromScope() { + this.beanFactory.destroyScopedBean(this.targetBeanName); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedObject.java b/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedObject.java new file mode 100644 index 0000000000..1a50082641 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedObject.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2007 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.aop.scope; + +import org.springframework.aop.RawTargetAccess; + +/** + * An AOP introduction interface for scoped objects. + * + *

    Objects created from the {@link ScopedProxyFactoryBean} can be cast + * to this interface, enabling access to the raw target object + * and programmatic removal of the target object. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see ScopedProxyFactoryBean + */ +public interface ScopedObject extends RawTargetAccess { + + /** + * Return the current target object behind this scoped object proxy, + * in its raw form (as stored in the target scope). + *

    The raw target object can for example be passed to persistence + * providers which would not be able to handle the scoped proxy object. + * @return the current target object behind this scoped object proxy + */ + Object getTargetObject(); + + /** + * Remove this object from its target scope, for example from + * the backing session. + *

    Note that no further calls may be made to the scoped object + * afterwards (at least within the current thread, that is, with + * the exact same target object in the target scope). + */ + void removeFromScope(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java b/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java new file mode 100644 index 0000000000..8a3d783fba --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedProxyFactoryBean.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2008 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.aop.scope; + +import java.lang.reflect.Modifier; + +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.aop.framework.ProxyConfig; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.aop.target.SimpleBeanTargetSource; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.FactoryBeanNotInitializedException; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.util.ClassUtils; + +/** + * Convenient proxy factory bean for scoped objects. + * + *

    Proxies created using this factory bean are thread-safe singletons + * and may be injected into shared objects, with transparent scoping behavior. + * + *

    Proxies returned by this class implement the {@link ScopedObject} interface. + * This presently allows for removing the corresponding object from the scope, + * seamlessly creating a new instance in the scope on next access. + * + *

    Please note that the proxies created by this factory are + * class-based proxies by default. This can be customized + * through switching the "proxyTargetClass" property to "false". + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 2.0 + * @see #setProxyTargetClass + */ +public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanFactoryAware { + + /** The TargetSource that manages scoping */ + private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource(); + + /** The name of the target bean */ + private String targetBeanName; + + /** The cached singleton proxy */ + private Object proxy; + + + /** + * Create a new ScopedProxyFactoryBean instance. + */ + public ScopedProxyFactoryBean() { + setProxyTargetClass(true); + } + + + /** + * Set the name of the bean that is to be scoped. + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + this.scopedTargetSource.setTargetBeanName(targetBeanName); + } + + public void setBeanFactory(BeanFactory beanFactory) { + if (!(beanFactory instanceof ConfigurableBeanFactory)) { + throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory); + } + ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory; + + this.scopedTargetSource.setBeanFactory(beanFactory); + + ProxyFactory pf = new ProxyFactory(); + pf.copyFrom(this); + pf.setTargetSource(this.scopedTargetSource); + + Class beanType = beanFactory.getType(this.targetBeanName); + if (beanType == null) { + throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName + + "': Target type could not be determined at the time of proxy creation."); + } + if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) { + pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader())); + } + + // Add an introduction that implements only the methods on ScopedObject. + ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName()); + pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject)); + + // Add the AopInfrastructureBean marker to indicate that the scoped proxy + // itself is not subject to auto-proxying! Only its target bean is. + pf.addInterface(AopInfrastructureBean.class); + + this.proxy = pf.getProxy(cbf.getBeanClassLoader()); + } + + + public Object getObject() { + if (this.proxy == null) { + throw new FactoryBeanNotInitializedException(); + } + return this.proxy; + } + + public Class getObjectType() { + if (this.proxy != null) { + return this.proxy.getClass(); + } + if (this.scopedTargetSource != null) { + return this.scopedTargetSource.getTargetClass(); + } + return null; + } + + public boolean isSingleton() { + return true; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java new file mode 100644 index 0000000000..e92c17fdf7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2008 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.aop.scope; + +import org.springframework.aop.framework.autoproxy.AutoProxyUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; + +/** + * Utility class for creating a scoped proxy. + * Used by ScopedProxyBeanDefinitionDecorator and ClassPathBeanDefinitionScanner. + * + * @author Mark Fisher + * @author Juergen Hoeller + * @author Rob Harrop + * @since 2.5 + */ +public abstract class ScopedProxyUtils { + + private static final String TARGET_NAME_PREFIX = "scopedTarget."; + + + /** + * Generates a scoped proxy for the supplied target bean, registering the target + * bean with an internal name and setting 'targetBeanName' on the scoped proxy. + * @param definition the original bean definition + * @param registry the bean definition registry + * @param proxyTargetClass whether to create a target class proxy + * @return the scoped proxy definition + */ + public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, + BeanDefinitionRegistry registry, boolean proxyTargetClass) { + + String originalBeanName = definition.getBeanName(); + BeanDefinition targetDefinition = definition.getBeanDefinition(); + + // Create a scoped proxy definition for the original bean name, + // "hiding" the target bean in an internal target definition. + RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); + scopedProxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition()); + scopedProxyDefinition.setSource(definition.getSource()); + scopedProxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + + String targetBeanName = getTargetBeanName(originalBeanName); + scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); + + if (proxyTargetClass) { + targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); + // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here. + } + else { + scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE); + } + + scopedProxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); + // The target bean should be ignored in favor of the scoped proxy. + targetDefinition.setAutowireCandidate(false); + + // Register the target bean as separate bean in the factory. + registry.registerBeanDefinition(targetBeanName, targetDefinition); + + // Return the scoped proxy definition as primary bean definition + // (potentially an inner bean). + return new BeanDefinitionHolder(scopedProxyDefinition, originalBeanName, definition.getAliases()); + } + + /** + * Generates the bean name that is used within the scoped proxy to reference the target bean. + * @param originalBeanName the original name of bean + * @return the generated bean to be used to reference the target bean + */ + public static String getTargetBeanName(String originalBeanName) { + return TARGET_NAME_PREFIX + originalBeanName; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/scope/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/scope/package.html new file mode 100644 index 0000000000..c79abd6944 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/scope/package.html @@ -0,0 +1,7 @@ + + + +Support for AOP-based scoping of target objects, with configurable backend. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java new file mode 100644 index 0000000000..193c94f95a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractBeanFactoryPointcutAdvisor.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import org.aopalliance.aop.Advice; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.util.Assert; + +/** + * Abstract BeanFactory-based PointcutAdvisor that allows for any Advice + * to be configured as reference to an Advice bean in a BeanFactory. + * + *

    Specifying the name of an advice bean instead of the advice object itself + * (if running within a BeanFactory) increases loose coupling at initialization time, + * in order to not initialize the advice object until the pointcut actually matches. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see #setAdviceBeanName + * @see DefaultBeanFactoryPointcutAdvisor + */ +public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { + + private String adviceBeanName; + + private BeanFactory beanFactory; + + private Advice advice; + + private final Object adviceMonitor = new Object(); + + + /** + * Specify the name of the advice bean that this advisor should refer to. + *

    An instance of the specified bean will be obtained on first access + * of this advisor's advice. This advisor will only ever obtain at most one + * single instance of the advice bean, caching the instance for the lifetime + * of the advisor. + * @see #getAdvice() + */ + public void setAdviceBeanName(String adviceBeanName) { + this.adviceBeanName = adviceBeanName; + } + + /** + * Return the name of the advice bean that this advisor refers to, if any. + */ + public String getAdviceBeanName() { + return this.adviceBeanName; + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + + public Advice getAdvice() { + synchronized (this.adviceMonitor) { + if (this.advice == null && this.adviceBeanName != null) { + Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'"); + this.advice = (Advice) this.beanFactory.getBean(this.adviceBeanName, Advice.class); + } + return this.advice; + } + } + + public String toString() { + return getClass().getName() + ": advice bean '" + getAdviceBeanName() + "'"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractExpressionPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractExpressionPointcut.java new file mode 100644 index 0000000000..743d4559fd --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractExpressionPointcut.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2006 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.aop.support; + +import java.io.Serializable; + +/** + * Abstract superclass for expression pointcuts, + * offering location and expression properties. + * + * @author Rod Johnson + * @author Rob Harrop + * @since 2.0 + * @see #setLocation + * @see #setExpression + */ +public abstract class AbstractExpressionPointcut implements ExpressionPointcut, Serializable { + + private String location; + + private String expression; + + + /** + * Set the location for debugging. + */ + public void setLocation(String location) { + this.location = location; + } + + /** + * Return location information about the pointcut expression + * if available. This is useful in debugging. + * @return location information as a human-readable String, + * or null if none is available + */ + public String getLocation() { + return this.location; + } + + public void setExpression(String expression) { + this.expression = expression; + try { + onSetExpression(expression); + } + catch (IllegalArgumentException ex) { + // Fill in location information if possible. + if (this.location != null) { + throw new IllegalArgumentException("Invalid expression at location [" + this.location + "]: " + ex); + } + else { + throw ex; + } + } + } + + /** + * Called when a new pointcut expression is set. + * The expression should be parsed at this point if possible. + *

    This implementation is empty. + * @param expression expression to set + * @throws IllegalArgumentException if the expression is invalid + * @see #setExpression + */ + protected void onSetExpression(String expression) throws IllegalArgumentException { + } + + /** + * Return this pointcut's expression. + */ + public String getExpression() { + return this.expression; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractGenericPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractGenericPointcutAdvisor.java new file mode 100644 index 0000000000..0d380af6bc --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractGenericPointcutAdvisor.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import org.aopalliance.aop.Advice; + +/** + * Abstract generic PointcutAdvisor that allows for any Advice to be configured. + * + * @author Juergen Hoeller + * @since 2.0 + * @see #setAdvice + * @see DefaultPointcutAdvisor + */ +public abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdvisor { + + private Advice advice; + + + /** + * Specify the advice that this advisor should apply. + */ + public void setAdvice(Advice advice) { + this.advice = advice; + } + + public Advice getAdvice() { + return this.advice; + } + + + public String toString() { + return getClass().getName() + ": advice [" + getAdvice() + "]"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractPointcutAdvisor.java new file mode 100644 index 0000000000..0201d0df63 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractPointcutAdvisor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; + +import org.springframework.aop.PointcutAdvisor; +import org.springframework.core.Ordered; +import org.springframework.util.ObjectUtils; + +/** + * Abstract base class for {@link org.springframework.aop.PointcutAdvisor} + * implementations. Can be subclassed for returning a specific pointcut/advice + * or a freely configurable pointcut/advice. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 1.1.2 + * @see AbstractGenericPointcutAdvisor + */ +public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered, Serializable { + + private int order = Ordered.LOWEST_PRECEDENCE; + + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public boolean isPerInstance() { + return true; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof PointcutAdvisor)) { + return false; + } + PointcutAdvisor otherAdvisor = (PointcutAdvisor) other; + return (ObjectUtils.nullSafeEquals(getAdvice(), otherAdvisor.getAdvice()) && + ObjectUtils.nullSafeEquals(getPointcut(), otherAdvisor.getPointcut())); + } + + public int hashCode() { + return PointcutAdvisor.class.hashCode(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java new file mode 100644 index 0000000000..74e76d84d0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AbstractRegexpMethodPointcut.java @@ -0,0 +1,228 @@ +/* + * Copyright 2002-2008 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.aop.support; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Abstract base regular expression pointcut bean. JavaBean properties are: + *

      + *
    • pattern: regular expression for the fully-qualified method names to match. + * The exact regexp syntax will depend on the subclass (e.g. Perl5 regular expressions) + *
    • patterns: alternative property taking a String array of patterns. The result will + * be the union of these patterns. + *
    + * + *

    Note: the regular expressions must be a match. For example, + * .*get.* will match com.mycom.Foo.getBar(). + * get.* will not. + * + *

    This base class is serializable. Subclasses should declare all fields transient + * - the initPatternRepresentation method in this class will be invoked again on the + * client side on deserialization. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1 + * @see JdkRegexpMethodPointcut + */ +public abstract class AbstractRegexpMethodPointcut extends StaticMethodMatcherPointcut + implements Serializable { + + /** Regular expressions to match */ + private String[] patterns = new String[0]; + + /** Regaular expressions not to match */ + private String[] excludedPatterns = new String[0]; + + + /** + * Convenience method when we have only a single pattern. + * Use either this method or {@link #setPatterns}, not both. + * @see #setPatterns + */ + public void setPattern(String pattern) { + setPatterns(new String[] {pattern}); + } + + /** + * Set the regular expressions defining methods to match. + * Matching will be the union of all these; if any match, + * the pointcut matches. + */ + public void setPatterns(String[] patterns) { + Assert.notEmpty(patterns, "'patterns' must not be empty"); + this.patterns = new String[patterns.length]; + for (int i = 0; i < patterns.length; i++) { + this.patterns[i] = StringUtils.trimWhitespace(patterns[i]); + } + initPatternRepresentation(this.patterns); + } + + /** + * Return the regular expressions for method matching. + */ + public String[] getPatterns() { + return this.patterns; + } + + /** + * Convenience method when we have only a single exclusion pattern. + * Use either this method or {@link #setExcludedPatterns}, not both. + * @see #setExcludedPatterns + */ + public void setExcludedPattern(String excludedPattern) { + setExcludedPatterns(new String[] {excludedPattern}); + } + + /** + * Set the regular expressions defining methods to match for exclusion. + * Matching will be the union of all these; if any match, + * the pointcut matches. + */ + public void setExcludedPatterns(String[] excludedPatterns) { + Assert.notEmpty(excludedPatterns, "'excludedPatterns' must not be empty"); + this.excludedPatterns = new String[excludedPatterns.length]; + for (int i = 0; i < excludedPatterns.length; i++) { + this.excludedPatterns[i] = StringUtils.trimWhitespace(excludedPatterns[i]); + } + initExcludedPatternRepresentation(this.excludedPatterns); + } + + /** + * Returns the regular expressions for exclusion matching. + */ + public String[] getExcludedPatterns() { + return this.excludedPatterns; + } + + + /** + * Try to match the regular expression against the fully qualified name + * of the target class as well as against the method's declaring class, + * plus the name of the method. + */ + public boolean matches(Method method, Class targetClass) { + return ((targetClass != null && matchesPattern(targetClass.getName() + "." + method.getName())) || + matchesPattern(method.getDeclaringClass().getName() + "." + method.getName())); + } + + /** + * Match the specified candidate against the configured patterns. + * @param signatureString "java.lang.Object.hashCode" style signature + * @return whether the candidate matches at least one of the specified patterns + */ + protected boolean matchesPattern(String signatureString) { + for (int i = 0; i < this.patterns.length; i++) { + boolean matched = matches(signatureString, i); + if (matched) { + for (int j = 0; j < this.excludedPatterns.length; j++) { + boolean excluded = matchesExclusion(signatureString, j); + if (excluded) { + return false; + } + } + return true; + } + } + return false; + } + + + /** + * Subclasses must implement this to initialize regexp pointcuts. + * Can be invoked multiple times. + *

    This method will be invoked from the setPatterns method, + * and also on deserialization. + * @param patterns the patterns to initialize + * @throws IllegalArgumentException in case of an invalid pattern + */ + protected abstract void initPatternRepresentation(String[] patterns) throws IllegalArgumentException; + + protected abstract void initExcludedPatternRepresentation(String[] excludedPatterns) throws IllegalArgumentException; + + /** + * Does the pattern at the given index match this string? + * @param pattern String pattern to match + * @param patternIndex index of pattern from 0 + * @return true if there is a match, else false. + */ + protected abstract boolean matches(String pattern, int patternIndex); + + /** + * Does the exclusion pattern at the given index match this string? + * @param pattern String pattern to match. + * @param patternIndex index of pattern starting from 0. + * @return true if there is a match, else false. + */ + protected abstract boolean matchesExclusion(String pattern, int patternIndex); + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AbstractRegexpMethodPointcut)) { + return false; + } + AbstractRegexpMethodPointcut otherPointcut = (AbstractRegexpMethodPointcut) other; + return (Arrays.equals(this.patterns, otherPointcut.patterns) && + Arrays.equals(this.excludedPatterns, otherPointcut.excludedPatterns)); + } + + public int hashCode() { + int result = 27; + for (int i = 0; i < this.patterns.length; i++) { + String pattern = this.patterns[i]; + result = 13 * result + pattern.hashCode(); + } + for (int i = 0; i < this.excludedPatterns.length; i++) { + String excludedPattern = this.excludedPatterns[i]; + result = 13 * result + excludedPattern.hashCode(); + } + return result; + } + + public String toString() { + return getClass().getName() + ": patterns " + ObjectUtils.nullSafeToString(this.patterns) + + ", excluded patterns " + ObjectUtils.nullSafeToString(this.excludedPatterns); + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Ask subclass to reinitialize. + initPatternRepresentation(this.patterns); + initExcludedPatternRepresentation(this.excludedPatterns); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/AopUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/AopUtils.java new file mode 100644 index 0000000000..43cab6981a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/AopUtils.java @@ -0,0 +1,323 @@ +/* + * Copyright 2002-2008 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.aop.support; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.springframework.aop.Advisor; +import org.springframework.aop.AopInvocationException; +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.IntroductionAwareMethodMatcher; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.aop.SpringProxy; +import org.springframework.aop.TargetClassAware; +import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.JdkVersion; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Utility methods for AOP support code. + * Mainly for internal use within Spring's AOP support. + * + *

    See {@link org.springframework.aop.framework.AopProxyUtils} for a + * collection of framework-specific AOP utility methods which depend + * on internals of Spring's AOP framework implementation. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @see org.springframework.aop.framework.AopProxyUtils + */ +public abstract class AopUtils { + + /** + * Check whether the given object is a JDK dynamic proxy or a CGLIB proxy. + * @param object the object to check + * @see #isJdkDynamicProxy + * @see #isCglibProxy + */ + public static boolean isAopProxy(Object object) { + return (object instanceof SpringProxy && + (Proxy.isProxyClass(object.getClass()) || isCglibProxyClass(object.getClass()))); + } + + /** + * Check whether the given object is a JDK dynamic proxy. + * @param object the object to check + * @see java.lang.reflect.Proxy#isProxyClass + */ + public static boolean isJdkDynamicProxy(Object object) { + return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass())); + } + + /** + * Check whether the given object is a CGLIB proxy. + * @param object the object to check + */ + public static boolean isCglibProxy(Object object) { + return (object instanceof SpringProxy && isCglibProxyClass(object.getClass())); + } + + /** + * Check whether the specified class is a CGLIB-generated class. + * @param clazz the class to check + */ + public static boolean isCglibProxyClass(Class clazz) { + return (clazz != null && clazz.getName().indexOf(ClassUtils.CGLIB_CLASS_SEPARATOR) != -1); + } + + /** + * Determine the target class of the given bean instance, + * which might be an AOP proxy. + *

    Returns the target class for an AOP proxy and the plain class else. + * @param candidate the instance to check (might be an AOP proxy) + * @return the target class (or the plain class of the given object as fallback) + * @see org.springframework.aop.TargetClassAware#getTargetClass() + */ + public static Class getTargetClass(Object candidate) { + Assert.notNull(candidate, "Candidate object must not be null"); + if (candidate instanceof TargetClassAware) { + return ((TargetClassAware) candidate).getTargetClass(); + } + if (isCglibProxyClass(candidate.getClass())) { + return candidate.getClass().getSuperclass(); + } + return candidate.getClass(); + } + + /** + * Determine whether the given method is an "equals" method. + * @see java.lang.Object#equals + */ + public static boolean isEqualsMethod(Method method) { + return ReflectionUtils.isEqualsMethod(method); + } + + /** + * Determine whether the given method is a "hashCode" method. + * @see java.lang.Object#hashCode + */ + public static boolean isHashCodeMethod(Method method) { + return ReflectionUtils.isHashCodeMethod(method); + } + + /** + * Determine whether the given method is a "toString" method. + * @see java.lang.Object#toString() + */ + public static boolean isToStringMethod(Method method) { + return ReflectionUtils.isToStringMethod(method); + } + + /** + * Determine whether the given method is a "finalize" method. + * @see java.lang.Object#finalize() + */ + public static boolean isFinalizeMethod(Method method) { + return (method != null && method.getName().equals("finalize") && + method.getParameterTypes().length == 0); + } + + /** + * Given a method, which may come from an interface, and a target class used + * in the current AOP invocation, find the corresponding target method if there + * is one. E.g. the method may be IFoo.bar() and the target class + * may be DefaultFoo. In this case, the method may be + * DefaultFoo.bar(). This enables attributes on that method to be found. + *

    NOTE: In contrast to {@link org.springframework.util.ClassUtils#getMostSpecificMethod}, + * this method resolves Java 5 bridge methods in order to retrieve attributes + * from the original method definition. + * @param method the method to be invoked, which may come from an interface + * @param targetClass the target class for the current invocation. + * May be null or may not even implement the method. + * @return the specific target method, or the original method if the + * targetClass doesn't implement it or is null + * @see org.springframework.util.ClassUtils#getMostSpecificMethod + */ + public static Method getMostSpecificMethod(Method method, Class targetClass) { + Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, targetClass); + // If we are dealing with method with generic parameters, find the original method. + if (JdkVersion.isAtLeastJava15()) { + resolvedMethod = BridgeMethodResolver.findBridgedMethod(resolvedMethod); + } + return resolvedMethod; + } + + + /** + * Can the given pointcut apply at all on the given class? + *

    This is an important test as it can be used to optimize + * out a pointcut for a class. + * @param pc the static or dynamic pointcut to check + * @param targetClass the class to test + * @return whether the pointcut can apply on any method + */ + public static boolean canApply(Pointcut pc, Class targetClass) { + return canApply(pc, targetClass, false); + } + + /** + * Can the given pointcut apply at all on the given class? + *

    This is an important test as it can be used to optimize + * out a pointcut for a class. + * @param pc the static or dynamic pointcut to check + * @param targetClass the class to test + * @param hasIntroductions whether or not the advisor chain + * for this bean includes any introductions + * @return whether the pointcut can apply on any method + */ + public static boolean canApply(Pointcut pc, Class targetClass, boolean hasIntroductions) { + if (!pc.getClassFilter().matches(targetClass)) { + return false; + } + + MethodMatcher methodMatcher = pc.getMethodMatcher(); + IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; + if (methodMatcher instanceof IntroductionAwareMethodMatcher) { + introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; + } + + Set classes = new HashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); + classes.add(targetClass); + for (Iterator it = classes.iterator(); it.hasNext();) { + Class clazz = (Class) it.next(); + Method[] methods = clazz.getMethods(); + for (int j = 0; j < methods.length; j++) { + if ((introductionAwareMethodMatcher != null && + introductionAwareMethodMatcher.matches(methods[j], targetClass, hasIntroductions)) || + methodMatcher.matches(methods[j], targetClass)) { + return true; + } + } + } + + return false; + } + + /** + * Can the given advisor apply at all on the given class? + * This is an important test as it can be used to optimize + * out a advisor for a class. + * @param advisor the advisor to check + * @param targetClass class we're testing + * @return whether the pointcut can apply on any method + */ + public static boolean canApply(Advisor advisor, Class targetClass) { + return canApply(advisor, targetClass, false); + } + + /** + * Can the given advisor apply at all on the given class? + *

    This is an important test as it can be used to optimize out a advisor for a class. + * This version also takes into account introductions (for IntroductionAwareMethodMatchers). + * @param advisor the advisor to check + * @param targetClass class we're testing + * @param hasIntroductions whether or not the advisor chain for this bean includes + * any introductions + * @return whether the pointcut can apply on any method + */ + public static boolean canApply(Advisor advisor, Class targetClass, boolean hasIntroductions) { + if (advisor instanceof IntroductionAdvisor) { + return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); + } + else if (advisor instanceof PointcutAdvisor) { + PointcutAdvisor pca = (PointcutAdvisor) advisor; + return canApply(pca.getPointcut(), targetClass, hasIntroductions); + } + else { + // It doesn't have a pointcut so we assume it applies. + return true; + } + } + + /** + * Determine the sublist of the candidateAdvisors list + * that is applicable to the given class. + * @param candidateAdvisors the Advisors to evaluate + * @param clazz the target class + * @return sublist of Advisors that can apply to an object of the given class + * (may be the incoming List as-is) + */ + public static List findAdvisorsThatCanApply(List candidateAdvisors, Class clazz) { + if (candidateAdvisors.isEmpty()) { + return candidateAdvisors; + } + List eligibleAdvisors = new LinkedList(); + for (Iterator it = candidateAdvisors.iterator(); it.hasNext();) { + Advisor candidate = (Advisor) it.next(); + if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { + eligibleAdvisors.add(candidate); + } + } + boolean hasIntroductions = !eligibleAdvisors.isEmpty(); + for (Iterator it = candidateAdvisors.iterator(); it.hasNext();) { + Advisor candidate = (Advisor) it.next(); + if (candidate instanceof IntroductionAdvisor) { + // already processed + continue; + } + if (canApply(candidate, clazz, hasIntroductions)) { + eligibleAdvisors.add(candidate); + } + } + return eligibleAdvisors; + } + + + /** + * Invoke the given target via reflection, as part of an AOP method invocation. + * @param target the target object + * @param method the method to invoke + * @param args the arguments for the method + * @return the invocation result, if any + * @throws Throwable if thrown by the target method + * @throws org.springframework.aop.AopInvocationException in case of a reflection error + */ + public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) + throws Throwable { + + // Use reflection to invoke the method. + try { + ReflectionUtils.makeAccessible(method); + return method.invoke(target, args); + } + catch (InvocationTargetException ex) { + // Invoked method threw a checked exception. + // We must rethrow it. The client won't see the interceptor. + throw ex.getTargetException(); + } + catch (IllegalArgumentException ex) { + throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" + + method + "] on target [" + target + "]", ex); + } + catch (IllegalAccessException ex) { + throw new AopInvocationException("Could not access method [" + method + "]", ex); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/ClassFilters.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/ClassFilters.java new file mode 100644 index 0000000000..fc48182bf6 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/ClassFilters.java @@ -0,0 +1,148 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; + +import org.springframework.aop.ClassFilter; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Static utility methods for composing + * {@link org.springframework.aop.ClassFilter ClassFilters}. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 11.11.2003 + * @see MethodMatchers + * @see Pointcuts + */ +public abstract class ClassFilters { + + /** + * Match all classes that either (or both) of the given ClassFilters matches. + * @param cf1 the first ClassFilter + * @param cf2 the second ClassFilter + * @return a distinct ClassFilter that matches all classes that either + * of the given ClassFilter matches + */ + public static ClassFilter union(ClassFilter cf1, ClassFilter cf2) { + Assert.notNull(cf1, "First ClassFilter must not be null"); + Assert.notNull(cf2, "Second ClassFilter must not be null"); + return new UnionClassFilter(new ClassFilter[] {cf1, cf2}); + } + + /** + * Match all classes that either (or all) of the given ClassFilters matches. + * @param classFilters the ClassFilters to match + * @return a distinct ClassFilter that matches all classes that either + * of the given ClassFilter matches + */ + public static ClassFilter union(ClassFilter[] classFilters) { + Assert.notEmpty(classFilters, "ClassFilter array must not be empty"); + return new UnionClassFilter(classFilters); + } + + /** + * Match all classes that both of the given ClassFilters match. + * @param cf1 the first ClassFilter + * @param cf2 the second ClassFilter + * @return a distinct ClassFilter that matches all classes that both + * of the given ClassFilter match + */ + public static ClassFilter intersection(ClassFilter cf1, ClassFilter cf2) { + Assert.notNull(cf1, "First ClassFilter must not be null"); + Assert.notNull(cf2, "Second ClassFilter must not be null"); + return new IntersectionClassFilter(new ClassFilter[] {cf1, cf2}); + } + + /** + * Match all classes that all of the given ClassFilters match. + * @param classFilters the ClassFilters to match + * @return a distinct ClassFilter that matches all classes that both + * of the given ClassFilter match + */ + public static ClassFilter intersection(ClassFilter[] classFilters) { + Assert.notEmpty(classFilters, "ClassFilter array must not be empty"); + return new IntersectionClassFilter(classFilters); + } + + + /** + * ClassFilter implementation for a union of the given ClassFilters. + */ + private static class UnionClassFilter implements ClassFilter, Serializable { + + private ClassFilter[] filters; + + public UnionClassFilter(ClassFilter[] filters) { + this.filters = filters; + } + + public boolean matches(Class clazz) { + for (int i = 0; i < this.filters.length; i++) { + if (this.filters[i].matches(clazz)) { + return true; + } + } + return false; + } + + public boolean equals(Object other) { + return (this == other || (other instanceof UnionClassFilter && + ObjectUtils.nullSafeEquals(this.filters, ((UnionClassFilter) other).filters))); + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.filters); + } + } + + + /** + * ClassFilter implementation for an intersection of the given ClassFilters. + */ + private static class IntersectionClassFilter implements ClassFilter, Serializable { + + private ClassFilter[] filters; + + public IntersectionClassFilter(ClassFilter[] filters) { + this.filters = filters; + } + + public boolean matches(Class clazz) { + for (int i = 0; i < this.filters.length; i++) { + if (!this.filters[i].matches(clazz)) { + return false; + } + } + return true; + } + + public boolean equals(Object other) { + return (this == other || (other instanceof IntersectionClassFilter && + ObjectUtils.nullSafeEquals(this.filters, ((IntersectionClassFilter) other).filters))); + } + + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.filters); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java new file mode 100644 index 0000000000..7d1a92d46b --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/ComposablePointcut.java @@ -0,0 +1,211 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Convenient class for building up pointcuts. All methods return + * ComposablePointcut, so we can use a concise idiom like: + * + * + * Pointcut pc = new ComposablePointcut().union(classFilter).intersection(methodMatcher).intersection(pointcut); + * + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @since 11.11.2003 + * @see Pointcuts + */ +public class ComposablePointcut implements Pointcut, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = -2743223737633663832L; + + private ClassFilter classFilter; + + private MethodMatcher methodMatcher; + + + /** + * Create a default ComposablePointcut, with ClassFilter.TRUE + * and MethodMatcher.TRUE. + */ + public ComposablePointcut() { + this.classFilter = ClassFilter.TRUE; + this.methodMatcher = MethodMatcher.TRUE; + } + + /** + * Create a ComposablePointcut based on the given Pointcut. + * @param pointcut the original Pointcut + */ + public ComposablePointcut(Pointcut pointcut) { + Assert.notNull(pointcut, "Pointcut must not be null"); + this.classFilter = pointcut.getClassFilter(); + this.methodMatcher = pointcut.getMethodMatcher(); + } + + /** + * Create a ComposablePointcut for the given ClassFilter, + * with MethodMatcher.TRUE. + * @param classFilter the ClassFilter to use + */ + public ComposablePointcut(ClassFilter classFilter) { + Assert.notNull(classFilter, "ClassFilter must not be null"); + this.classFilter = classFilter; + this.methodMatcher = MethodMatcher.TRUE; + } + + /** + * Create a ComposablePointcut for the given MethodMatcher, + * with ClassFilter.TRUE. + * @param methodMatcher the MethodMatcher to use + */ + public ComposablePointcut(MethodMatcher methodMatcher) { + Assert.notNull(methodMatcher, "MethodMatcher must not be null"); + this.classFilter = ClassFilter.TRUE; + this.methodMatcher = methodMatcher; + } + + /** + * Create a ComposablePointcut for the given ClassFilter and MethodMatcher. + * @param classFilter the ClassFilter to use + * @param methodMatcher the MethodMatcher to use + */ + public ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher) { + Assert.notNull(classFilter, "ClassFilter must not be null"); + Assert.notNull(methodMatcher, "MethodMatcher must not be null"); + this.classFilter = classFilter; + this.methodMatcher = methodMatcher; + } + + + /** + * Apply a union with the given ClassFilter. + * @param other the ClassFilter to apply a union with + * @return this composable pointcut (for call chaining) + */ + public ComposablePointcut union(ClassFilter other) { + this.classFilter = ClassFilters.union(this.classFilter, other); + return this; + } + + /** + * Apply an intersection with the given ClassFilter. + * @param other the ClassFilter to apply an intersection with + * @return this composable pointcut (for call chaining) + */ + public ComposablePointcut intersection(ClassFilter other) { + this.classFilter = ClassFilters.intersection(this.classFilter, other); + return this; + } + + /** + * Apply a union with the given MethodMatcher. + * @param other the MethodMatcher to apply a union with + * @return this composable pointcut (for call chaining) + */ + public ComposablePointcut union(MethodMatcher other) { + this.methodMatcher = MethodMatchers.union(this.methodMatcher, other); + return this; + } + + /** + * Apply an intersection with the given MethodMatcher. + * @param other the MethodMatcher to apply an intersection with + * @return this composable pointcut (for call chaining) + */ + public ComposablePointcut intersection(MethodMatcher other) { + this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other); + return this; + } + + /** + * Apply a union with the given Pointcut. + *

    Note that for a Pointcut union, methods will only match if their + * original ClassFilter (from the originating Pointcut) matches as well. + * MethodMatchers and ClassFilters from different Pointcuts will never + * get interleaved with each other. + * @param other the Pointcut to apply a union with + * @return this composable pointcut (for call chaining) + */ + public ComposablePointcut union(Pointcut other) { + this.methodMatcher = MethodMatchers.union( + this.methodMatcher, this.classFilter, other.getMethodMatcher(), other.getClassFilter()); + this.classFilter = ClassFilters.union(this.classFilter, other.getClassFilter()); + return this; + } + + /** + * Apply an intersection with the given Pointcut. + * @param other the Pointcut to apply an intersection with + * @return this composable pointcut (for call chaining) + */ + public ComposablePointcut intersection(Pointcut other) { + this.classFilter = ClassFilters.intersection(this.classFilter, other.getClassFilter()); + this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other.getMethodMatcher()); + return this; + } + + + public ClassFilter getClassFilter() { + return this.classFilter; + } + + public MethodMatcher getMethodMatcher() { + return this.methodMatcher; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ComposablePointcut)) { + return false; + } + + ComposablePointcut that = (ComposablePointcut) other; + return ObjectUtils.nullSafeEquals(that.classFilter, this.classFilter) && + ObjectUtils.nullSafeEquals(that.methodMatcher, this.methodMatcher); + } + + public int hashCode() { + int code = 17; + if (this.classFilter != null) { + code = 37 * code + this.classFilter.hashCode(); + } + if (this.methodMatcher != null) { + code = 37 * code + this.methodMatcher.hashCode(); + } + return code; + } + + public String toString() { + return "ComposablePointcut: ClassFilter [" + this.classFilter + + "], MethodMatcher [" + this.methodMatcher + "]"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java new file mode 100644 index 0000000000..3539a7b23e --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/ControlFlowPointcut.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2006 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.aop.support; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.core.ControlFlow; +import org.springframework.core.ControlFlowFactory; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Pointcut and method matcher for use in simple cflow-style pointcut. + * Note that evaluating such pointcuts is 10-15 times slower than evaluating + * normal pointcuts, but they are useful in some cases. + * + * @author Rod Johnson + * @author Rob Harrop + * @see org.springframework.core.ControlFlow + */ +public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable { + + private Class clazz; + + private String methodName; + + private int evaluations; + + + /** + * Construct a new pointcut that matches all control flows below that class. + * @param clazz the clazz + */ + public ControlFlowPointcut(Class clazz) { + this(clazz, null); + } + + /** + * Construct a new pointcut that matches all calls below the + * given method in the given class. If the method name is null, + * matches all control flows below that class. + * @param clazz the clazz + * @param methodName the name of the method + */ + public ControlFlowPointcut(Class clazz, String methodName) { + Assert.notNull(clazz, "Class must not be null"); + this.clazz = clazz; + this.methodName = methodName; + } + + + /** + * Subclasses can override this for greater filtering (and performance). + */ + public boolean matches(Class clazz) { + return true; + } + + /** + * Subclasses can override this if it's possible to filter out + * some candidate classes. + */ + public boolean matches(Method method, Class targetClass) { + return true; + } + + public boolean isRuntime() { + return true; + } + + public boolean matches(Method method, Class targetClass, Object[] args) { + ++this.evaluations; + ControlFlow cflow = ControlFlowFactory.createControlFlow(); + return (this.methodName != null) ? cflow.under(this.clazz, this.methodName) : cflow.under(this.clazz); + } + + /** + * It's useful to know how many times we've fired, for optimization. + */ + public int getEvaluations() { + return evaluations; + } + + + public ClassFilter getClassFilter() { + return this; + } + + public MethodMatcher getMethodMatcher() { + return this; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ControlFlowPointcut)) { + return false; + } + ControlFlowPointcut that = (ControlFlowPointcut) other; + return (this.clazz.equals(that.clazz)) && ObjectUtils.nullSafeEquals(that.methodName, this.methodName); + } + + public int hashCode() { + int code = 17; + code = 37 * code + this.clazz.hashCode(); + if (this.methodName != null) { + code = 37 * code + this.methodName.hashCode(); + } + return code; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultBeanFactoryPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultBeanFactoryPointcutAdvisor.java new file mode 100644 index 0000000000..d59779e3a0 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultBeanFactoryPointcutAdvisor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import org.springframework.aop.Pointcut; + +/** + * Concrete BeanFactory-based PointcutAdvisor that allows for any Advice + * to be configured as reference to an Advice bean in the BeanFactory, + * as well as the Pointcut to be configured through a bean property. + * + *

    Specifying the name of an advice bean instead of the advice object itself + * (if running within a BeanFactory) increases loose coupling at initialization time, + * in order to not initialize the advice object until the pointcut actually matches. + * + * @author Juergen Hoeller + * @since 2.0.2 + * @see #setPointcut + * @see #setAdviceBeanName + */ +public class DefaultBeanFactoryPointcutAdvisor extends AbstractBeanFactoryPointcutAdvisor { + + private Pointcut pointcut = Pointcut.TRUE; + + + /** + * Specify the pointcut targeting the advice. + *

    Default is Pointcut.TRUE. + * @see #setAdviceBeanName + */ + public void setPointcut(Pointcut pointcut) { + this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE); + } + + public Pointcut getPointcut() { + return this.pointcut; + } + + + public String toString() { + return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice bean '" + getAdviceBeanName() + "'"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java new file mode 100644 index 0000000000..8a0f49330c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultIntroductionAdvisor.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.DynamicIntroductionAdvice; +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.IntroductionInfo; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Simple {@link org.springframework.aop.IntroductionAdvisor} implementation + * that by default applies to any class. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 11.11.2003 + */ +public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFilter, Ordered, Serializable { + + private final Advice advice; + + private final Set interfaces = new HashSet(); + + private int order = Integer.MAX_VALUE; + + + /** + * Create a DefaultIntroductionAdvisor for the given advice. + * @param advice the Advice to apply (may implement the + * {@link org.springframework.aop.IntroductionInfo} interface) + * @see #addInterface + */ + public DefaultIntroductionAdvisor(Advice advice) { + this(advice, (advice instanceof IntroductionInfo ? (IntroductionInfo) advice : null)); + } + + /** + * Create a DefaultIntroductionAdvisor for the given advice. + * @param advice the Advice to apply + * @param introductionInfo the IntroductionInfo that describes + * the interface to introduce (may be null) + */ + public DefaultIntroductionAdvisor(Advice advice, IntroductionInfo introductionInfo) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + if (introductionInfo != null) { + Class[] introducedInterfaces = introductionInfo.getInterfaces(); + if (introducedInterfaces.length == 0) { + throw new IllegalArgumentException("IntroductionAdviceSupport implements no interfaces"); + } + for (int i = 0; i < introducedInterfaces.length; i++) { + addInterface(introducedInterfaces[i]); + } + } + } + + /** + * Create a DefaultIntroductionAdvisor for the given advice. + * @param advice the Advice to apply + * @param intf the interface to introduce + */ + public DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class intf) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + addInterface(intf); + } + + + /** + * Add the specified interface to the list of interfaces to introduce. + * @param intf the interface to introduce + */ + public void addInterface(Class intf) { + Assert.notNull(intf, "Interface must not be null"); + if (!intf.isInterface()) { + throw new IllegalArgumentException("Specified class [" + intf.getName() + "] must be an interface"); + } + this.interfaces.add(intf); + } + + public Class[] getInterfaces() { + return (Class[]) this.interfaces.toArray(new Class[this.interfaces.size()]); + } + + public void validateInterfaces() throws IllegalArgumentException { + for (Iterator it = this.interfaces.iterator(); it.hasNext();) { + Class ifc = (Class) it.next(); + if (this.advice instanceof DynamicIntroductionAdvice && + !((DynamicIntroductionAdvice) this.advice).implementsInterface(ifc)) { + throw new IllegalArgumentException("DynamicIntroductionAdvice [" + this.advice + "] " + + "does not implement interface [" + ifc.getName() + "] specified for introduction"); + } + } + } + + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + + public Advice getAdvice() { + return this.advice; + } + + public boolean isPerInstance() { + return true; + } + + public ClassFilter getClassFilter() { + return this; + } + + public boolean matches(Class clazz) { + return true; + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof DefaultIntroductionAdvisor)) { + return false; + } + DefaultIntroductionAdvisor otherAdvisor = (DefaultIntroductionAdvisor) other; + return (this.advice.equals(otherAdvisor.advice) && this.interfaces.equals(otherAdvisor.interfaces)); + } + + public int hashCode() { + return this.advice.hashCode() * 13 + this.interfaces.hashCode(); + } + + public String toString() { + return ClassUtils.getShortName(getClass()) + ": advice [" + this.advice + "]; interfaces " + + ClassUtils.classNamesToString(this.interfaces); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultPointcutAdvisor.java new file mode 100644 index 0000000000..d1359278bb --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DefaultPointcutAdvisor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; + +/** + * Convenient Pointcut-driven Advisor implementation. + * + *

    This is the most commonly used Advisor implementation. It can be used + * with any pointcut and advice type, except for introductions. There is + * normally no need to subclass this class, or to implement custom Advisors. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setPointcut + * @see #setAdvice + */ +public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable { + + private Pointcut pointcut = Pointcut.TRUE; + + + /** + * Create an empty DefaultPointcutAdvisor. + *

    Advice must be set before use using setter methods. + * Pointcut will normally be set also, but defaults to Pointcut.TRUE. + */ + public DefaultPointcutAdvisor() { + } + + /** + * Create a DefaultPointcutAdvisor that matches all methods. + *

    Pointcut.TRUE will be used as Pointcut. + * @param advice the Advice to use + */ + public DefaultPointcutAdvisor(Advice advice) { + this(Pointcut.TRUE, advice); + } + + /** + * Create a DefaultPointcutAdvisor, specifying Pointcut and Advice. + * @param pointcut the Pointcut targeting the Advice + * @param advice the Advice to run when Pointcut matches + */ + public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) { + this.pointcut = pointcut; + setAdvice(advice); + } + + + /** + * Specify the pointcut targeting the advice. + *

    Default is Pointcut.TRUE. + * @see #setAdvice + */ + public void setPointcut(Pointcut pointcut) { + this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE); + } + + public Pointcut getPointcut() { + return this.pointcut; + } + + + public String toString() { + return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java new file mode 100644 index 0000000000..378fb353c7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DelegatePerTargetObjectIntroductionInterceptor.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.DynamicIntroductionAdvice; +import org.springframework.aop.IntroductionInterceptor; +import org.springframework.aop.ProxyMethodInvocation; + +/** + * Convenient implementation of the + * {@link org.springframework.aop.IntroductionInterceptor} interface. + * + *

    This differs from {@link DelegatingIntroductionInterceptor} in that a single + * instance of this class can be used to advise multiple target objects, and each target + * object will have its own delegate (whereas DelegatingIntroductionInterceptor + * shares the same delegate, and hence the same state across all targets). + * + *

    The suppressInterface method can be used to suppress interfaces + * implemented by the delegate class but which should not be introduced to the + * owning AOP proxy. + * + *

    An instance of this class is serializable if the delegates are. + * + *

    Note: There are some implementation similarities between this class and + * {@link DelegatingIntroductionInterceptor} that suggest a possible refactoring + * to extract a common ancestor class in the future. + * + * @author Adrian Colyer + * @author Juergen Hoeller + * @since 2.0 + * @see #suppressInterface + * @see DelegatingIntroductionInterceptor + */ +public class DelegatePerTargetObjectIntroductionInterceptor extends IntroductionInfoSupport + implements IntroductionInterceptor { + + /** + * Hold weak references to keys as we don't want to interfere with garbage collection.. + */ + private Map delegateMap = new WeakHashMap(); + + private Class defaultImplType; + + private Class interfaceType; + + + public DelegatePerTargetObjectIntroductionInterceptor(Class defaultImplType, Class interfaceType) { + this.defaultImplType = defaultImplType; + this.interfaceType = interfaceType; + // cCeate a new delegate now (but don't store it in the map). + // We do this for two reasons: + // 1) to fail early if there is a problem instantiating delegates + // 2) to populate the interface map once and once only + Object delegate = createNewDelegate(); + implementInterfacesOnObject(delegate); + suppressInterface(IntroductionInterceptor.class); + suppressInterface(DynamicIntroductionAdvice.class); + } + + + /** + * Subclasses may need to override this if they want to perform custom + * behaviour in around advice. However, subclasses should invoke this + * method, which handles introduced interfaces and forwarding to the target. + */ + public Object invoke(MethodInvocation mi) throws Throwable { + if (isMethodOnIntroducedInterface(mi)) { + Object delegate = getIntroductionDelegateFor(mi.getThis()); + + // Using the following method rather than direct reflection, + // we get correct handling of InvocationTargetException + // if the introduced method throws an exception. + Object retVal = AopUtils.invokeJoinpointUsingReflection(delegate, mi.getMethod(), mi.getArguments()); + + // Massage return value if possible: if the delegate returned itself, + // we really want to return the proxy. + if (retVal == delegate && mi instanceof ProxyMethodInvocation) { + retVal = ((ProxyMethodInvocation) mi).getProxy(); + } + return retVal; + } + + return doProceed(mi); + } + + /** + * Proceed with the supplied {@link org.aopalliance.intercept.MethodInterceptor}. + * Subclasses can override this method to intercept method invocations on the + * target object which is useful when an introduction needs to monitor the object + * that it is introduced into. This method is never called for + * {@link MethodInvocation MethodInvocations} on the introduced interfaces. + */ + protected Object doProceed(MethodInvocation mi) throws Throwable { + // If we get here, just pass the invocation on. + return mi.proceed(); + } + + private Object getIntroductionDelegateFor(Object targetObject) { + synchronized(this.delegateMap) { + if (this.delegateMap.containsKey(targetObject)) { + return this.delegateMap.get(targetObject); + } + else { + Object delegate = createNewDelegate(); + this.delegateMap.put(targetObject, delegate); + return delegate; + } + } + } + + private Object createNewDelegate() { + try { + return this.defaultImplType.newInstance(); + } + catch (Throwable ex) { + throw new IllegalArgumentException("Cannot create default implementation for '" + + this.interfaceType.getName() + "' mixin (" + this.defaultImplType.getName() + "): " + ex); + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java new file mode 100644 index 0000000000..fc9631bd61 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DelegatingIntroductionInterceptor.java @@ -0,0 +1,134 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.DynamicIntroductionAdvice; +import org.springframework.aop.IntroductionInterceptor; +import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.util.Assert; + +/** + * Convenient implementation of the + * {@link org.springframework.aop.IntroductionInterceptor} interface. + * + *

    Subclasses merely need to extend this class and implement the interfaces + * to be introduced themselves. In this case the delegate is the subclass + * instance itself. Alternatively a separate delegate may implement the + * interface, and be set via the delegate bean property. + * + *

    Delegates or subclasses may implement any number of interfaces. + * All interfaces except IntroductionInterceptor are picked up from + * the subclass or delegate by default. + * + *

    The suppressInterface method can be used to suppress interfaces + * implemented by the delegate but which should not be introduced to the owning + * AOP proxy. + * + *

    An instance of this class is serializable if the delegate is. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.11.2003 + * @see #suppressInterface + * @see DelegatePerTargetObjectIntroductionInterceptor + */ +public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport + implements IntroductionInterceptor { + + /** + * Object that actually implements the interfaces. + * May be "this" if a subclass implements the introduced interfaces. + */ + private Object delegate; + + + /** + * Construct a new DelegatingIntroductionInterceptor, providing + * a delegate that implements the interfaces to be introduced. + * @param delegate the delegate that implements the introduced interfaces + */ + public DelegatingIntroductionInterceptor(Object delegate) { + init(delegate); + } + + /** + * Construct a new DelegatingIntroductionInterceptor. + * The delegate will be the subclass, which must implement + * additional interfaces. + */ + protected DelegatingIntroductionInterceptor() { + init(this); + } + + + /** + * Both constructors use this init method, as it is impossible to pass + * a "this" reference from one constructor to another. + * @param delegate the delegate object + */ + private void init(Object delegate) { + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; + implementInterfacesOnObject(delegate); + + // We don't want to expose the control interface + suppressInterface(IntroductionInterceptor.class); + suppressInterface(DynamicIntroductionAdvice.class); + } + + + /** + * Subclasses may need to override this if they want to perform custom + * behaviour in around advice. However, subclasses should invoke this + * method, which handles introduced interfaces and forwarding to the target. + */ + public Object invoke(MethodInvocation mi) throws Throwable { + if (isMethodOnIntroducedInterface(mi)) { + // Using the following method rather than direct reflection, we + // get correct handling of InvocationTargetException + // if the introduced method throws an exception. + Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments()); + + // Massage return value if possible: if the delegate returned itself, + // we really want to return the proxy. + if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) { + Object proxy = ((ProxyMethodInvocation) mi).getProxy(); + if (mi.getMethod().getReturnType().isInstance(proxy)) { + retVal = proxy; + } + } + return retVal; + } + + return doProceed(mi); + } + + /** + * Proceed with the supplied {@link org.aopalliance.intercept.MethodInterceptor}. + * Subclasses can override this method to intercept method invocations on the + * target object which is useful when an introduction needs to monitor the object + * that it is introduced into. This method is never called for + * {@link MethodInvocation MethodInvocations} on the introduced interfaces. + */ + protected Object doProceed(MethodInvocation mi) throws Throwable { + // If we get here, just pass the invocation on. + return mi.proceed(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java new file mode 100644 index 0000000000..3bb0451899 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcher.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2005 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.aop.support; + +import java.lang.reflect.Method; + +import org.springframework.aop.MethodMatcher; + +/** + * Convenient abstract superclass for dynamic method matchers, + * which do care about arguments at runtime. + */ +public abstract class DynamicMethodMatcher implements MethodMatcher { + + public final boolean isRuntime() { + return true; + } + + /** + * Can override to add preconditions for dynamic matching. This implementation + * always returns true. + */ + public boolean matches(Method method, Class targetClass) { + return true; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java new file mode 100644 index 0000000000..5adc4fa539 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/DynamicMethodMatcherPointcut.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2005 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.aop.support; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; + +/** + * Convenient superclass when we want to force subclasses to + * implement MethodMatcher interface, but subclasses + * will want to be pointcuts. The getClassFilter() method can + * be overriden to customize ClassFilter behaviour as well. + * + * @author Rod Johnson + */ +public abstract class DynamicMethodMatcherPointcut extends DynamicMethodMatcher implements Pointcut { + + public ClassFilter getClassFilter() { + return ClassFilter.TRUE; + } + + public final MethodMatcher getMethodMatcher() { + return this; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/ExpressionPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/ExpressionPointcut.java new file mode 100644 index 0000000000..24077191a5 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/ExpressionPointcut.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2006 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.aop.support; + +import org.springframework.aop.Pointcut; + +/** + * Interface to be implemented by pointcuts that use String expressions. + * + * @author Rob Harrop + * @since 2.0 + */ +public interface ExpressionPointcut extends Pointcut { + + /** + * Return the String expression for this pointcut. + */ + String getExpression(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java new file mode 100644 index 0000000000..21b25a85ad --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/IntroductionInfoSupport.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.IntroductionInfo; +import org.springframework.util.ClassUtils; + +/** + * Support for implementations of {@link org.springframework.aop.IntroductionInfo}. + * + *

    Allows subclasses to conveniently add all interfaces from a given object, + * and to suppress interfaces that should not be added. Also allows for querying + * all introduced interfaces. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class IntroductionInfoSupport implements IntroductionInfo, Serializable { + + protected transient Log logger = LogFactory.getLog(getClass()); + + /** Set of interface Classes */ + protected Set publishedInterfaces = new HashSet(); + + /** + * Methods that we know we should implement here: key is Method, value is Boolean. + **/ + private transient Map rememberedMethods = createRememberedMethodMap(); + + + /** + * Suppress the specified interface, which may have been autodetected + * due to the delegate implementing it. Call this method to exclude + * internal interfaces from being visible at the proxy level. + *

    Does nothing if the interface is not implemented by the delegate. + * @param intf the interface to suppress + */ + public void suppressInterface(Class intf) { + this.publishedInterfaces.remove(intf); + } + + public Class[] getInterfaces() { + return (Class[]) this.publishedInterfaces.toArray(new Class[this.publishedInterfaces.size()]); + } + + /** + * Check whether the specified interfaces is a published introduction interface. + * @param intf the interface to check + * @return whether the interface is part of this introduction + */ + public boolean implementsInterface(Class intf) { + for (Iterator it = this.publishedInterfaces.iterator(); it.hasNext();) { + Class pubIntf = (Class) it.next(); + if (intf.isInterface() && intf.isAssignableFrom(pubIntf)) { + return true; + } + } + return false; + } + + /** + * Publish all interfaces that the given delegate implements at the proxy level. + * @param delegate the delegate object + */ + protected void implementInterfacesOnObject(Object delegate) { + this.publishedInterfaces.addAll(ClassUtils.getAllInterfacesAsSet(delegate)); + } + + private Map createRememberedMethodMap() { + return new IdentityHashMap(32); + } + + /** + * Is this method on an introduced interface? + * @param mi the method invocation + * @return whether the invoked method is on an introduced interface + */ + protected final boolean isMethodOnIntroducedInterface(MethodInvocation mi) { + Boolean rememberedResult = (Boolean) this.rememberedMethods.get(mi.getMethod()); + if (rememberedResult != null) { + return rememberedResult.booleanValue(); + } + else { + // Work it out and cache it. + boolean result = implementsInterface(mi.getMethod().getDeclaringClass()); + this.rememberedMethods.put(mi.getMethod(), (result ? Boolean.TRUE : Boolean.FALSE)); + return result; + } + } + + + //--------------------------------------------------------------------- + // Serialization support + //--------------------------------------------------------------------- + + /** + * This method is implemented only to restore the logger. + * We don't make the logger static as that would mean that subclasses + * would use this class's log category. + */ + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + // Rely on default serialization; just initialize state after deserialization. + ois.defaultReadObject(); + + // Initialize transient fields. + this.logger = LogFactory.getLog(getClass()); + this.rememberedMethods = createRememberedMethodMap(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/JdkRegexpMethodPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/JdkRegexpMethodPointcut.java new file mode 100644 index 0000000000..b80d4df696 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/JdkRegexpMethodPointcut.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Regular expression pointcut based on the java.util.regex package. + * Supports the following JavaBean properties: + *

      + *
    • pattern: regular expression for the fully-qualified method names to match + *
    • patterns: alternative property taking a String array of patterns. The result will + * be the union of these patterns. + *
    + * + *

    Note: the regular expressions must be a match. For example, + * .*get.* will match com.mycom.Foo.getBar(). + * get.* will not. + * + * @author Dmitriy Kopylenko + * @author Rob Harrop + * @since 1.1 + */ +public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut { + + /** + * Compiled form of the patterns. + */ + private transient Pattern[] compiledPatterns = new Pattern[0]; + + /** + * Compiled form of the exclusion patterns. + */ + private transient Pattern[] compiledExclusionPatterns = new Pattern[0]; + + + /** + * Initialize {@link Pattern Patterns} from the supplied String[]. + */ + protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException { + this.compiledPatterns = compilePatterns(patterns); + } + + /** + * Returns true if the {@link Pattern} at index patternIndex + * matches the supplied candidate String. + */ + protected boolean matches(String pattern, int patternIndex) { + Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern); + return matcher.matches(); + } + + /** + * Initialize exclusion {@link Pattern Patterns} from the supplied String[]. + */ + protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws IllegalArgumentException { + this.compiledExclusionPatterns = compilePatterns(excludedPatterns); + } + + /** + * Returns true if the exclusion {@link Pattern} at index patternIndex + * matches the supplied candidate String. + */ + protected boolean matchesExclusion(String candidate, int patternIndex) { + Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate); + return matcher.matches(); + } + + /** + * Compiles the supplied String[] into an array of + * {@link Pattern} objects and returns that array. + */ + private Pattern[] compilePatterns(String[] source) { + Pattern[] destination = new Pattern[source.length]; + for (int i = 0; i < source.length; i++) { + destination[i] = Pattern.compile(source[i]); + } + return destination; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/MethodMatchers.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/MethodMatchers.java new file mode 100644 index 0000000000..99f4ddc8b9 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/MethodMatchers.java @@ -0,0 +1,254 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.IntroductionAwareMethodMatcher; +import org.springframework.aop.MethodMatcher; +import org.springframework.util.Assert; + +/** + * Static utility methods for composing + * {@link org.springframework.aop.MethodMatcher MethodMatchers}. + * + *

    A MethodMatcher may be evaluated statically (based on method + * and target class) or need further evaluation dynamically + * (based on arguments at the time of method invocation). + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 11.11.2003 + * @see ClassFilters + * @see Pointcuts + */ +public abstract class MethodMatchers { + + /** + * Match all methods that either (or both) of the given MethodMatchers matches. + * @param mm1 the first MethodMatcher + * @param mm2 the second MethodMatcher + * @return a distinct MethodMatcher that matches all methods that either + * of the given MethodMatchers matches + */ + public static MethodMatcher union(MethodMatcher mm1, MethodMatcher mm2) { + return new UnionMethodMatcher(mm1, mm2); + } + + /** + * Match all methods that either (or both) of the given MethodMatchers matches. + * @param mm1 the first MethodMatcher + * @param cf1 the corresponding ClassFilter for the first MethodMatcher + * @param mm2 the second MethodMatcher + * @param cf2 the corresponding ClassFilter for the second MethodMatcher + * @return a distinct MethodMatcher that matches all methods that either + * of the given MethodMatchers matches + */ + static MethodMatcher union(MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) { + return new ClassFilterAwareUnionMethodMatcher(mm1, cf1, mm2, cf2); + } + + /** + * Match all methods that both of the given MethodMatchers match. + * @param mm1 the first MethodMatcher + * @param mm2 the second MethodMatcher + * @return a distinct MethodMatcher that matches all methods that both + * of the given MethodMatchers match + */ + public static MethodMatcher intersection(MethodMatcher mm1, MethodMatcher mm2) { + return new IntersectionMethodMatcher(mm1, mm2); + } + + /** + * Apply the given MethodMatcher to the given Method, supporting an + * {@link org.springframework.aop.IntroductionAwareMethodMatcher} + * (if applicable). + * @param mm the MethodMatcher to apply (may be an IntroductionAwareMethodMatcher) + * @param method the candidate method + * @param targetClass the target class (may be null, in which case + * the candidate class must be taken to be the method's declaring class) + * @param hasIntroductions true if the object on whose behalf we are + * asking is the subject on one or more introductions; false otherwise + * @return whether or not this method matches statically + */ + public static boolean matches(MethodMatcher mm, Method method, Class targetClass, boolean hasIntroductions) { + Assert.notNull(mm, "MethodMatcher must not be null"); + return ((mm instanceof IntroductionAwareMethodMatcher && + ((IntroductionAwareMethodMatcher) mm).matches(method, targetClass, hasIntroductions)) || + mm.matches(method, targetClass)); + } + + + /** + * MethodMatcher implementation for a union of two given MethodMatchers. + */ + private static class UnionMethodMatcher implements IntroductionAwareMethodMatcher, Serializable { + + private MethodMatcher mm1; + private MethodMatcher mm2; + + public UnionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { + Assert.notNull(mm1, "First MethodMatcher must not be null"); + Assert.notNull(mm2, "Second MethodMatcher must not be null"); + this.mm1 = mm1; + this.mm2 = mm2; + } + + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + return (matchesClass1(targetClass) && MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions)) || + (matchesClass2(targetClass) && MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions)); + } + + public boolean matches(Method method, Class targetClass) { + return (matchesClass1(targetClass) && this.mm1.matches(method, targetClass)) || + (matchesClass2(targetClass) && this.mm2.matches(method, targetClass)); + } + + protected boolean matchesClass1(Class targetClass) { + return true; + } + + protected boolean matchesClass2(Class targetClass) { + return true; + } + + public boolean isRuntime() { + return this.mm1.isRuntime() || this.mm2.isRuntime(); + } + + public boolean matches(Method method, Class targetClass, Object[] args) { + return this.mm1.matches(method, targetClass, args) || this.mm2.matches(method, targetClass, args); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof UnionMethodMatcher)) { + return false; + } + UnionMethodMatcher that = (UnionMethodMatcher) obj; + return (this.mm1.equals(that.mm1) && this.mm2.equals(that.mm2)); + } + + public int hashCode() { + int hashCode = 17; + hashCode = 37 * hashCode + this.mm1.hashCode(); + hashCode = 37 * hashCode + this.mm2.hashCode(); + return hashCode; + } + } + + + /** + * MethodMatcher implementation for a union of two given MethodMatchers, + * supporting an associated ClassFilter per MethodMatcher. + */ + private static class ClassFilterAwareUnionMethodMatcher extends UnionMethodMatcher { + + private final ClassFilter cf1; + private final ClassFilter cf2; + + public ClassFilterAwareUnionMethodMatcher(MethodMatcher mm1, ClassFilter cf1, MethodMatcher mm2, ClassFilter cf2) { + super(mm1, mm2); + this.cf1 = cf1; + this.cf2 = cf2; + } + + protected boolean matchesClass1(Class targetClass) { + return this.cf1.matches(targetClass); + } + + protected boolean matchesClass2(Class targetClass) { + return this.cf2.matches(targetClass); + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ClassFilterAwareUnionMethodMatcher)) { + return false; + } + ClassFilterAwareUnionMethodMatcher that = (ClassFilterAwareUnionMethodMatcher) other; + return (this.cf1.equals(that.cf1) && this.cf2.equals(that.cf2) && super.equals(other)); + } + } + + + /** + * MethodMatcher implementation for an intersection of two given MethodMatchers. + */ + private static class IntersectionMethodMatcher implements IntroductionAwareMethodMatcher, Serializable { + + private MethodMatcher mm1; + private MethodMatcher mm2; + + public IntersectionMethodMatcher(MethodMatcher mm1, MethodMatcher mm2) { + Assert.notNull(mm1, "First MethodMatcher must not be null"); + Assert.notNull(mm2, "Second MethodMatcher must not be null"); + this.mm1 = mm1; + this.mm2 = mm2; + } + + public boolean matches(Method method, Class targetClass, boolean hasIntroductions) { + return MethodMatchers.matches(this.mm1, method, targetClass, hasIntroductions) && + MethodMatchers.matches(this.mm2, method, targetClass, hasIntroductions); + } + + public boolean matches(Method method, Class targetClass) { + return this.mm1.matches(method, targetClass) && this.mm2.matches(method, targetClass); + } + + public boolean isRuntime() { + return this.mm1.isRuntime() || this.mm2.isRuntime(); + } + + public boolean matches(Method method, Class targetClass, Object[] args) { + // Because a dynamic intersection may be composed of a static and dynamic part, + // we must avoid calling the 3-arg matches method on a dynamic matcher, as + // it will probably be an unsupported operation. + boolean aMatches = this.mm1.isRuntime() ? + this.mm1.matches(method, targetClass, args) : this.mm1.matches(method, targetClass); + boolean bMatches = this.mm2.isRuntime() ? + this.mm2.matches(method, targetClass, args) : this.mm2.matches(method, targetClass); + return aMatches && bMatches; + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof IntersectionMethodMatcher)) { + return false; + } + IntersectionMethodMatcher that = (IntersectionMethodMatcher) other; + return (this.mm1.equals(that.mm1) && this.mm2.equals(that.mm2)); + } + + public int hashCode() { + int hashCode = 17; + hashCode = 37 * hashCode + this.mm1.hashCode(); + hashCode = 37 * hashCode + this.mm2.hashCode(); + return hashCode; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java new file mode 100644 index 0000000000..5005f33b09 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcut.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2006 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.aop.support; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.util.ObjectUtils; +import org.springframework.util.PatternMatchUtils; + +/** + * Pointcut bean for simple method name matches, as alternative to regexp patterns. + * Does not handle overloaded methods: all methods *with a given name will be eligible. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @author Rob Harrop + * @since 11.02.2004 + * @see #isMatch + */ +public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable { + + private List mappedNames = new LinkedList(); + + + /** + * Convenience method when we have only a single method name to match. + * Use either this method or setMappedNames, not both. + * @see #setMappedNames + */ + public void setMappedName(String mappedName) { + setMappedNames(new String[] { mappedName }); + } + + /** + * Set the method names defining methods to match. + * Matching will be the union of all these; if any match, + * the pointcut matches. + */ + public void setMappedNames(String[] mappedNames) { + this.mappedNames = new LinkedList(); + if (mappedNames != null) { + for (int i = 0; i < mappedNames.length; i++) { + this.mappedNames.add(mappedNames[i]); + } + } + } + + /** + * Add another eligible method name, in addition to those already named. + * Like the set methods, this method is for use when configuring proxies, + * before a proxy is used. + *

    NB: This method does not work after the proxy is in + * use, as advice chains will be cached. + * @param name name of the additional method that will match + * @return this pointcut to allow for multiple additions in one line + */ + public NameMatchMethodPointcut addMethodName(String name) { + // TODO in a future release, consider a way of letting proxies + // cause advice changed events. + this.mappedNames.add(name); + return this; + } + + + public boolean matches(Method method, Class targetClass) { + for (int i = 0; i < this.mappedNames.size(); i++) { + String mappedName = (String) this.mappedNames.get(i); + if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) { + return true; + } + } + return false; + } + + /** + * Return if the given method name matches the mapped name. + *

    The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, + * as well as direct equality. Can be overridden in subclasses. + * @param methodName the method name of the class + * @param mappedName the name in the descriptor + * @return if the names match + * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String) + */ + protected boolean isMatch(String methodName, String mappedName) { + return PatternMatchUtils.simpleMatch(mappedName, methodName); + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + return (other instanceof NameMatchMethodPointcut && + ObjectUtils.nullSafeEquals(this.mappedNames, ((NameMatchMethodPointcut) other).mappedNames)); + } + + public int hashCode() { + return (this.mappedNames != null ? this.mappedNames.hashCode() : 0); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java new file mode 100644 index 0000000000..4cd21ec070 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/NameMatchMethodPointcutAdvisor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2006 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.aop.support; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.Pointcut; + +/** + * Convenient class for name-match method pointcuts that hold an Advice, + * making them an Advisor. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @see NameMatchMethodPointcut + */ +public class NameMatchMethodPointcutAdvisor extends AbstractGenericPointcutAdvisor { + + private final NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); + + + public NameMatchMethodPointcutAdvisor() { + } + + public NameMatchMethodPointcutAdvisor(Advice advice) { + setAdvice(advice); + } + + + /** + * Set the {@link ClassFilter} to use for this pointcut. + * Default is {@link ClassFilter#TRUE}. + * @see NameMatchMethodPointcut#setClassFilter + */ + public void setClassFilter(ClassFilter classFilter) { + this.pointcut.setClassFilter(classFilter); + } + + /** + * Convenience method when we have only a single method name to match. + * Use either this method or setMappedNames, not both. + * @see #setMappedNames + * @see NameMatchMethodPointcut#setMappedName + */ + public void setMappedName(String mappedName) { + this.pointcut.setMappedName(mappedName); + } + + /** + * Set the method names defining methods to match. + * Matching will be the union of all these; if any match, + * the pointcut matches. + * @see NameMatchMethodPointcut#setMappedNames + */ + public void setMappedNames(String[] mappedNames) { + this.pointcut.setMappedNames(mappedNames); + } + + /** + * Add another eligible method name, in addition to those already named. + * Like the set methods, this method is for use when configuring proxies, + * before a proxy is used. + * @param name name of the additional method that will match + * @return this pointcut to allow for multiple additions in one line + * @see NameMatchMethodPointcut#addMethodName + */ + public NameMatchMethodPointcut addMethodName(String name) { + return this.pointcut.addMethodName(name); + } + + + public Pointcut getPointcut() { + return this.pointcut; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/Pointcuts.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/Pointcuts.java new file mode 100644 index 0000000000..cdeb21e8b9 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/Pointcuts.java @@ -0,0 +1,127 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.util.Assert; + +/** + * Pointcut constants for matching getters and setters, + * and static methods useful for manipulating and evaluating pointcuts. + * These methods are particularly useful for composing pointcuts + * using the union and intersection methods. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public abstract class Pointcuts { + + /** Pointcut matching all bean property setters, in any class */ + public static final Pointcut SETTERS = SetterPointcut.INSTANCE; + + /** Pointcut matching all bean property getters, in any class */ + public static final Pointcut GETTERS = GetterPointcut.INSTANCE; + + + /** + * Match all methods that either (or both) of the given pointcuts matches. + * @param pc1 the first Pointcut + * @param pc2 the second Pointcut + * @return a distinct Pointcut that matches all methods that either + * of the given Pointcuts matches + */ + public static Pointcut union(Pointcut pc1, Pointcut pc2) { + return new ComposablePointcut(pc1).union(pc2); + } + + /** + * Match all methods that both the given pointcuts match. + * @param pc1 the first Pointcut + * @param pc2 the second Pointcut + * @return a distinct Pointcut that matches all methods that both + * of the given Pointcuts match + */ + public static Pointcut intersection(Pointcut pc1, Pointcut pc2) { + return new ComposablePointcut(pc1).intersection(pc2); + } + + /** + * Perform the least expensive check for a pointcut match. + * @param pointcut the pointcut to match + * @param method the candidate method + * @param targetClass the target class + * @param args arguments to the method + * @return whether there's a runtime match + */ + public static boolean matches(Pointcut pointcut, Method method, Class targetClass, Object[] args) { + Assert.notNull(pointcut, "Pointcut must not be null"); + if (pointcut == Pointcut.TRUE) { + return true; + } + if (pointcut.getClassFilter().matches(targetClass)) { + // Only check if it gets past first hurdle. + MethodMatcher mm = pointcut.getMethodMatcher(); + if (mm.matches(method, targetClass)) { + // We may need additional runtime (argument) check. + return (!mm.isRuntime() || mm.matches(method, targetClass, args)); + } + } + return false; + } + + + /** + * Pointcut implementation that matches bean property setters. + */ + private static class SetterPointcut extends StaticMethodMatcherPointcut implements Serializable { + + public static SetterPointcut INSTANCE = new SetterPointcut(); + + public boolean matches(Method method, Class targetClass) { + return method.getName().startsWith("set") && + method.getParameterTypes().length == 1 && + method.getReturnType() == Void.TYPE; + } + + private Object readResolve() { + return INSTANCE; + } + } + + + /** + * Pointcut implementation that matches bean property getters. + */ + private static class GetterPointcut extends StaticMethodMatcherPointcut implements Serializable { + + public static GetterPointcut INSTANCE = new GetterPointcut(); + + public boolean matches(Method method, Class targetClass) { + return method.getName().startsWith("get") && + method.getParameterTypes().length == 0; + } + + private Object readResolve() { + return INSTANCE; + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java new file mode 100644 index 0000000000..2fcded937f --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/RegexpMethodPointcutAdvisor.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.util.ObjectUtils; + +/** + * Convenient class for regexp method pointcuts that hold an Advice, + * making them an {@link org.springframework.aop.Advisor}. + * + *

    Configure this class using the "pattern" and "patterns" + * pass-through properties. These are analogous to the pattern + * and patterns properties of {@link AbstractRegexpMethodPointcut}. + * + *

    Can delegate to any {@link AbstractRegexpMethodPointcut} subclass. + * By default, {@link JdkRegexpMethodPointcut} will be used. To choose + * a specific one, override the {@link #createPointcut} method. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setPattern + * @see #setPatterns + * @see JdkRegexpMethodPointcut + */ +public class RegexpMethodPointcutAdvisor extends AbstractGenericPointcutAdvisor { + + private String[] patterns; + + private AbstractRegexpMethodPointcut pointcut; + + private final Object pointcutMonitor = new SerializableMonitor(); + + + /** + * Create an empty RegexpMethodPointcutAdvisor. + * @see #setPattern + * @see #setPatterns + * @see #setAdvice + */ + public RegexpMethodPointcutAdvisor() { + } + + /** + * Create a RegexpMethodPointcutAdvisor for the given advice. + * The pattern still needs to be specified afterwards. + * @param advice the advice to use + * @see #setPattern + * @see #setPatterns + */ + public RegexpMethodPointcutAdvisor(Advice advice) { + setAdvice(advice); + } + + /** + * Create a RegexpMethodPointcutAdvisor for the given advice. + * @param pattern the pattern to use + * @param advice the advice to use + */ + public RegexpMethodPointcutAdvisor(String pattern, Advice advice) { + setPattern(pattern); + setAdvice(advice); + } + + /** + * Create a RegexpMethodPointcutAdvisor for the given advice. + * @param patterns the patterns to use + * @param advice the advice to use + */ + public RegexpMethodPointcutAdvisor(String[] patterns, Advice advice) { + setPatterns(patterns); + setAdvice(advice); + } + + + /** + * Set the regular expression defining methods to match. + *

    Use either this method or {@link #setPatterns}, not both. + * @see #setPatterns + */ + public void setPattern(String pattern) { + setPatterns(new String[] {pattern}); + } + + /** + * Set the regular expressions defining methods to match. + * To be passed through to the pointcut implementation. + *

    Matching will be the union of all these; if any of the + * patterns matches, the pointcut matches. + * @see AbstractRegexpMethodPointcut#setPatterns + */ + public void setPatterns(String[] patterns) { + this.patterns = patterns; + } + + + /** + * Initialize the singleton Pointcut held within this Advisor. + */ + public Pointcut getPointcut() { + synchronized (this.pointcutMonitor) { + if (this.pointcut == null) { + this.pointcut = createPointcut(); + this.pointcut.setPatterns(this.patterns); + } + return pointcut; + } + } + + /** + * Create the actual pointcut: By default, a {@link JdkRegexpMethodPointcut} + * will be used. + * @return the Pointcut instance (never null) + */ + protected AbstractRegexpMethodPointcut createPointcut() { + return new JdkRegexpMethodPointcut(); + } + + public String toString() { + return getClass().getName() + ": advice [" + getAdvice() + + "], pointcut patterns " + ObjectUtils.nullSafeToString(this.patterns); + } + + + /** + * Empty class used for a serializable monitor object. + */ + private static class SerializableMonitor implements Serializable { + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/RootClassFilter.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/RootClassFilter.java new file mode 100644 index 0000000000..193de1d969 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/RootClassFilter.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2005 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.aop.support; + +import java.io.Serializable; + +import org.springframework.aop.ClassFilter; + +/** + * Simple ClassFilter implementation that passes classes (and optionally subclasses) + * @author Rod Johnson + */ +public class RootClassFilter implements ClassFilter, Serializable { + + private Class clazz; + + // TODO inheritance + + public RootClassFilter(Class clazz) { + this.clazz = clazz; + } + + public boolean matches(Class candidate) { + return clazz.isAssignableFrom(candidate); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java new file mode 100644 index 0000000000..108f999dbb --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcher.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2005 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.aop.support; + +import java.lang.reflect.Method; + +import org.springframework.aop.MethodMatcher; + +/** + * Convenient abstract superclass for static method matchers, which don't care + * about arguments at runtime. + */ +public abstract class StaticMethodMatcher implements MethodMatcher { + + public final boolean isRuntime() { + return false; + } + + public final boolean matches(Method method, Class targetClass, Object[] args) { + // should never be invoked because isRuntime() returns false + throw new UnsupportedOperationException("Illegal MethodMatcher usage"); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcut.java new file mode 100644 index 0000000000..7e96730e37 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcut.java @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2008 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.aop.support; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; + +/** + * Convenient superclass when we want to force subclasses to implement the + * {@link MethodMatcher} interface but subclasses will want to be pointcuts. + * + *

    The {@link #setClassFilter "classFilter"} property can be set to customize + * {@link ClassFilter} behavior. The default is {@link ClassFilter#TRUE}. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut { + + private ClassFilter classFilter = ClassFilter.TRUE; + + + /** + * Set the {@link ClassFilter} to use for this pointcut. + * Default is {@link ClassFilter#TRUE}. + */ + public void setClassFilter(ClassFilter classFilter) { + this.classFilter = classFilter; + } + + public ClassFilter getClassFilter() { + return this.classFilter; + } + + + public final MethodMatcher getMethodMatcher() { + return this; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java new file mode 100644 index 0000000000..7844dce8b3 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/StaticMethodMatcherPointcutAdvisor.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2007 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.aop.support; + +import java.io.Serializable; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.PointcutAdvisor; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +/** + * Convenient base class for Advisors that are also static pointcuts. + * Serializable if Advice and subclass are. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public abstract class StaticMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcut + implements PointcutAdvisor, Ordered, Serializable { + + private int order = Integer.MAX_VALUE; + + private Advice advice; + + + /** + * Create a new StaticMethodMatcherPointcutAdvisor, + * expecting bean-style configuration. + * @see #setAdvice + */ + public StaticMethodMatcherPointcutAdvisor() { + } + + /** + * Create a new StaticMethodMatcherPointcutAdvisor for the given advice. + * @param advice the Advice to use + */ + public StaticMethodMatcherPointcutAdvisor(Advice advice) { + Assert.notNull(advice, "Advice must not be null"); + this.advice = advice; + } + + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return this.order; + } + + public void setAdvice(Advice advice) { + this.advice = advice; + } + + public Advice getAdvice() { + return this.advice; + } + + public boolean isPerInstance() { + return true; + } + + public Pointcut getPointcut() { + return this; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java new file mode 100644 index 0000000000..9136c8974e --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationClassFilter.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2007 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.aop.support.annotation; + +import java.lang.annotation.Annotation; + +import org.springframework.aop.ClassFilter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; + +/** + * Simple ClassFilter that looks for a specific Java 5 annotation + * being present on a class. + * + * @author Juergen Hoeller + * @since 2.0 + * @see AnnotationMatchingPointcut + */ +public class AnnotationClassFilter implements ClassFilter { + + private final Class annotationType; + + private final boolean checkInherited; + + + /** + * Create a new AnnotationClassFilter for the given annotation type. + * @param annotationType the annotation type to look for + */ + public AnnotationClassFilter(Class annotationType) { + this(annotationType, false); + } + + /** + * Create a new AnnotationClassFilter for the given annotation type. + * @param annotationType the annotation type to look for + * @param checkInherited whether to explicitly check the superclasses and + * interfaces for the annotation type as well (even if the annotation type + * is not marked as inherited itself) + */ + public AnnotationClassFilter(Class annotationType, boolean checkInherited) { + Assert.notNull(annotationType, "Annotation type must not be null"); + this.annotationType = annotationType; + this.checkInherited = checkInherited; + } + + + public boolean matches(Class clazz) { + return (this.checkInherited ? + (AnnotationUtils.findAnnotation(clazz, this.annotationType) != null) : + clazz.isAnnotationPresent(this.annotationType)); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java new file mode 100644 index 0000000000..01181525e8 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMatchingPointcut.java @@ -0,0 +1,124 @@ +/* + * Copyright 2002-2007 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.aop.support.annotation; + +import java.lang.annotation.Annotation; + +import org.springframework.aop.ClassFilter; +import org.springframework.aop.MethodMatcher; +import org.springframework.aop.Pointcut; +import org.springframework.util.Assert; + +/** + * Simple Pointcut that looks for a specific Java 5 annotation + * being present on a {@link #forClassAnnotation class} or + * {@link #forMethodAnnotation method}. + * + * @author Juergen Hoeller + * @since 2.0 + * @see AnnotationClassFilter + * @see AnnotationMethodMatcher + */ +public class AnnotationMatchingPointcut implements Pointcut { + + private final ClassFilter classFilter; + + private final MethodMatcher methodMatcher; + + + /** + * Create a new AnnotationMatchingPointcut for the given annotation type. + * @param classAnnotationType the annotation type to look for at the class level + */ + public AnnotationMatchingPointcut(Class classAnnotationType) { + this.classFilter = new AnnotationClassFilter(classAnnotationType); + this.methodMatcher = MethodMatcher.TRUE; + } + + /** + * Create a new AnnotationMatchingPointcut for the given annotation type. + * @param classAnnotationType the annotation type to look for at the class level + * @param checkInherited whether to explicitly check the superclasses and + * interfaces for the annotation type as well (even if the annotation type + * is not marked as inherited itself) + */ + public AnnotationMatchingPointcut(Class classAnnotationType, boolean checkInherited) { + this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited); + this.methodMatcher = MethodMatcher.TRUE; + } + + /** + * Create a new AnnotationMatchingPointcut for the given annotation type. + * @param classAnnotationType the annotation type to look for at the class level + * (can be null) + * @param methodAnnotationType the annotation type to look for at the method level + * (can be null) + */ + public AnnotationMatchingPointcut( + Class classAnnotationType, Class methodAnnotationType) { + + Assert.isTrue((classAnnotationType != null || methodAnnotationType != null), + "Either Class annotation type or Method annotation type needs to be specified (or both)"); + + if (classAnnotationType != null) { + this.classFilter = new AnnotationClassFilter(classAnnotationType); + } + else { + this.classFilter = ClassFilter.TRUE; + } + + if (methodAnnotationType != null) { + this.methodMatcher = new AnnotationMethodMatcher(methodAnnotationType); + } + else { + this.methodMatcher = MethodMatcher.TRUE; + } + } + + + public ClassFilter getClassFilter() { + return this.classFilter; + } + + public MethodMatcher getMethodMatcher() { + return this.methodMatcher; + } + + + /** + * Factory method for an AnnotationMatchingPointcut that matches + * for the specified annotation at the class level. + * @param annotationType the annotation type to look for at the class level + * @return the corresponding AnnotationMatchingPointcut + */ + public static AnnotationMatchingPointcut forClassAnnotation(Class annotationType) { + Assert.notNull(annotationType, "Annotation type must not be null"); + return new AnnotationMatchingPointcut(annotationType); + } + + /** + * Factory method for an AnnotationMatchingPointcut that matches + * for the specified annotation at the method level. + * @param annotationType the annotation type to look for at the method level + * @return the corresponding AnnotationMatchingPointcut + */ + public static AnnotationMatchingPointcut forMethodAnnotation(Class annotationType) { + Assert.notNull(annotationType, "Annotation type must not be null"); + return new AnnotationMatchingPointcut(null, annotationType); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java new file mode 100644 index 0000000000..0f1d1b4bec --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/AnnotationMethodMatcher.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2007 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.aop.support.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.springframework.aop.support.AopUtils; +import org.springframework.aop.support.StaticMethodMatcher; +import org.springframework.util.Assert; + +/** + * Simple MethodMatcher that looks for a specific Java 5 annotation + * being present on a method (checking both the method on the invoked + * interface, if any, and the corresponding method on the target class). + * + * @author Juergen Hoeller + * @since 2.0 + * @see AnnotationMatchingPointcut + */ +public class AnnotationMethodMatcher extends StaticMethodMatcher { + + private final Class annotationType; + + + /** + * Create a new AnnotationClassFilter for the given annotation type. + * @param annotationType the annotation type to look for + */ + public AnnotationMethodMatcher(Class annotationType) { + Assert.notNull(annotationType, "Annotation type must not be null"); + this.annotationType = annotationType; + } + + + public boolean matches(Method method, Class targetClass) { + if (method.isAnnotationPresent(this.annotationType)) { + return true; + } + // The method may be on an interface, so let's check on the target class as well. + Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); + return (specificMethod != method && specificMethod.isAnnotationPresent(this.annotationType)); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/package.html new file mode 100644 index 0000000000..149e008b8d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/annotation/package.html @@ -0,0 +1,7 @@ + + + +Annotation support for AOP pointcuts. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/support/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/support/package.html new file mode 100644 index 0000000000..0b75b87b65 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/support/package.html @@ -0,0 +1,7 @@ + + + +Convenience classes for using Spring's AOP API. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java new file mode 100644 index 0000000000..d86ba4b36f --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractBeanFactoryBasedTargetSource.java @@ -0,0 +1,213 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import java.io.NotSerializableException; +import java.io.ObjectStreamException; +import java.io.Serializable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; + +/** + * Base class for {@link org.springframework.aop.TargetSource} implementations + * that are based on a Spring {@link org.springframework.beans.factory.BeanFactory}, + * delegating to Spring-managed bean instances. + * + *

    Subclasses can create prototype instances or lazily access a + * singleton target, for example. See {@link LazyInitTargetSource} and + * {@link AbstractPrototypeBasedTargetSource}'s subclasses for concrete strategies. + * + *

    BeanFactory-based TargetSources are serializable. This involves + * disconnecting the current target and turning into a {@link SingletonTargetSource}. + * + * @author Juergen Hoeller + * @author Rod Johnson + * @since 1.1.4 + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see LazyInitTargetSource + * @see PrototypeTargetSource + * @see ThreadLocalTargetSource + * @see CommonsPoolTargetSource + */ +public abstract class AbstractBeanFactoryBasedTargetSource + implements TargetSource, BeanFactoryAware, Serializable { + + /** use serialVersionUID from Spring 1.2.7 for interoperability */ + private static final long serialVersionUID = -4721607536018568393L; + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** Name of the target bean we will create on each invocation */ + private String targetBeanName; + + /** Class of the target */ + private Class targetClass; + + /** + * BeanFactory that owns this TargetSource. We need to hold onto this + * reference so that we can create new prototype instances as necessary. + */ + private BeanFactory beanFactory; + + + /** + * Set the name of the target bean in the factory. + *

    The target bean should not be a singleton, else the same instance will + * always be obtained from the factory, resulting in the same behavior as + * provided by {@link SingletonTargetSource}. + * @param targetBeanName name of the target bean in the BeanFactory + * that owns this interceptor + * @see SingletonTargetSource + */ + public void setTargetBeanName(String targetBeanName) { + this.targetBeanName = targetBeanName; + } + + /** + * Return the name of the target bean in the factory. + */ + public String getTargetBeanName() { + return this.targetBeanName; + } + + /** + * Specify the target class explicitly, to avoid any kind of access to the + * target bean (for example, to avoid initialization of a FactoryBean instance). + *

    Default is to detect the type automatically, through a getType + * call on the BeanFactory (or even a full getBean call as fallback). + */ + public void setTargetClass(Class targetClass) { + this.targetClass = targetClass; + } + + /** + * Set the owning BeanFactory. We need to save a reference so that we can + * use the getBean method on every invocation. + */ + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (this.targetBeanName == null) { + throw new IllegalStateException("Property'targetBeanName' is required"); + } + this.beanFactory = beanFactory; + } + + /** + * Return the owning BeanFactory. + */ + public BeanFactory getBeanFactory() { + return this.beanFactory; + } + + + public synchronized Class getTargetClass() { + if (this.targetClass == null && this.beanFactory != null) { + // Determine type of the target bean. + this.targetClass = this.beanFactory.getType(this.targetBeanName); + if (this.targetClass == null) { + if (logger.isTraceEnabled()) { + logger.trace("Getting bean with name '" + this.targetBeanName + "' in order to determine type"); + } + this.targetClass = this.beanFactory.getBean(this.targetBeanName).getClass(); + } + } + return this.targetClass; + } + + public boolean isStatic() { + return false; + } + + public void releaseTarget(Object target) throws Exception { + // Nothing to do here. + } + + + /** + * Copy configuration from the other AbstractBeanFactoryBasedTargetSource object. + * Subclasses should override this if they wish to expose it. + * @param other object to copy configuration from + */ + protected void copyFrom(AbstractBeanFactoryBasedTargetSource other) { + this.targetBeanName = other.targetBeanName; + this.targetClass = other.targetClass; + this.beanFactory = other.beanFactory; + } + + /** + * Replaces this object with a SingletonTargetSource on serialization. + * Protected as otherwise it won't be invoked for subclasses. + * (The writeReplace() method must be visible to the class + * being serialized.) + *

    With this implementation of this method, there is no need to mark + * non-serializable fields in this class or subclasses as transient. + */ + protected Object writeReplace() throws ObjectStreamException { + if (logger.isDebugEnabled()) { + logger.debug("Disconnecting TargetSource [" + this + "]"); + } + try { + // Create disconnected SingletonTargetSource. + return new SingletonTargetSource(getTarget()); + } + catch (Exception ex) { + logger.error("Cannot get target for disconnecting TargetSource [" + this + "]", ex); + throw new NotSerializableException( + "Cannot get target for disconnecting TargetSource [" + this + "]: " + ex); + } + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || !getClass().equals(other.getClass())) { + return false; + } + AbstractBeanFactoryBasedTargetSource otherTargetSource = (AbstractBeanFactoryBasedTargetSource) other; + return (ObjectUtils.nullSafeEquals(this.beanFactory, otherTargetSource.beanFactory) && + ObjectUtils.nullSafeEquals(this.targetBeanName, otherTargetSource.targetBeanName)); + } + + public int hashCode() { + int hashCode = getClass().hashCode(); + hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.beanFactory); + hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.targetBeanName); + return hashCode; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(ClassUtils.getShortName(getClass())); + sb.append(" for target bean '").append(this.targetBeanName).append("'"); + if (this.targetClass != null) { + sb.append(" of type [").append(this.targetClass.getName()).append("]"); + } + return sb.toString(); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractLazyCreationTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractLazyCreationTargetSource.java new file mode 100644 index 0000000000..647a67937e --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractLazyCreationTargetSource.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; + +/** + * {@link org.springframework.aop.TargetSource} implementation that will + * lazily create a user-managed object. + * + *

    Creation of the lazy target object is controlled by the user by implementing + * the {@link #createObject()} method. This TargetSource will invoke + * this method the first time the proxy is accessed. + * + *

    Useful when you need to pass a reference to some dependency to an object + * but you don't actually want the dependency to be created until it is first used. + * A typical scenario for this is a connection to a remote resource. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 1.2.4 + * @see #isInitialized() + * @see #createObject() + */ +public abstract class AbstractLazyCreationTargetSource implements TargetSource { + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** The lazily initialized target object */ + private Object lazyTarget; + + + /** + * Return whether the lazy target object of this TargetSource + * has already been fetched. + */ + public synchronized boolean isInitialized() { + return (this.lazyTarget != null); + } + + /** + * This default implementation returns null if the + * target is null (it is hasn't yet been initialized), + * or the target class if the target has already been initialized. + *

    Subclasses may wish to override this method in order to provide + * a meaningful value when the target is still null. + * @see #isInitialized() + */ + public synchronized Class getTargetClass() { + return (this.lazyTarget != null ? this.lazyTarget.getClass() : null); + } + + public boolean isStatic() { + return false; + } + + /** + * Returns the lazy-initialized target object, + * creating it on-the-fly if it doesn't exist already. + * @see #createObject() + */ + public synchronized Object getTarget() throws Exception { + if (this.lazyTarget == null) { + logger.debug("Initializing lazy target object"); + this.lazyTarget = createObject(); + } + return this.lazyTarget; + } + + public void releaseTarget(Object target) throws Exception { + // nothing to do + } + + + /** + * Subclasses should implement this method to return the lazy initialized object. + * Called the first time the proxy is invoked. + * @return the created object + * @throws Exception if creation failed + */ + protected abstract Object createObject() throws Exception; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java new file mode 100644 index 0000000000..c3f55566e7 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPoolingTargetSource.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.DisposableBean; + +/** + * Abstract base class for pooling {@link org.springframework.aop.TargetSource} + * implementations which maintain a pool of target instances, acquiring and + * releasing a target object from the pool for each method invocation. + * This abstract base class is independent of concrete pooling technology; + * see the subclass {@link CommonsPoolTargetSource} for a concrete example. + * + *

    Subclasses must implement the {@link #getTarget} and + * {@link #releaseTarget} methods based on their chosen object pool. + * The {@link #newPrototypeInstance()} method inherited from + * {@link AbstractPrototypeBasedTargetSource} can be used to create objects + * in order to put them into the pool. + * + *

    Subclasses must also implement some of the monitoring methods from the + * {@link PoolingConfig} interface. The {@link #getPoolingConfigMixin()} method + * makes these stats available on proxied objects through an IntroductionAdvisor. + * + *

    This class implements the {@link org.springframework.beans.factory.DisposableBean} + * interface in order to force subclasses to implement a {@link #destroy()} + * method, closing down their object pool. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #getTarget + * @see #releaseTarget + * @see #destroy + */ +public abstract class AbstractPoolingTargetSource extends AbstractPrototypeBasedTargetSource + implements PoolingConfig, DisposableBean { + + /** The maximum size of the pool */ + private int maxSize = -1; + + + /** + * Set the maximum size of the pool. + * Default is -1, indicating no size limit. + */ + public void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } + + /** + * Return the maximum size of the pool. + */ + public int getMaxSize() { + return this.maxSize; + } + + + public final void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + try { + createPool(); + } + catch (Throwable ex) { + throw new BeanInitializationException("Could not create instance pool for TargetSource", ex); + } + } + + + /** + * Create the pool. + * @throws Exception to avoid placing constraints on pooling APIs + */ + protected abstract void createPool() throws Exception; + + /** + * Acquire an object from the pool. + * @return an object from the pool + * @throws Exception we may need to deal with checked exceptions from pool + * APIs, so we're forgiving with our exception signature + */ + public abstract Object getTarget() throws Exception; + + /** + * Return the given object to the pool. + * @param target object that must have been acquired from the pool + * via a call to getTarget() + * @throws Exception to allow pooling APIs to throw exception + * @see #getTarget + */ + public abstract void releaseTarget(Object target) throws Exception; + + + /** + * Return an IntroductionAdvisor that providing a mixin + * exposing statistics about the pool maintained by this object. + */ + public DefaultIntroductionAdvisor getPoolingConfigMixin() { + DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor(this); + return new DefaultIntroductionAdvisor(dii, PoolingConfig.class); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java new file mode 100644 index 0000000000..2b6b095756 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/AbstractPrototypeBasedTargetSource.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; + +/** + * Base class for dynamic TargetSources that can create new prototype bean + * instances to support a pooling or new-instance-per-invocation strategy. + * + *

    Such TargetSources must run in a BeanFactory, as it needs to call the + * getBean method to create a new prototype instance. + * Therefore, this base class extends {@link AbstractBeanFactoryBasedTargetSource}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see PrototypeTargetSource + * @see ThreadLocalTargetSource + * @see CommonsPoolTargetSource + */ +public abstract class AbstractPrototypeBasedTargetSource extends AbstractBeanFactoryBasedTargetSource { + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + super.setBeanFactory(beanFactory); + + // Check whether the target bean is defined as prototype. + if (!beanFactory.isPrototype(getTargetBeanName())) { + throw new BeanDefinitionStoreException( + "Cannot use prototype-based TargetSource against non-prototype bean with name '" + + getTargetBeanName() + "': instances would not be independent"); + } + } + + /** + * Subclasses should call this method to create a new prototype instance. + * @throws BeansException if bean creation failed + */ + protected Object newPrototypeInstance() throws BeansException { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Creating new instance of bean '" + getTargetBeanName() + "'"); + } + return getBeanFactory().getBean(getTargetBeanName()); + } + + /** + * Subclasses should call this method to destroy an obsolete prototype instance. + * @param target the bean instance to destroy + */ + protected void destroyPrototypeInstance(Object target) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Destroying instance of bean '" + getTargetBeanName() + "'"); + } + if (getBeanFactory() instanceof ConfigurableBeanFactory) { + ((ConfigurableBeanFactory) getBeanFactory()).destroyBean(getTargetBeanName(), target); + } + else if (target instanceof DisposableBean) { + try { + ((DisposableBean) target).destroy(); + } + catch (Throwable ex) { + this.logger.error("Couldn't invoke destroy method of bean with name '" + getTargetBeanName() + "'", ex); + } + } + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java new file mode 100644 index 0000000000..fff391cf50 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/CommonsPoolTargetSource.java @@ -0,0 +1,290 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import org.apache.commons.pool.ObjectPool; +import org.apache.commons.pool.PoolableObjectFactory; +import org.apache.commons.pool.impl.GenericObjectPool; + +import org.springframework.beans.BeansException; +import org.springframework.core.Constants; + +/** + * TargetSource implementation that holds objects in a configurable + * Jakarta Commons Pool. + * + *

    By default, an instance of GenericObjectPool is created. + * Subclasses may change the type of ObjectPool used by + * overriding the createObjectPool() method. + * + *

    Provides many configuration properties mirroring those of the Commons Pool + * GenericObjectPool class; these properties are passed to the + * GenericObjectPool during construction. If creating a subclass of this + * class to change the ObjectPool implementation type, pass in the values + * of configuration properties that are relevant to your chosen implementation. + * + *

    The testOnBorrow, testOnReturn and testWhileIdle + * properties are explictly not mirrored because the implementation of + * PoolableObjectFactory used by this class does not implement + * meaningful validation. All exposed Commons Pool properties use the corresponding + * Commons Pool defaults: for example, + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @see GenericObjectPool + * @see #createObjectPool() + * @see #setMaxSize + * @see #setMaxIdle + * @see #setMinIdle + * @see #setMaxWait + * @see #setTimeBetweenEvictionRunsMillis + * @see #setMinEvictableIdleTimeMillis + */ +public class CommonsPoolTargetSource extends AbstractPoolingTargetSource + implements PoolableObjectFactory { + + private static final Constants constants = new Constants(GenericObjectPool.class); + + + private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE; + + private int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE; + + private long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT; + + private long timeBetweenEvictionRunsMillis = GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; + + private long minEvictableIdleTimeMillis = GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; + + private byte whenExhaustedAction = GenericObjectPool.DEFAULT_WHEN_EXHAUSTED_ACTION; + + /** + * The Jakarta Commons ObjectPool used to pool target objects + */ + private ObjectPool pool; + + + /** + * Create a CommonsPoolTargetSource with default settings. + * Default maximum size of the pool is 8. + * @see #setMaxSize + * @see GenericObjectPool#setMaxActive + */ + public CommonsPoolTargetSource() { + setMaxSize(GenericObjectPool.DEFAULT_MAX_ACTIVE); + } + + /** + * Set the maximum number of idle objects in the pool. + * Default is 8. + * @see GenericObjectPool#setMaxIdle + */ + public void setMaxIdle(int maxIdle) { + this.maxIdle = maxIdle; + } + + /** + * Return the maximum number of idle objects in the pool. + */ + public int getMaxIdle() { + return this.maxIdle; + } + + /** + * Set the minimum number of idle objects in the pool. + * Default is 0. + * @see GenericObjectPool#setMinIdle + */ + public void setMinIdle(int minIdle) { + this.minIdle = minIdle; + } + + /** + * Return the minimum number of idle objects in the pool. + */ + public int getMinIdle() { + return this.minIdle; + } + + /** + * Set the maximum waiting time for fetching an object from the pool. + * Default is -1, waiting forever. + * @see GenericObjectPool#setMaxWait + */ + public void setMaxWait(long maxWait) { + this.maxWait = maxWait; + } + + /** + * Return the maximum waiting time for fetching an object from the pool. + */ + public long getMaxWait() { + return this.maxWait; + } + + /** + * Set the time between eviction runs that check idle objects whether + * they have been idle for too long or have become invalid. + * Default is -1, not performing any eviction. + * @see GenericObjectPool#setTimeBetweenEvictionRunsMillis + */ + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + /** + * Return the time between eviction runs that check idle objects. + */ + public long getTimeBetweenEvictionRunsMillis() { + return this.timeBetweenEvictionRunsMillis; + } + + /** + * Set the minimum time that an idle object can sit in the pool before + * it becomes subject to eviction. Default is 1800000 (30 minutes). + *

    Note that eviction runs need to be performed to take this + * setting into effect. + * @see #setTimeBetweenEvictionRunsMillis + * @see GenericObjectPool#setMinEvictableIdleTimeMillis + */ + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + /** + * Return the minimum time that an idle object can sit in the pool. + */ + public long getMinEvictableIdleTimeMillis() { + return this.minEvictableIdleTimeMillis; + } + + /** + * Set the action to take when the pool is exhausted. Uses the + * constant names defined in Commons Pool's GenericObjectPool class: + * "WHEN_EXHAUSTED_BLOCK", "WHEN_EXHAUSTED_FAIL", "WHEN_EXHAUSTED_GROW". + * @see #setWhenExhaustedAction(byte) + */ + public void setWhenExhaustedActionName(String whenExhaustedActionName) { + setWhenExhaustedAction(constants.asNumber(whenExhaustedActionName).byteValue()); + } + + /** + * Set the action to take when the pool is exhausted. Uses the + * constant values defined in Commons Pool's GenericObjectPool class. + * @see GenericObjectPool#setWhenExhaustedAction(byte) + * @see GenericObjectPool#WHEN_EXHAUSTED_BLOCK + * @see GenericObjectPool#WHEN_EXHAUSTED_FAIL + * @see GenericObjectPool#WHEN_EXHAUSTED_GROW + */ + public void setWhenExhaustedAction(byte whenExhaustedAction) { + this.whenExhaustedAction = whenExhaustedAction; + } + + /** + * Return the action to take when the pool is exhausted. + */ + public byte getWhenExhaustedAction() { + return whenExhaustedAction; + } + + + /** + * Creates and holds an ObjectPool instance. + * @see #createObjectPool() + */ + protected final void createPool() { + logger.debug("Creating Commons object pool"); + this.pool = createObjectPool(); + } + + /** + * Subclasses can override this if they want to return a specific Commons pool. + * They should apply any configuration properties to the pool here. + *

    Default is a GenericObjectPool instance with the given pool size. + * @return an empty Commons ObjectPool. + * @see org.apache.commons.pool.impl.GenericObjectPool + * @see #setMaxSize + */ + protected ObjectPool createObjectPool() { + GenericObjectPool gop = new GenericObjectPool(this); + gop.setMaxActive(getMaxSize()); + gop.setMaxIdle(getMaxIdle()); + gop.setMinIdle(getMinIdle()); + gop.setMaxWait(getMaxWait()); + gop.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis()); + gop.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis()); + gop.setWhenExhaustedAction(getWhenExhaustedAction()); + return gop; + } + + + /** + * Borrow an object from the ObjectPool. + */ + public Object getTarget() throws Exception { + return this.pool.borrowObject(); + } + + /** + * Returns the specified object to the underlying ObjectPool. + */ + public void releaseTarget(Object target) throws Exception { + this.pool.returnObject(target); + } + + public int getActiveCount() throws UnsupportedOperationException { + return this.pool.getNumActive(); + } + + public int getIdleCount() throws UnsupportedOperationException { + return this.pool.getNumIdle(); + } + + + /** + * Closes the underlying ObjectPool when destroying this object. + */ + public void destroy() throws Exception { + logger.debug("Closing Commons ObjectPool"); + this.pool.close(); + } + + + //---------------------------------------------------------------------------- + // Implementation of org.apache.commons.pool.PoolableObjectFactory interface + //---------------------------------------------------------------------------- + + public Object makeObject() throws BeansException { + return newPrototypeInstance(); + } + + public void destroyObject(Object obj) throws Exception { + destroyPrototypeInstance(obj); + } + + public boolean validateObject(Object obj) { + return true; + } + + public void activateObject(Object obj) { + } + + public void passivateObject(Object obj) { + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java new file mode 100644 index 0000000000..c5fe74b167 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/EmptyTargetSource.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import java.io.Serializable; + +import org.springframework.aop.TargetSource; +import org.springframework.util.ObjectUtils; + +/** + * Canonical TargetSource when there is no target + * (or just the target class known), and behavior is supplied + * by interfaces and advisors only. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class EmptyTargetSource implements TargetSource, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = 3680494563553489691L; + + + //--------------------------------------------------------------------- + // Static factory methods + //--------------------------------------------------------------------- + + /** + * The canonical (Singleton) instance of this {@link EmptyTargetSource}. + */ + public static final EmptyTargetSource INSTANCE = new EmptyTargetSource(null, true); + + + /** + * Return an EmptyTargetSource for the given target Class. + * @param targetClass the target Class (may be null) + * @see #getTargetClass() + */ + public static EmptyTargetSource forClass(Class targetClass) { + return forClass(targetClass, true); + } + + /** + * Return an EmptyTargetSource for the given target Class. + * @param targetClass the target Class (may be null) + * @param isStatic whether the TargetSource should be marked as static + * @see #getTargetClass() + */ + public static EmptyTargetSource forClass(Class targetClass, boolean isStatic) { + return (targetClass == null && isStatic ? INSTANCE : new EmptyTargetSource(targetClass, isStatic)); + } + + + //--------------------------------------------------------------------- + // Instance implementation + //--------------------------------------------------------------------- + + private final Class targetClass; + + private final boolean isStatic; + + + /** + * Create a new instance of the {@link EmptyTargetSource} class. + *

    This constructor is private to enforce the + * Singleton pattern / factory method pattern. + * @param targetClass the target class to expose (may be null) + * @param isStatic whether the TargetSource is marked as static + */ + private EmptyTargetSource(Class targetClass, boolean isStatic) { + this.targetClass = targetClass; + this.isStatic = isStatic; + } + + /** + * Always returns the specified target Class, or null if none. + */ + public Class getTargetClass() { + return this.targetClass; + } + + /** + * Always returns true. + */ + public boolean isStatic() { + return this.isStatic; + } + + /** + * Always returns null. + */ + public Object getTarget() { + return null; + } + + /** + * Nothing to release. + */ + public void releaseTarget(Object target) { + } + + + /** + * Returns the canonical instance on deserialization in case + * of no target class, thus protecting the Singleton pattern. + */ + private Object readResolve() { + return (this.targetClass == null && this.isStatic ? INSTANCE : this); + } + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof EmptyTargetSource)) { + return false; + } + EmptyTargetSource otherTs = (EmptyTargetSource) other; + return (ObjectUtils.nullSafeEquals(this.targetClass, otherTs.targetClass) && this.isStatic == otherTs.isStatic); + } + + public int hashCode() { + return EmptyTargetSource.class.hashCode() * 13 + ObjectUtils.nullSafeHashCode(this.targetClass); + } + + public String toString() { + return "EmptyTargetSource: " + + (this.targetClass != null ? "target class [" + this.targetClass.getName() + "]" : "no target class") + + ", " + (this.isStatic ? "static" : "dynamic"); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java new file mode 100644 index 0000000000..05cc10573c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/HotSwappableTargetSource.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import java.io.Serializable; + +import org.springframework.aop.TargetSource; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.aop.TargetSource} implementation that + * caches a local target object, but allows the target to be swapped + * while the application is running. + * + *

    If configuring an object of this class in a Spring IoC container, + * use constructor injection. + * + *

    This TargetSource is serializable if the target is at the time + * of serialization. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class HotSwappableTargetSource implements TargetSource, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = 7497929212653839187L; + + + /** The current target object */ + private Object target; + + + /** + * Create a new HotSwappableTargetSource with the given initial target object. + * @param initialTarget the initial target object + */ + public HotSwappableTargetSource(Object initialTarget) { + Assert.notNull(initialTarget, "Target object must not be null"); + this.target = initialTarget; + } + + + /** + * Return the type of the current target object. + *

    The returned type should usually be constant across all target objects. + */ + public synchronized Class getTargetClass() { + return this.target.getClass(); + } + + public final boolean isStatic() { + return false; + } + + public synchronized Object getTarget() { + return this.target; + } + + public void releaseTarget(Object target) { + // nothing to do + } + + + /** + * Swap the target, returning the old target object. + * @param newTarget the new target object + * @return the old target object + * @throws IllegalArgumentException if the new target is invalid + */ + public synchronized Object swap(Object newTarget) throws IllegalArgumentException { + Assert.notNull(newTarget, "Target object must not be null"); + Object old = this.target; + this.target = newTarget; + return old; + } + + + /** + * Two HotSwappableTargetSources are equal if the current target + * objects are equal. + */ + public boolean equals(Object other) { + return (this == other || (other instanceof HotSwappableTargetSource && + this.target.equals(((HotSwappableTargetSource) other).target))); + } + + public int hashCode() { + return HotSwappableTargetSource.class.hashCode(); + } + + public String toString() { + return "HotSwappableTargetSource for target: " + this.target; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/LazyInitTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/LazyInitTargetSource.java new file mode 100644 index 0000000000..2786e16fba --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/LazyInitTargetSource.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import org.springframework.beans.BeansException; + +/** + * {@link org.springframework.aop.TargetSource} that lazily accesses a + * singleton bean from a {@link org.springframework.beans.factory.BeanFactory}. + * + *

    Useful when a proxy reference is needed on initialization but + * the actual target object should not be initialized until first use. + * When the target bean is defined in an + * {@link org.springframework.context.ApplicationContext} (or a + * BeanFactory that is eagerly pre-instantiating singleton beans) + * it must be marked as "lazy-init" too, else it will be instantiated by said + * ApplicationContext (or BeanFactory) on startup. + *

    For example: + * + *

    + * <bean id="serviceTarget" class="example.MyService" lazy-init="true">
    + *   ...
    + * </bean>
    + *
    + * <bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean">
    + *   <property name="targetSource">
    + *     <bean class="org.springframework.aop.target.LazyInitTargetSource">
    + *       <property name="targetBeanName"><idref local="serviceTarget"/></property>
    + *     </bean>
    + *   </property>
    + * </bean>
    + * + * The "serviceTarget" bean will not get initialized until a method on the + * "service" proxy gets invoked. + * + *

    Subclasses can extend this class and override the {@link #postProcessTargetObject(Object)} to + * perform some additional processing with the target object when it is first loaded. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 1.1.4 + * @see org.springframework.beans.factory.BeanFactory#getBean + * @see #postProcessTargetObject + */ +public class LazyInitTargetSource extends AbstractBeanFactoryBasedTargetSource { + + private Object target; + + + public synchronized Object getTarget() throws BeansException { + if (this.target == null) { + this.target = getBeanFactory().getBean(getTargetBeanName()); + postProcessTargetObject(this.target); + } + return this.target; + } + + /** + * Subclasses may override this method to perform additional processing on + * the target object when it is first loaded. + * @param targetObject the target object that has just been instantiated (and configured) + */ + protected void postProcessTargetObject(Object targetObject) { + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/PoolingConfig.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/PoolingConfig.java new file mode 100644 index 0000000000..4327a2747a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/PoolingConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2005 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.aop.target; + +/** + * Config interface for a pooling target source. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface PoolingConfig { + + /** + * Return the maximum size of the pool. + */ + int getMaxSize(); + + /** + * Return the number of active objects in the pool. + * @throws UnsupportedOperationException if not supported by the pool + */ + int getActiveCount() throws UnsupportedOperationException; + + /** + * Return the number of idle objects in the pool. + * @throws UnsupportedOperationException if not supported by the pool + */ + int getIdleCount() throws UnsupportedOperationException; + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java new file mode 100644 index 0000000000..3b3a2c8554 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/PrototypeTargetSource.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import org.springframework.beans.BeansException; + +/** + * TargetSource that creates a new instance of the target bean for each + * request, destroying each instance on release (after each request). + * Obtains bean instances from its containing + * {@link org.springframework.beans.factory.BeanFactory}. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see #setBeanFactory + * @see #setTargetBeanName + */ +public class PrototypeTargetSource extends AbstractPrototypeBasedTargetSource { + + /** + * Obtain a new prototype instance for every call. + * @see #newPrototypeInstance() + */ + public Object getTarget() throws BeansException { + return newPrototypeInstance(); + } + + /** + * Destroy the given independent instance. + * @see #destroyPrototypeInstance + */ + public void releaseTarget(Object target) { + destroyPrototypeInstance(target); + } + + public String toString() { + return "PrototypeTargetSource for target bean with name '" + getTargetBeanName() + "'"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/SimpleBeanTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/SimpleBeanTargetSource.java new file mode 100644 index 0000000000..656a88088d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/SimpleBeanTargetSource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2007 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.aop.target; + +/** + * Simple {@link org.springframework.aop.TargetSource} implementation, + * freshly obtaining the specified target bean from its containing + * Spring {@link org.springframework.beans.factory.BeanFactory}. + * + *

    Can obtain any kind of target bean: singleton, scoped, or prototype. + * Typically used for scoped beans. + * + * @author Juergen Hoeller + * @since 2.0.3 + */ +public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource { + + public Object getTarget() throws Exception { + return getBeanFactory().getBean(getTargetBeanName()); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java new file mode 100644 index 0000000000..9bde8408f2 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/SingletonTargetSource.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2007 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.aop.target; + +import java.io.Serializable; + +import org.springframework.aop.TargetSource; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Implementation of the {@link org.springframework.aop.TargetSource} interface + * that holds a given object. This is the default implementation of the TargetSource + * interface, as used by the Spring AOP framework. There is usually no need to + * create objects of this class in application code. + * + *

    This class is serializable. However, the actual serializability of a + * SingletonTargetSource will depend on whether the target is serializable. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.aop.framework.AdvisedSupport#setTarget(Object) + */ +public class SingletonTargetSource implements TargetSource, Serializable { + + /** use serialVersionUID from Spring 1.2 for interoperability */ + private static final long serialVersionUID = 9031246629662423738L; + + + /** Target cached and invoked using reflection */ + private final Object target; + + + /** + * Create a new SingletonTargetSource for the given target. + * @param target the target object + */ + public SingletonTargetSource(Object target) { + Assert.notNull(target, "Target object must not be null"); + this.target = target; + } + + + public Class getTargetClass() { + return this.target.getClass(); + } + + public Object getTarget() { + return this.target; + } + + public void releaseTarget(Object target) { + // nothing to do + } + + public boolean isStatic() { + return true; + } + + + /** + * Two invoker interceptors are equal if they have the same target or if the + * targets or the targets are equal. + */ + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SingletonTargetSource)) { + return false; + } + SingletonTargetSource otherTargetSource = (SingletonTargetSource) other; + return this.target.equals(otherTargetSource.target); + } + + /** + * SingletonTargetSource uses the hash code of the target object. + */ + public int hashCode() { + return this.target.hashCode(); + } + + public String toString() { + return "SingletonTargetSource for target object [" + ObjectUtils.identityToString(this.target) + "]"; + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java new file mode 100644 index 0000000000..79f581289c --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSource.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2008 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.aop.target; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.springframework.aop.IntroductionAdvisor; +import org.springframework.aop.support.DefaultIntroductionAdvisor; +import org.springframework.aop.support.DelegatingIntroductionInterceptor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; + +/** + * Alternative to an object pool. This TargetSource uses a threading model in which + * every thread has its own copy of the target. There's no contention for targets. + * Target object creation is kept to a minimum on the running server. + * + *

    Application code is written as to a normal pool; callers can't assume they + * will be dealing with the same instance in invocations in different threads. + * However, state can be relied on during the operations of a single thread: + * for example, if one caller makes repeated calls on the AOP proxy. + * + *

    Cleanup of thread-bound objects is performed on BeanFactory destruction, + * calling their DisposableBean.destroy() method if available. + * Be aware that many thread-bound objects can be around until the application + * actually shuts down. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Rob Harrop + * @see ThreadLocalTargetSourceStats + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ +public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource + implements ThreadLocalTargetSourceStats, DisposableBean { + + /** + * ThreadLocal holding the target associated with the current + * thread. Unlike most ThreadLocals, which are static, this variable + * is meant to be per thread per instance of the ThreadLocalTargetSource class. + */ + private final ThreadLocal targetInThread = new ThreadLocal() { + public String toString() { + return "Thread-local instance of bean '" + getTargetBeanName() + "'"; + } + }; + + /** + * Set of managed targets, enabling us to keep track of the targets we've created. + */ + private final Set targetSet = Collections.synchronizedSet(new HashSet()); + + private int invocationCount; + + private int hitCount; + + + /** + * Implementation of abstract getTarget() method. + * We look for a target held in a ThreadLocal. If we don't find one, + * we create one and bind it to the thread. No synchronization is required. + */ + public Object getTarget() throws BeansException { + ++this.invocationCount; + Object target = this.targetInThread.get(); + if (target == null) { + if (logger.isDebugEnabled()) { + logger.debug("No target for prototype '" + getTargetBeanName() + "' bound to thread: " + + "creating one and binding it to thread '" + Thread.currentThread().getName() + "'"); + } + // Associate target with ThreadLocal. + target = newPrototypeInstance(); + this.targetInThread.set(target); + this.targetSet.add(target); + } + else { + ++this.hitCount; + } + return target; + } + + /** + * Dispose of targets if necessary; clear ThreadLocal. + * @see #destroyPrototypeInstance + */ + public void destroy() { + logger.debug("Destroying ThreadLocalTargetSource bindings"); + synchronized (this.targetSet) { + for (Iterator it = this.targetSet.iterator(); it.hasNext(); ) { + destroyPrototypeInstance(it.next()); + } + this.targetSet.clear(); + } + // Clear ThreadLocal, just in case. + this.targetInThread.set(null); + } + + + public int getInvocationCount() { + return this.invocationCount; + } + + public int getHitCount() { + return this.hitCount; + } + + public int getObjectCount() { + return this.targetSet.size(); + } + + + /** + * Return an introduction advisor mixin that allows the AOP proxy to be + * cast to ThreadLocalInvokerStats. + */ + public IntroductionAdvisor getStatsMixin() { + DelegatingIntroductionInterceptor dii = new DelegatingIntroductionInterceptor(this); + return new DefaultIntroductionAdvisor(dii, ThreadLocalTargetSourceStats.class); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSourceStats.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSourceStats.java new file mode 100644 index 0000000000..7e3a2fa5cf --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/ThreadLocalTargetSourceStats.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2005 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.aop.target; + +/** + * Statistics for a ThreadLocal TargetSource. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public interface ThreadLocalTargetSourceStats { + + /** + * Return the number of client invocations. + */ + int getInvocationCount(); + + /** + * Return the number of hits that were satisfied by a thread-bound object. + */ + int getHitCount(); + + /** + * Return the number of thread-bound objects created. + */ + int getObjectCount(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java new file mode 100644 index 0000000000..04449566cf --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/AbstractRefreshableTargetSource.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2008 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.aop.target.dynamic; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.TargetSource; + +/** + * Abstract {@link org.springframework.aop.TargetSource} implementation that + * wraps a refreshable target object. Subclasses can determine whether a + * refresh is required, and need to provide fresh target objects. + * + *

    Implements the {@link Refreshable} interface in order to allow for + * explicit control over the refresh status. + * + * @author Rod Johnson + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see #requiresRefresh() + * @see #freshTarget() + */ +public abstract class AbstractRefreshableTargetSource implements TargetSource, Refreshable { + + /** Logger available to subclasses */ + protected Log logger = LogFactory.getLog(getClass()); + + protected Object targetObject; + + private long refreshCheckDelay = -1; + + private long lastRefreshCheck = -1; + + private long lastRefreshTime = -1; + + private long refreshCount = 0; + + + /** + * Set the delay between refresh checks, in milliseconds. + * Default is -1, indicating no refresh checks at all. + *

    Note that an actual refresh will only happen when + * {@link #requiresRefresh()} returns true. + */ + public void setRefreshCheckDelay(long refreshCheckDelay) { + this.refreshCheckDelay = refreshCheckDelay; + } + + + public synchronized Class getTargetClass() { + if (this.targetObject == null) { + refresh(); + } + return this.targetObject.getClass(); + } + + /** + * Not static. + */ + public boolean isStatic() { + return false; + } + + public final synchronized Object getTarget() { + if ((refreshCheckDelayElapsed() && requiresRefresh()) || this.targetObject == null) { + refresh(); + } + return this.targetObject; + } + + /** + * No need to release target. + */ + public void releaseTarget(Object object) { + } + + + public final synchronized void refresh() { + logger.debug("Attempting to refresh target"); + + this.targetObject = freshTarget(); + this.refreshCount++; + this.lastRefreshTime = System.currentTimeMillis(); + + logger.debug("Target refreshed successfully"); + } + + public synchronized long getRefreshCount() { + return this.refreshCount; + } + + public synchronized long getLastRefreshTime() { + return this.lastRefreshTime; + } + + + private boolean refreshCheckDelayElapsed() { + if (this.refreshCheckDelay < 0) { + return false; + } + + long currentTimeMillis = System.currentTimeMillis(); + + if (this.lastRefreshCheck < 0 || currentTimeMillis - this.lastRefreshCheck > this.refreshCheckDelay) { + // Going to perform a refresh check - update the timestamp. + this.lastRefreshCheck = currentTimeMillis; + logger.debug("Refresh check delay elapsed - checking whether refresh is required"); + return true; + } + + return false; + } + + + /** + * Determine whether a refresh is required. + * Invoked for each refresh check, after the refresh check delay has elapsed. + *

    The default implementation always returns true, triggering + * a refresh every time the delay has elapsed. To be overridden by subclasses + * with an appropriate check of the underlying target resource. + * @return whether a refresh is required + */ + protected boolean requiresRefresh() { + return true; + } + + /** + * Obtain a fresh target object. + *

    Only invoked if a refresh check has found that a refresh is required + * (that is, {@link #requiresRefresh()} has returned true). + * @return the fresh target object + */ + protected abstract Object freshTarget(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java new file mode 100644 index 0000000000..8fc839d30d --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/BeanFactoryRefreshableTargetSource.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2006 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.aop.target.dynamic; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.Assert; + +/** + * Refreshable TargetSource that fetches fresh target beans from a BeanFactory. + * + *

    Can be subclassed to override requiresRefresh() to suppress + * unnecessary refreshes. By default, a refresh will be performed every time + * the "refreshCheckDelay" has elapsed. + * + * @author Rob Harrop + * @author Rod Johnson + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.0 + * @see org.springframework.beans.factory.BeanFactory + * @see #requiresRefresh() + * @see #setRefreshCheckDelay + */ +public class BeanFactoryRefreshableTargetSource extends AbstractRefreshableTargetSource { + + private final BeanFactory beanFactory; + + private final String beanName; + + + /** + * Create a new BeanFactoryRefreshableTargetSource for the given + * bean factory and bean name. + *

    Note that the passed-in BeanFactory should have an appropriate + * bean definition set up for the given bean name. + * @param beanFactory the BeanFactory to fetch beans from + * @param beanName the name of the target bean + */ + public BeanFactoryRefreshableTargetSource(BeanFactory beanFactory, String beanName) { + Assert.notNull(beanFactory, "BeanFactory is required"); + Assert.notNull(beanName, "Bean name is required"); + this.beanFactory = beanFactory; + this.beanName = beanName; + } + + + /** + * Retrieve a fresh target object. + */ + protected final Object freshTarget() { + return this.obtainFreshBean(this.beanFactory, this.beanName); + } + + /** + * A template method that subclasses may override to provide a + * fresh target object for the given bean factory and bean name. + *

    This default implementation fetches a new target bean + * instance from the bean factory. + * @see org.springframework.beans.factory.BeanFactory#getBean + */ + protected Object obtainFreshBean(BeanFactory beanFactory, String beanName) { + return beanFactory.getBean(beanName); + } + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/Refreshable.java b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/Refreshable.java new file mode 100644 index 0000000000..7d55a0d59a --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/Refreshable.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2006 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.aop.target.dynamic; + +/** + * Interface to be implemented by dynamic target objects, + * which support reloading and optionally polling for updates. + * + * @author Rod Johnson + * @author Rob Harrop + * @since 2.0 + */ +public interface Refreshable { + + /** + * Refresh the underlying target object. + */ + void refresh(); + + /** + * Return the number of actual refreshes since startup. + */ + long getRefreshCount(); + + /** + * Return the last time an actual refresh happened (as timestamp). + */ + long getLastRefreshTime(); + +} diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/package.html new file mode 100644 index 0000000000..bbbc9a55df --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/dynamic/package.html @@ -0,0 +1,7 @@ + + + +Support for AOP-based refreshing of target objects. + + + diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/target/package.html b/org.springframework.aop/src/main/java/org/springframework/aop/target/package.html new file mode 100644 index 0000000000..26f9d79a87 --- /dev/null +++ b/org.springframework.aop/src/main/java/org/springframework/aop/target/package.html @@ -0,0 +1,15 @@ + + + +This package contains implementations of the org.springframework.aop.TargetSource interface. +
    +The simplest implementation is the SingletonTargetSource, used by default in the AOP framework +to wrap a single target instance. This is normally appropriate. + +
    +Other provided implementations include pooling implementations, that provide a target +from a pool for each request, ensuring a single threaded programming model; and a +"prototype" implementation, that uses a new target instance for each invocation. + + + diff --git a/org.springframework.aop/template.mf b/org.springframework.aop/template.mf index c8049413e5..4cf5a82f99 100644 --- a/org.springframework.aop/template.mf +++ b/org.springframework.aop/template.mf @@ -3,4 +3,14 @@ Bundle-Name: Spring AOP Bundle-Vendor: SpringSource Bundle-ManifestVersion: 2 Import-Template: - org.apache.commons.logging;version="[1.1.1, 2.0.0)", + com.jamonapi.*;version="[2.4.0, 3.0.0)";resolution:=optional, + net.sf.cglib.*;version="[2.1.3, 3.0.0)";resolution:=optional, + org.aopalliance.*;version="[1.0.0, 2.0.0)", + org.apache.commons.logging.*;version="[1.1.1, 2.0.0)", + org.apache.commons.pool.*;version="[1.3.0, 2.0.0)";resolution:=optional, + org.aspectj.*;version="[1.5.4, 2.0.0)";resolution:=optional, + org.springframework.beans.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.core.*;version="[2.5.5.A, 2.5.5.A]", + org.springframework.util.*;version="[2.5.5.A, 2.5.5.A]" +Unversioned-Imports: + org.w3c.dom.*