001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.aop.framework;
018
019import java.lang.reflect.AccessibleObject;
020import java.lang.reflect.Method;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.aopalliance.intercept.MethodInterceptor;
026import org.aopalliance.intercept.MethodInvocation;
027
028import org.springframework.aop.ProxyMethodInvocation;
029import org.springframework.aop.support.AopUtils;
030import org.springframework.core.BridgeMethodResolver;
031
032/**
033 * Spring's implementation of the AOP Alliance
034 * {@link org.aopalliance.intercept.MethodInvocation} interface,
035 * implementing the extended
036 * {@link org.springframework.aop.ProxyMethodInvocation} interface.
037 *
038 * <p>Invokes the target object using reflection. Subclasses can override the
039 * {@link #invokeJoinpoint()} method to change this behavior, so this is also
040 * a useful base class for more specialized MethodInvocation implementations.
041 *
042 * <p>It is possible to clone an invocation, to invoke {@link #proceed()}
043 * repeatedly (once per clone), using the {@link #invocableClone()} method.
044 * It is also possible to attach custom attributes to the invocation,
045 * using the {@link #setUserAttribute} / {@link #getUserAttribute} methods.
046 *
047 * <p><b>NOTE:</b> This class is considered internal and should not be
048 * directly accessed. The sole reason for it being public is compatibility
049 * with existing framework integrations (e.g. Pitchfork). For any other
050 * purposes, use the {@link ProxyMethodInvocation} interface instead.
051 *
052 * @author Rod Johnson
053 * @author Juergen Hoeller
054 * @author Adrian Colyer
055 * @see #invokeJoinpoint
056 * @see #proceed
057 * @see #invocableClone
058 * @see #setUserAttribute
059 * @see #getUserAttribute
060 */
061public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
062
063        protected final Object proxy;
064
065        protected final Object target;
066
067        protected final Method method;
068
069        protected Object[] arguments;
070
071        private final Class<?> targetClass;
072
073        /**
074         * Lazily initialized map of user-specific attributes for this invocation.
075         */
076        private Map<String, Object> userAttributes;
077
078        /**
079         * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
080         * that need dynamic checks.
081         */
082        protected final List<?> interceptorsAndDynamicMethodMatchers;
083
084        /**
085         * Index from 0 of the current interceptor we're invoking.
086         * -1 until we invoke: then the current interceptor.
087         */
088        private int currentInterceptorIndex = -1;
089
090
091        /**
092         * Construct a new ReflectiveMethodInvocation with the given arguments.
093         * @param proxy the proxy object that the invocation was made on
094         * @param target the target object to invoke
095         * @param method the method to invoke
096         * @param arguments the arguments to invoke the method with
097         * @param targetClass the target class, for MethodMatcher invocations
098         * @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
099         * along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
100         * MethodMatchers included in this struct must already have been found to have matched
101         * as far as was possibly statically. Passing an array might be about 10% faster,
102         * but would complicate the code. And it would work only for static pointcuts.
103         */
104        protected ReflectiveMethodInvocation(
105                        Object proxy, Object target, Method method, Object[] arguments,
106                        Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
107
108                this.proxy = proxy;
109                this.target = target;
110                this.targetClass = targetClass;
111                this.method = BridgeMethodResolver.findBridgedMethod(method);
112                this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
113                this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
114        }
115
116
117        @Override
118        public final Object getProxy() {
119                return this.proxy;
120        }
121
122        @Override
123        public final Object getThis() {
124                return this.target;
125        }
126
127        @Override
128        public final AccessibleObject getStaticPart() {
129                return this.method;
130        }
131
132        /**
133         * Return the method invoked on the proxied interface.
134         * May or may not correspond with a method invoked on an underlying
135         * implementation of that interface.
136         */
137        @Override
138        public final Method getMethod() {
139                return this.method;
140        }
141
142        @Override
143        public final Object[] getArguments() {
144                return (this.arguments != null ? this.arguments : new Object[0]);
145        }
146
147        @Override
148        public void setArguments(Object... arguments) {
149                this.arguments = arguments;
150        }
151
152
153        @Override
154        public Object proceed() throws Throwable {
155                // We start with an index of -1 and increment early.
156                if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
157                        return invokeJoinpoint();
158                }
159
160                Object interceptorOrInterceptionAdvice =
161                                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
162                if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
163                        // Evaluate dynamic method matcher here: static part will already have
164                        // been evaluated and found to match.
165                        InterceptorAndDynamicMethodMatcher dm =
166                                        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
167                        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
168                                return dm.interceptor.invoke(this);
169                        }
170                        else {
171                                // Dynamic matching failed.
172                                // Skip this interceptor and invoke the next in the chain.
173                                return proceed();
174                        }
175                }
176                else {
177                        // It's an interceptor, so we just invoke it: The pointcut will have
178                        // been evaluated statically before this object was constructed.
179                        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
180                }
181        }
182
183        /**
184         * Invoke the joinpoint using reflection.
185         * Subclasses can override this to use custom invocation.
186         * @return the return value of the joinpoint
187         * @throws Throwable if invoking the joinpoint resulted in an exception
188         */
189        protected Object invokeJoinpoint() throws Throwable {
190                return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
191        }
192
193
194        /**
195         * This implementation returns a shallow copy of this invocation object,
196         * including an independent copy of the original arguments array.
197         * <p>We want a shallow copy in this case: We want to use the same interceptor
198         * chain and other object references, but we want an independent value for the
199         * current interceptor index.
200         * @see java.lang.Object#clone()
201         */
202        @Override
203        public MethodInvocation invocableClone() {
204                Object[] cloneArguments = null;
205                if (this.arguments != null) {
206                        // Build an independent copy of the arguments array.
207                        cloneArguments = new Object[this.arguments.length];
208                        System.arraycopy(this.arguments, 0, cloneArguments, 0, this.arguments.length);
209                }
210                return invocableClone(cloneArguments);
211        }
212
213        /**
214         * This implementation returns a shallow copy of this invocation object,
215         * using the given arguments array for the clone.
216         * <p>We want a shallow copy in this case: We want to use the same interceptor
217         * chain and other object references, but we want an independent value for the
218         * current interceptor index.
219         * @see java.lang.Object#clone()
220         */
221        @Override
222        public MethodInvocation invocableClone(Object... arguments) {
223                // Force initialization of the user attributes Map,
224                // for having a shared Map reference in the clone.
225                if (this.userAttributes == null) {
226                        this.userAttributes = new HashMap<String, Object>();
227                }
228
229                // Create the MethodInvocation clone.
230                try {
231                        ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone();
232                        clone.arguments = arguments;
233                        return clone;
234                }
235                catch (CloneNotSupportedException ex) {
236                        throw new IllegalStateException(
237                                        "Should be able to clone object of type [" + getClass() + "]: " + ex);
238                }
239        }
240
241
242        @Override
243        public void setUserAttribute(String key, Object value) {
244                if (value != null) {
245                        if (this.userAttributes == null) {
246                                this.userAttributes = new HashMap<String, Object>();
247                        }
248                        this.userAttributes.put(key, value);
249                }
250                else {
251                        if (this.userAttributes != null) {
252                                this.userAttributes.remove(key);
253                        }
254                }
255        }
256
257        @Override
258        public Object getUserAttribute(String key) {
259                return (this.userAttributes != null ? this.userAttributes.get(key) : null);
260        }
261
262        /**
263         * Return user attributes associated with this invocation.
264         * This method provides an invocation-bound alternative to a ThreadLocal.
265         * <p>This map is initialized lazily and is not used in the AOP framework itself.
266         * @return any user attributes associated with this invocation
267         * (never {@code null})
268         */
269        public Map<String, Object> getUserAttributes() {
270                if (this.userAttributes == null) {
271                        this.userAttributes = new HashMap<String, Object>();
272                }
273                return this.userAttributes;
274        }
275
276
277        @Override
278        public String toString() {
279                // Don't do toString on target, it may be proxied.
280                StringBuilder sb = new StringBuilder("ReflectiveMethodInvocation: ");
281                sb.append(this.method).append("; ");
282                if (this.target == null) {
283                        sb.append("target is null");
284                }
285                else {
286                        sb.append("target is of class [").append(this.target.getClass().getName()).append(']');
287                }
288                return sb.toString();
289        }
290
291}