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