⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 aspectjadviceparameternamediscoverer.java

📁 有关此类编程有心德的高手 希望能够多多给予指教
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
/*
 * 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 <code>null</code>.
 *
 * <p>This class interprets arguments in the following way:
 * <ol>
 * <li>If the first parameter of the method is of type {@link JoinPoint}
 * or {@link ProceedingJoinPoint}, it is assumed to be for passing
 * <code>thisJoinPoint</code> to the advice, and the parameter name will
 * be assigned the value <code>"thisJoinPoint"</code>.</li>
 * <li>If the first parameter of the method is of type
 * <code>JoinPoint.StaticPart</code>, it is assumed to be for passing
 * <code>"thisJoinPointStaticPart"</code> to the advice, and the parameter name
 * will be assigned the value <code>"thisJoinPointStaticPart"</code>.</li>
 * <li>If a {@link #setThrowingName(String) throwingName} has been set, and
 * there are no unbound arguments of type <code>Throwable+</code>, then an
 * {@link IllegalArgumentException} is raised. If there is more than one
 * unbound argument of type <code>Throwable+</code>, then an
 * {@link AmbiguousBindingException} is raised. If there is exactly one
 * unbound argument of type <code>Throwable+</code>, then the corresponding
 * parameter name is assigned the value &lt;throwingName&gt;.</li>
 * <li>If there remain unbound arguments, then the pointcut expression is
 * examined. Let <code>a</code> be the number of annotation-based pointcut
 * expressions (&#64;annotation, &#64;this, &#64;target, &#64;args,
 * &#64;within, &#64;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 <code>a</code> is
 * zero we proceed to the next stage. If <code>a</code> &gt; 1 then an
 * <code>AmbiguousBindingException</code> is raised. If <code>a</code> == 1,
 * and there are no unbound arguments of type <code>Annotation+</code>,
 * then an <code>IllegalArgumentException</code> is raised. if there is
 * exactly one such argument, then the corresponding parameter name is
 * assigned the value from the pointcut expression.</li>
 * <li>If a returningName has been set, and there are no unbound arguments
 * then an <code>IllegalArgumentException</code> is raised. If there is
 * more than one unbound argument then an
 * <code>AmbiguousBindingException</code> is raised. If there is exactly
 * one unbound argument then the corresponding parameter name is assigned
 * the value &lt;returningName&gt;.</li>
 * <li>If there remain unbound arguments, then the pointcut expression is
 * examined once more for <code>this</code>, <code>target</code>, and
 * <code>args</code> 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 <code>args</code>) then an
 * <code>AmbiguousBindingException</code> is raised. If there is exactly
 * one argument of a primitive type, then if exactly one <code>args</code>
 * bound variable was found, we assign the corresponding parameter name
 * the variable name. If there were no <code>args</code> bound variables
 * found an <code>IllegalStateException</code> is raised. If there are
 * multiple <code>args</code> bound variables, an
 * <code>AmbiguousBindingException</code> is raised. At this point, if
 * there remains more than one unbound argument we raise an
 * <code>AmbiguousBindingException</code>. 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
 * <code>this</code>, <code>target</code>, or <code>args</code>, it is
 * assigned as the corresponding parameter name. If there are multiple
 * possibilities, an <code>AmbiguousBindingException</code> is raised.</li>
 * </ol>
 *
 * <p>The behavior on raising an <code>IllegalArgumentException</code> or
 * <code>AmbiguousBindingException</code> 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 <code>getParameterNames(..)</code> method will simply return
 * <code>null</code>. If the {@link #setRaiseExceptions(boolean) raiseExceptions}
 * property is set to <code>true</code>, the conditions will be thrown as
 * <code>IllegalArgumentException</code> and <code>AmbiguousBindingException</code>,
 * respectively.
 *
 * <p>Was that perfectly clear? ;)
 *
 * <p>Short version: If an unambiguous binding can be deduced, then it is.
 * If the advice requirements cannot possibly be satisfied, then <code>null</code>
 * is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
 * property to <code>true</code>, descriptive exceptions will be thrown instead of
 * returning <code>null</code> 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 <code>true</code> if exceptions are to be thrown
	 */
	public void setRaiseExceptions(boolean raiseExceptions) {
		this.raiseExceptions = raiseExceptions;
	}

	/**
	 * If <code>afterReturning</code> 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 <code>afterThrowing</code> 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.
	 * <p>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 <code>null</code>
	 * @throws UnsupportedOperationException if
	 * {@link #setRaiseExceptions(boolean) raiseExceptions} has been set to <code>true</code>
	 */
	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++) {

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -