📄 aspectjadviceparameternamediscoverer.java
字号:
/*
* 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 <throwingName>.</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 (@annotation, @this, @target, @args,
* @within, @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> > 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 <returningName>.</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 + -