spring MethodReference 源码

  • 2022-08-08
  • 浏览 (322)

spring MethodReference 代码

文件路径:/spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java

/*
 * Copyright 2002-2020 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
 *
 *      https://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.expression.spel.ast;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;

import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ExpressionInvocationTargetException;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.ReflectiveMethodExecutor;
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * Expression language AST node that represents a method reference.
 *
 * @author Andy Clement
 * @author Juergen Hoeller
 * @since 3.0
 */
public class MethodReference extends SpelNodeImpl {

	private final String name;

	private final boolean nullSafe;

	@Nullable
	private String originalPrimitiveExitTypeDescriptor;

	@Nullable
	private volatile CachedMethodExecutor cachedExecutor;


	public MethodReference(boolean nullSafe, String methodName, int startPos, int endPos, SpelNodeImpl... arguments) {
		super(startPos, endPos, arguments);
		this.name = methodName;
		this.nullSafe = nullSafe;
	}


	public final String getName() {
		return this.name;
	}

	@Override
	protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
		Object[] arguments = getArguments(state);
		if (state.getActiveContextObject().getValue() == null) {
			throwIfNotNullSafe(getArgumentTypes(arguments));
			return ValueRef.NullValueRef.INSTANCE;
		}
		return new MethodValueRef(state, arguments);
	}

	@Override
	public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
		EvaluationContext evaluationContext = state.getEvaluationContext();
		Object value = state.getActiveContextObject().getValue();
		TypeDescriptor targetType = state.getActiveContextObject().getTypeDescriptor();
		Object[] arguments = getArguments(state);
		TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments);
		updateExitTypeDescriptor();
		return result;
	}

	private TypedValue getValueInternal(EvaluationContext evaluationContext,
			@Nullable Object value, @Nullable TypeDescriptor targetType, Object[] arguments) {

		List<TypeDescriptor> argumentTypes = getArgumentTypes(arguments);
		if (value == null) {
			throwIfNotNullSafe(argumentTypes);
			return TypedValue.NULL;
		}

		MethodExecutor executorToUse = getCachedExecutor(evaluationContext, value, targetType, argumentTypes);
		if (executorToUse != null) {
			try {
				return executorToUse.execute(evaluationContext, value, arguments);
			}
			catch (AccessException ex) {
				// Two reasons this can occur:
				// 1. the method invoked actually threw a real exception
				// 2. the method invoked was not passed the arguments it expected and
				//    has become 'stale'

				// In the first case we should not retry, in the second case we should see
				// if there is a better suited method.

				// To determine the situation, the AccessException will contain a cause.
				// If the cause is an InvocationTargetException, a user exception was
				// thrown inside the method. Otherwise the method could not be invoked.
				throwSimpleExceptionIfPossible(value, ex);

				// At this point we know it wasn't a user problem so worth a retry if a
				// better candidate can be found.
				this.cachedExecutor = null;
			}
		}

		// either there was no accessor or it no longer existed
		executorToUse = findAccessorForMethod(argumentTypes, value, evaluationContext);
		this.cachedExecutor = new CachedMethodExecutor(
				executorToUse, (value instanceof Class ? (Class<?>) value : null), targetType, argumentTypes);
		try {
			return executorToUse.execute(evaluationContext, value, arguments);
		}
		catch (AccessException ex) {
			// Same unwrapping exception handling as above in above catch block
			throwSimpleExceptionIfPossible(value, ex);
			throw new SpelEvaluationException(getStartPosition(), ex,
					SpelMessage.EXCEPTION_DURING_METHOD_INVOCATION, this.name,
					value.getClass().getName(), ex.getMessage());
		}
	}

	private void throwIfNotNullSafe(List<TypeDescriptor> argumentTypes) {
		if (!this.nullSafe) {
			throw new SpelEvaluationException(getStartPosition(),
					SpelMessage.METHOD_CALL_ON_NULL_OBJECT_NOT_ALLOWED,
					FormatHelper.formatMethodForMessage(this.name, argumentTypes));
		}
	}

	private Object[] getArguments(ExpressionState state) {
		Object[] arguments = new Object[getChildCount()];
		for (int i = 0; i < arguments.length; i++) {
			// Make the root object the active context again for evaluating the parameter expressions
			try {
				state.pushActiveContextObject(state.getScopeRootContextObject());
				arguments[i] = this.children[i].getValueInternal(state).getValue();
			}
			finally {
				state.popActiveContextObject();
			}
		}
		return arguments;
	}

	private List<TypeDescriptor> getArgumentTypes(Object... arguments) {
		List<TypeDescriptor> descriptors = new ArrayList<>(arguments.length);
		for (Object argument : arguments) {
			descriptors.add(TypeDescriptor.forObject(argument));
		}
		return Collections.unmodifiableList(descriptors);
	}

	@Nullable
	private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value,
			@Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) {

		List<MethodResolver> methodResolvers = evaluationContext.getMethodResolvers();
		if (methodResolvers.size() != 1 || !(methodResolvers.get(0) instanceof ReflectiveMethodResolver)) {
			// Not a default ReflectiveMethodResolver - don't know whether caching is valid
			return null;
		}

		CachedMethodExecutor executorToCheck = this.cachedExecutor;
		if (executorToCheck != null && executorToCheck.isSuitable(value, target, argumentTypes)) {
			return executorToCheck.get();
		}
		this.cachedExecutor = null;
		return null;
	}

	private MethodExecutor findAccessorForMethod(List<TypeDescriptor> argumentTypes, Object targetObject,
			EvaluationContext evaluationContext) throws SpelEvaluationException {

		AccessException accessException = null;
		for (MethodResolver methodResolver : evaluationContext.getMethodResolvers()) {
			try {
				MethodExecutor methodExecutor = methodResolver.resolve(
						evaluationContext, targetObject, this.name, argumentTypes);
				if (methodExecutor != null) {
					return methodExecutor;
				}
			}
			catch (AccessException ex) {
				accessException = ex;
				break;
			}
		}

		String method = FormatHelper.formatMethodForMessage(this.name, argumentTypes);
		String className = FormatHelper.formatClassNameForMessage(
				targetObject instanceof Class ? ((Class<?>) targetObject) : targetObject.getClass());
		if (accessException != null) {
			throw new SpelEvaluationException(
					getStartPosition(), accessException, SpelMessage.PROBLEM_LOCATING_METHOD, method, className);
		}
		else {
			throw new SpelEvaluationException(getStartPosition(), SpelMessage.METHOD_NOT_FOUND, method, className);
		}
	}

	/**
	 * Decode the AccessException, throwing a lightweight evaluation exception or,
	 * if the cause was a RuntimeException, throw the RuntimeException directly.
	 */
	private void throwSimpleExceptionIfPossible(Object value, AccessException ex) {
		if (ex.getCause() instanceof InvocationTargetException) {
			Throwable rootCause = ex.getCause().getCause();
			if (rootCause instanceof RuntimeException) {
				throw (RuntimeException) rootCause;
			}
			throw new ExpressionInvocationTargetException(getStartPosition(),
					"A problem occurred when trying to execute method '" + this.name +
					"' on object of type [" + value.getClass().getName() + "]", rootCause);
		}
	}

	private void updateExitTypeDescriptor() {
		CachedMethodExecutor executorToCheck = this.cachedExecutor;
		if (executorToCheck != null && executorToCheck.get() instanceof ReflectiveMethodExecutor) {
			Method method = ((ReflectiveMethodExecutor) executorToCheck.get()).getMethod();
			String descriptor = CodeFlow.toDescriptor(method.getReturnType());
			if (this.nullSafe && CodeFlow.isPrimitive(descriptor)) {
				this.originalPrimitiveExitTypeDescriptor = descriptor;
				this.exitTypeDescriptor = CodeFlow.toBoxedDescriptor(descriptor);
			}
			else {
				this.exitTypeDescriptor = descriptor;
			}
		}
	}

	@Override
	public String toStringAST() {
		StringJoiner sj = new StringJoiner(",", "(", ")");
		for (int i = 0; i < getChildCount(); i++) {
			sj.add(getChild(i).toStringAST());
		}
		return this.name + sj.toString();
	}

	/**
	 * A method reference is compilable if it has been resolved to a reflectively accessible method
	 * and the child nodes (arguments to the method) are also compilable.
	 */
	@Override
	public boolean isCompilable() {
		CachedMethodExecutor executorToCheck = this.cachedExecutor;
		if (executorToCheck == null || executorToCheck.hasProxyTarget() ||
				!(executorToCheck.get() instanceof ReflectiveMethodExecutor)) {
			return false;
		}

		for (SpelNodeImpl child : this.children) {
			if (!child.isCompilable()) {
				return false;
			}
		}

		ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) executorToCheck.get();
		if (executor.didArgumentConversionOccur()) {
			return false;
		}
		Class<?> clazz = executor.getMethod().getDeclaringClass();
		if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) {
			return false;
		}

		return true;
	}

	@Override
	public void generateCode(MethodVisitor mv, CodeFlow cf) {
		CachedMethodExecutor executorToCheck = this.cachedExecutor;
		if (executorToCheck == null || !(executorToCheck.get() instanceof ReflectiveMethodExecutor)) {
			throw new IllegalStateException("No applicable cached executor found: " + executorToCheck);
		}

		ReflectiveMethodExecutor methodExecutor = (ReflectiveMethodExecutor) executorToCheck.get();
		Method method = methodExecutor.getMethod();
		boolean isStaticMethod = Modifier.isStatic(method.getModifiers());
		String descriptor = cf.lastDescriptor();

		Label skipIfNull = null;
		if (descriptor == null && !isStaticMethod) {
			// Nothing on the stack but something is needed
			cf.loadTarget(mv);
		}
		if ((descriptor != null || !isStaticMethod) && this.nullSafe) {
			mv.visitInsn(DUP);
			skipIfNull = new Label();
			Label continueLabel = new Label();
			mv.visitJumpInsn(IFNONNULL, continueLabel);
			CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
			mv.visitJumpInsn(GOTO, skipIfNull);
			mv.visitLabel(continueLabel);
		}
		if (descriptor != null && isStaticMethod) {
			// Something on the stack when nothing is needed
			mv.visitInsn(POP);
		}

		if (CodeFlow.isPrimitive(descriptor)) {
			CodeFlow.insertBoxIfNecessary(mv, descriptor.charAt(0));
		}

		String classDesc;
		if (Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
			classDesc = method.getDeclaringClass().getName().replace('.', '/');
		}
		else {
			Class<?> publicDeclaringClass = methodExecutor.getPublicDeclaringClass();
			Assert.state(publicDeclaringClass != null, "No public declaring class");
			classDesc = publicDeclaringClass.getName().replace('.', '/');
		}

		if (!isStaticMethod && (descriptor == null || !descriptor.substring(1).equals(classDesc))) {
			CodeFlow.insertCheckCast(mv, "L" + classDesc);
		}

		generateCodeForArguments(mv, cf, method, this.children);
		mv.visitMethodInsn((isStaticMethod ? INVOKESTATIC : (method.isDefault() ? INVOKEINTERFACE : INVOKEVIRTUAL)),
				classDesc, method.getName(), CodeFlow.createSignatureDescriptor(method),
				method.getDeclaringClass().isInterface());
		cf.pushDescriptor(this.exitTypeDescriptor);

		if (this.originalPrimitiveExitTypeDescriptor != null) {
			// The output of the accessor will be a primitive but from the block above it might be null,
			// so to have a 'common stack' element at skipIfNull target we need to box the primitive
			CodeFlow.insertBoxIfNecessary(mv, this.originalPrimitiveExitTypeDescriptor);
		}
		if (skipIfNull != null) {
			mv.visitLabel(skipIfNull);
		}
	}


	private class MethodValueRef implements ValueRef {

		private final EvaluationContext evaluationContext;

		@Nullable
		private final Object value;

		@Nullable
		private final TypeDescriptor targetType;

		private final Object[] arguments;

		public MethodValueRef(ExpressionState state, Object[] arguments) {
			this.evaluationContext = state.getEvaluationContext();
			this.value = state.getActiveContextObject().getValue();
			this.targetType = state.getActiveContextObject().getTypeDescriptor();
			this.arguments = arguments;
		}

		@Override
		public TypedValue getValue() {
			TypedValue result = MethodReference.this.getValueInternal(
					this.evaluationContext, this.value, this.targetType, this.arguments);
			updateExitTypeDescriptor();
			return result;
		}

		@Override
		public void setValue(@Nullable Object newValue) {
			throw new IllegalAccessError();
		}

		@Override
		public boolean isWritable() {
			return false;
		}
	}


	private static class CachedMethodExecutor {

		private final MethodExecutor methodExecutor;

		@Nullable
		private final Class<?> staticClass;

		@Nullable
		private final TypeDescriptor target;

		private final List<TypeDescriptor> argumentTypes;

		public CachedMethodExecutor(MethodExecutor methodExecutor, @Nullable Class<?> staticClass,
				@Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) {

			this.methodExecutor = methodExecutor;
			this.staticClass = staticClass;
			this.target = target;
			this.argumentTypes = argumentTypes;
		}

		public boolean isSuitable(Object value, @Nullable TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
			return ((this.staticClass == null || this.staticClass == value) &&
					ObjectUtils.nullSafeEquals(this.target, target) && this.argumentTypes.equals(argumentTypes));
		}

		public boolean hasProxyTarget() {
			return (this.target != null && Proxy.isProxyClass(this.target.getType()));
		}

		public MethodExecutor get() {
			return this.methodExecutor;
		}
	}

}

相关信息

spring 源码目录

相关文章

spring Assign 源码

spring AstUtils 源码

spring BeanReference 源码

spring BooleanLiteral 源码

spring CompoundExpression 源码

spring ConstructorReference 源码

spring Elvis 源码

spring FloatLiteral 源码

spring FormatHelper 源码

spring FunctionReference 源码

0  赞