1 /*
2 * Copyright 2002-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.aop.framework;
18
19 import java.lang.reflect.AccessibleObject;
20 import java.lang.reflect.Method;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25 import org.aopalliance.intercept.MethodInterceptor;
26 import org.aopalliance.intercept.MethodInvocation;
27
28 import org.springframework.aop.ProxyMethodInvocation;
29 import org.springframework.aop.support.AopUtils;
30
31 /**
32 * Spring's implementation of the AOP Alliance
33 * {@link org.aopalliance.intercept.MethodInvocation} interface,
34 * implementing the extended
35 * {@link org.springframework.aop.ProxyMethodInvocation} interface.
36 *
37 * <p>Invokes the target object using reflection. Subclasses can override the
38 * {@link #invokeJoinpoint()} method to change this behavior, so this is also
39 * a useful base class for more specialized MethodInvocation implementations.
40 *
41 * <p>It is possible to clone an invocation, to invoke {@link #proceed()}
42 * repeatedly (once per clone), using the {@link #invocableClone()} method.
43 * It is also possible to attach custom attributes to the invocation,
44 * using the {@link #setUserAttribute} / {@link #getUserAttribute} methods.
45 *
46 * <p><b>NOTE:</b> This class is considered internal and should not be
47 * directly accessed. The sole reason for it being public is compatibility
48 * with existing framework integrations (e.g. Pitchfork). For any other
49 * purposes, use the {@link ProxyMethodInvocation} interface instead.
50 *
51 * @author Rod Johnson
52 * @author Juergen Hoeller
53 * @author Adrian Colyer
54 * @see #invokeJoinpoint
55 * @see #proceed
56 * @see #invocableClone
57 * @see #setUserAttribute
58 * @see #getUserAttribute
59 */
60 public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
61
62 protected final Object proxy;
63
64 protected final Object target;
65
66 protected final Method method;
67
68 protected Object[] arguments;
69
70 private final Class targetClass;
71
72 /**
73 * Lazily initialized map of user-specific attributes for this invocation.
74 */
75 private Map userAttributes;
76
77 /**
78 * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
79 * that need dynamic checks.
80 */
81 protected final List interceptorsAndDynamicMethodMatchers;
82
83 /**
84 * Index from 0 of the current interceptor we're invoking.
85 * -1 until we invoke: then the current interceptor.
86 */
87 private int currentInterceptorIndex = -1;
88
89
90 /**
91 * Construct a new ReflectiveMethodInvocation with the given arguments.
92 * @param proxy the proxy object that the invocation was made on
93 * @param target the target object to invoke
94 * @param method the method to invoke
95 * @param arguments the arguments to invoke the method with
96 * @param targetClass the target class, for MethodMatcher invocations
97 * @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
98 * along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
99 * MethodMatchers included in this struct must already have been found to have matched
100 * as far as was possibly statically. Passing an array might be about 10% faster,
101 * but would complicate the code. And it would work only for static pointcuts.
102 */
103 protected ReflectiveMethodInvocation(
104 Object proxy, Object target, Method method, Object[] arguments,
105 Class targetClass, List interceptorsAndDynamicMethodMatchers) {
106
107 this.proxy = proxy;
108 this.target = target;
109 this.targetClass = targetClass;
110 this.method = method;
111 this.arguments = arguments;
112 this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
113 }
114
115
116 public final Object getProxy() {
117 return this.proxy;
118 }
119
120 public final Object getThis() {
121 return this.target;
122 }
123
124 public final AccessibleObject getStaticPart() {
125 return this.method;
126 }
127
128 /**
129 * Return the method invoked on the proxied interface.
130 * May or may not correspond with a method invoked on an underlying
131 * implementation of that interface.
132 */
133 public final Method getMethod() {
134 return this.method;
135 }
136
137 public final Object[] getArguments() {
138 return (this.arguments != null ? this.arguments : new Object[0]);
139 }
140
141 public void setArguments(Object[] arguments) {
142 this.arguments = arguments;
143 }
144
145
146 public Object proceed() throws Throwable {
147 // We start with an index of -1 and increment early.
148 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
149 return invokeJoinpoint();
150 }
151
152 Object interceptorOrInterceptionAdvice =
153 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
154 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
155 // Evaluate dynamic method matcher here: static part will already have
156 // been evaluated and found to match.
157 InterceptorAndDynamicMethodMatcher dm =
158 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
159 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
160 return dm.interceptor.invoke(this);
161 }
162 else {
163 // Dynamic matching failed.
164 // Skip this interceptor and invoke the next in the chain.
165 return proceed();
166 }
167 }
168 else {
169 // It's an interceptor, so we just invoke it: The pointcut will have
170 // been evaluated statically before this object was constructed.
171 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
172 }
173 }
174
175 /**
176 * Invoke the joinpoint using reflection.
177 * Subclasses can override this to use custom invocation.
178 * @return the return value of the joinpoint
179 * @throws Throwable if invoking the joinpoint resulted in an exception
180 */
181 protected Object invokeJoinpoint() throws Throwable {
182 return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
183 }
184
185
186 /**
187 * This implementation returns a shallow copy of this invocation object,
188 * including an independent copy of the original arguments array.
189 * <p>We want a shallow copy in this case: We want to use the same interceptor
190 * chain and other object references, but we want an independent value for the
191 * current interceptor index.
192 * @see java.lang.Object#clone()
193 */
194 public MethodInvocation invocableClone() {
195 Object[] cloneArguments = null;
196 if (this.arguments != null) {
197 // Build an independent copy of the arguments array.
198 cloneArguments = new Object[this.arguments.length];
199 System.arraycopy(this.arguments, 0, cloneArguments, 0, this.arguments.length);
200 }
201 return invocableClone(cloneArguments);
202 }
203
204 /**
205 * This implementation returns a shallow copy of this invocation object,
206 * using the given arguments array for the clone.
207 * <p>We want a shallow copy in this case: We want to use the same interceptor
208 * chain and other object references, but we want an independent value for the
209 * current interceptor index.
210 * @see java.lang.Object#clone()
211 */
212 public MethodInvocation invocableClone(Object[] arguments) {
213 // Force initialization of the user attributes Map,
214 // for having a shared Map reference in the clone.
215 if (this.userAttributes == null) {
216 this.userAttributes = new HashMap();
217 }
218
219 // Create the MethodInvocation clone.
220 try {
221 ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone();
222 clone.arguments = arguments;
223 return clone;
224 }
225 catch (CloneNotSupportedException ex) {
226 throw new IllegalStateException(
227 "Should be able to clone object of type [" + getClass() + "]: " + ex);
228 }
229 }
230
231
232 public void setUserAttribute(String key, Object value) {
233 if (value != null) {
234 if (this.userAttributes == null) {
235 this.userAttributes = new HashMap();
236 }
237 this.userAttributes.put(key, value);
238 }
239 else {
240 if (this.userAttributes != null) {
241 this.userAttributes.remove(key);
242 }
243 }
244 }
245
246 public Object getUserAttribute(String key) {
247 return (this.userAttributes != null ? this.userAttributes.get(key) : null);
248 }
249
250 /**
251 * Return user attributes associated with this invocation.
252 * This method provides an invocation-bound alternative to a ThreadLocal.
253 * <p>This map is initialized lazily and is not used in the AOP framework itself.
254 * @return any user attributes associated with this invocation
255 * (never <code>null</code>)
256 */
257 public Map getUserAttributes() {
258 if (this.userAttributes == null) {
259 this.userAttributes = new HashMap();
260 }
261 return this.userAttributes;
262 }
263
264
265 public String toString() {
266 // Don't do toString on target, it may be proxied.
267 StringBuffer sb = new StringBuffer("ReflectiveMethodInvocation: ");
268 sb.append(this.method).append("; ");
269 if (this.target == null) {
270 sb.append("target is null");
271 }
272 else {
273 sb.append("target is of class [").append(this.target.getClass().getName()).append(']');
274 }
275 return sb.toString();
276 }
277
278 }