All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.datalking.aop.aspectj.jadvice.AbstractAspectJAdvice Maven / Gradle / Ivy

package com.github.datalking.aop.aspectj.jadvice;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import com.github.datalking.aop.MethodMatcher;
import com.github.datalking.aop.Pointcut;
import com.github.datalking.aop.ProxyMethodInvocation;
import com.github.datalking.aop.aspectj.AspectInstanceFactory;
import com.github.datalking.aop.aspectj.AspectJAdviceParameterNameDiscoverer;
import com.github.datalking.aop.aspectj.AspectJExpressionPointcut;
import com.github.datalking.aop.aspectj.AspectJPrecedenceInformation;
import com.github.datalking.aop.aspectj.MethodInvocationProceedingJoinPoint;
import com.github.datalking.aop.interceptor.ExposeInvocationInterceptor;
import com.github.datalking.aop.support.ComposablePointcut;
import com.github.datalking.aop.support.MethodMatchers;
import com.github.datalking.aop.support.StaticMethodMatcher;
import com.github.datalking.common.DefaultParameterNameDiscoverer;
import com.github.datalking.common.ParameterNameDiscoverer;
import com.github.datalking.util.Assert;
import com.github.datalking.util.StringUtils;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.weaver.tools.JoinPointMatch;
import org.aspectj.weaver.tools.PointcutParameter;

/**
 * AOP Alliance Advice 抽象类
 */
public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable {


    protected static final String JOIN_POINT_KEY = JoinPoint.class.getName();


    public static JoinPoint currentJoinPoint() {
        MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
        if (!(mi instanceof ProxyMethodInvocation)) {
            throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
        }
        ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
        JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY);
        if (jp == null) {
            jp = new MethodInvocationProceedingJoinPoint(pmi);
            pmi.setUserAttribute(JOIN_POINT_KEY, jp);
        }
        return jp;
    }

    private final Class declaringClass;

    private final String methodName;

    private final Class[] parameterTypes;

    protected transient Method aspectJAdviceMethod;

    private final AspectJExpressionPointcut pointcut;

    private final AspectInstanceFactory aspectInstanceFactory;

    // aspect bean名称,用来区分advice是否来自同一个aspect
    private String aspectName;

    // aspect中advice的声明顺序
    private int declarationOrder;

    private String[] argumentNames = null;

    private String returningName = null;

    private Class discoveredReturningType = Object.class;

    private String throwingName = null;

    private Class discoveredThrowingType = Object.class;

    private Type discoveredReturningGenericType;

    private int joinPointArgumentIndex = -1;

    private int joinPointStaticPartArgumentIndex = -1;

    private Map argumentBindings;

    private boolean argumentsIntrospected = false;

    public AbstractAspectJAdvice(Method aspectJAdviceMethod,
                                 AspectJExpressionPointcut pointcut,
                                 AspectInstanceFactory aspectInstanceFactory) {

        Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
        this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
        this.methodName = aspectJAdviceMethod.getName();
        this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
        this.aspectJAdviceMethod = aspectJAdviceMethod;
        this.pointcut = pointcut;
        this.aspectInstanceFactory = aspectInstanceFactory;
    }


    public final Method getAspectJAdviceMethod() {
        return this.aspectJAdviceMethod;
    }

    public final AspectJExpressionPointcut getPointcut() {
        calculateArgumentBindings();
        return this.pointcut;
    }

    public final Pointcut buildSafePointcut() {
        Pointcut pc = getPointcut();
        MethodMatcher safeMethodMatcher = MethodMatchers.intersection(new AdviceExcludingMethodMatcher(this.aspectJAdviceMethod), pc.getMethodMatcher());
        return new ComposablePointcut(pc.getClassFilter(), safeMethodMatcher);
    }


    public final AspectInstanceFactory getAspectInstanceFactory() {
        return this.aspectInstanceFactory;
    }


//    public final ClassLoader getAspectClassLoader() {
//        return this.aspectInstanceFactory.getAspectClassLoader();
//    }

    //@Override
    public int getOrder() {
        return this.aspectInstanceFactory.getOrder();
    }


    public void setAspectName(String name) {
        this.aspectName = name;
    }

    @Override
    public String getAspectName() {
        return this.aspectName;
    }

    public void setDeclarationOrder(int order) {
        this.declarationOrder = order;
    }

    @Override
    public int getDeclarationOrder() {
        return this.declarationOrder;
    }


    public void setArgumentNames(String argNames) {
        String[] tokens = StringUtils.commaDelimitedListToStringArray(argNames);
        setArgumentNamesFromStringArray(tokens);
    }

    public void setArgumentNamesFromStringArray(String... args) {
        this.argumentNames = new String[args.length];
        for (int i = 0; i < args.length; i++) {
            this.argumentNames[i] = StringUtils.trimWhitespace(args[i]);
            if (!isVariableName(this.argumentNames[i])) {
                throw new IllegalArgumentException("'argumentNames' property of AbstractAspectJAdvice contains an argument name '" +
                        this.argumentNames[i] + "' that is not a valid Java identifier");
            }
        }
        if (this.argumentNames != null) {
            if (this.aspectJAdviceMethod.getParameterTypes().length == this.argumentNames.length + 1) {
                // May need to add implicit join point arg name...
                Class firstArgType = this.aspectJAdviceMethod.getParameterTypes()[0];
                if (firstArgType == JoinPoint.class ||
                        firstArgType == ProceedingJoinPoint.class ||
                        firstArgType == JoinPoint.StaticPart.class) {
                    String[] oldNames = this.argumentNames;
                    this.argumentNames = new String[oldNames.length + 1];
                    this.argumentNames[0] = "THIS_JOIN_POINT";
                    System.arraycopy(oldNames, 0, this.argumentNames, 1, oldNames.length);
                }
            }
        }
    }

    public void setReturningName(String name) {
        throw new UnsupportedOperationException("Only afterReturning advice can be used to bind a return value");
    }


    protected void setReturningNameNoCheck(String name) {
        // name could be a variable or a type...
        if (isVariableName(name)) {
            this.returningName = name;
        } else {
            // assume a type
            try {
                this.discoveredReturningType = Class.forName(name);
            } catch (Throwable ex) {
                throw new IllegalArgumentException("Returning name '" + name +
                        "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " +
                        "Root cause: " + ex);
            }
        }
    }

    protected Class getDiscoveredReturningType() {
        return this.discoveredReturningType;
    }

    protected Type getDiscoveredReturningGenericType() {
        return this.discoveredReturningGenericType;
    }

    public void setThrowingName(String name) {
        throw new UnsupportedOperationException("Only afterThrowing advice can be used to bind a thrown exception");
    }

    protected void setThrowingNameNoCheck(String name) {
        // name could be a variable or a type...
        if (isVariableName(name)) {
            this.throwingName = name;
        } else {
            // assume a type
            try {
                this.discoveredReturningType = Class.forName(name);
            } catch (Throwable ex) {
                throw new IllegalArgumentException("Throwing name '" + name +
                        "' is neither a valid argument name nor the fully-qualified name of a Java type on the classpath. " +
                        "Root cause: " + ex);
            }
        }
    }

    protected Class getDiscoveredThrowingType() {
        return this.discoveredThrowingType;
    }

    private boolean isVariableName(String name) {
        char[] chars = name.toCharArray();
        if (!Character.isJavaIdentifierStart(chars[0])) {
            return false;
        }
        for (int i = 1; i < chars.length; i++) {
            if (!Character.isJavaIdentifierPart(chars[i])) {
                return false;
            }
        }
        return true;
    }


    public synchronized final void calculateArgumentBindings() {
        // The simple case... nothing to bind.
        if (this.argumentsIntrospected || this.parameterTypes.length == 0) {
            return;
        }

        int numUnboundArgs = this.parameterTypes.length;
        Class[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes();
        if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0])) {
            numUnboundArgs--;
        } else if (maybeBindJoinPointStaticPart(parameterTypes[0])) {
            numUnboundArgs--;
        }

        if (numUnboundArgs > 0) {
            // need to bind arguments by name as returned from the pointcut match
            bindArgumentsByName(numUnboundArgs);
        }

        this.argumentsIntrospected = true;
    }

    private boolean maybeBindJoinPoint(Class candidateParameterType) {
        if (JoinPoint.class == candidateParameterType) {
            this.joinPointArgumentIndex = 0;
            return true;
        } else {
            return false;
        }
    }

    private boolean maybeBindProceedingJoinPoint(Class candidateParameterType) {
        if (ProceedingJoinPoint.class == candidateParameterType) {
            if (!supportsProceedingJoinPoint()) {
                throw new IllegalArgumentException("ProceedingJoinPoint is only supported for around advice");
            }
            this.joinPointArgumentIndex = 0;
            return true;
        } else {
            return false;
        }
    }

    protected boolean supportsProceedingJoinPoint() {
        return false;
    }

    private boolean maybeBindJoinPointStaticPart(Class candidateParameterType) {
        if (JoinPoint.StaticPart.class == candidateParameterType) {
            this.joinPointStaticPartArgumentIndex = 0;
            return true;
        } else {
            return false;
        }
    }

    private void bindArgumentsByName(int numArgumentsExpectingToBind) {
        if (this.argumentNames == null) {
            this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod);
        }
        if (this.argumentNames != null) {
            // We have been able to determine the arg names.
            bindExplicitArguments(numArgumentsExpectingToBind);
        } else {
            throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " +
                    "requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " +
                    "the argument names were not specified and could not be discovered.");
        }
    }

    protected ParameterNameDiscoverer createParameterNameDiscoverer() {
        // We need to discover them, or if that fails, guess, and if we can't guess with 100% accuracy, fail.
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        AspectJAdviceParameterNameDiscoverer adviceParameterNameDiscoverer = new AspectJAdviceParameterNameDiscoverer(this.pointcut.getExpression());
        adviceParameterNameDiscoverer.setReturningName(this.returningName);
        adviceParameterNameDiscoverer.setThrowingName(this.throwingName);
        // Last in chain, so if we're called and we fail, that's bad...
        adviceParameterNameDiscoverer.setRaiseExceptions(true);
        discoverer.addDiscoverer(adviceParameterNameDiscoverer);
        return discoverer;
    }

    private void bindExplicitArguments(int numArgumentsLeftToBind) {
        this.argumentBindings = new HashMap<>();

        int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterTypes().length;
        if (this.argumentNames.length != numExpectedArgumentNames) {
            throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames +
                    " arguments to bind by name in advice, but actually found " +
                    this.argumentNames.length + " arguments.");
        }

        // So we match in number...
        int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind;
        for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) {
            this.argumentBindings.put(this.argumentNames[i], i);
        }

        // Check that returning and throwing were in the argument names list if specified, and find the discovered argument types.
        if (this.returningName != null) {
            if (!this.argumentBindings.containsKey(this.returningName)) {
                throw new IllegalStateException("Returning argument name '" + this.returningName +
                        "' was not bound in advice arguments");
            } else {
                Integer index = this.argumentBindings.get(this.returningName);
                this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index];
                this.discoveredReturningGenericType = this.aspectJAdviceMethod.getGenericParameterTypes()[index];
            }
        }
        if (this.throwingName != null) {
            if (!this.argumentBindings.containsKey(this.throwingName)) {
                throw new IllegalStateException("Throwing argument name '" + this.throwingName +
                        "' was not bound in advice arguments");
            } else {
                Integer index = this.argumentBindings.get(this.throwingName);
                this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index];
            }
        }

        // configure the pointcut expression accordingly.
        configurePointcutParameters(argumentIndexOffset);
    }

    private void configurePointcutParameters(int argumentIndexOffset) {
        int numParametersToRemove = argumentIndexOffset;
        if (this.returningName != null) {
            numParametersToRemove++;
        }
        if (this.throwingName != null) {
            numParametersToRemove++;
        }
        String[] pointcutParameterNames = new String[this.argumentNames.length - numParametersToRemove];
        Class[] pointcutParameterTypes = new Class[pointcutParameterNames.length];
        Class[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes();

        int index = 0;
        for (int i = 0; i < this.argumentNames.length; i++) {
            if (i < argumentIndexOffset) {
                continue;
            }
            if (this.argumentNames[i].equals(this.returningName) ||
                    this.argumentNames[i].equals(this.throwingName)) {
                continue;
            }
            pointcutParameterNames[index] = this.argumentNames[i];
            pointcutParameterTypes[index] = methodParameterTypes[i];
            index++;
        }

        this.pointcut.setParameterNames(pointcutParameterNames);
        this.pointcut.setParameterTypes(pointcutParameterTypes);
    }


    protected Object[] argBinding(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable ex) {
        calculateArgumentBindings();

        // AMC start
        Object[] adviceInvocationArgs = new Object[this.parameterTypes.length];
        int numBound = 0;

        if (this.joinPointArgumentIndex != -1) {
            adviceInvocationArgs[this.joinPointArgumentIndex] = jp;
            numBound++;
        } else if (this.joinPointStaticPartArgumentIndex != -1) {
            adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart();
            numBound++;
        }

        if (this.argumentBindings != null && !this.argumentBindings.isEmpty()) {
            // binding from pointcut match
            if (jpMatch != null) {
                PointcutParameter[] parameterBindings = jpMatch.getParameterBindings();
                for (PointcutParameter parameter : parameterBindings) {
                    String name = parameter.getName();
                    Integer index = this.argumentBindings.get(name);
                    adviceInvocationArgs[index] = parameter.getBinding();
                    numBound++;
                }
            }
            // binding from returning clause
            if (this.returningName != null) {
                Integer index = this.argumentBindings.get(this.returningName);
                adviceInvocationArgs[index] = returnValue;
                numBound++;
            }
            // binding from thrown exception
            if (this.throwingName != null) {
                Integer index = this.argumentBindings.get(this.throwingName);
                adviceInvocationArgs[index] = ex;
                numBound++;
            }
        }

        if (numBound != this.parameterTypes.length) {
            throw new IllegalStateException("Required to bind " + this.parameterTypes.length +
                    " arguments, but only bound " + numBound + " (JoinPointMatch " +
                    (jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)");
        }

        return adviceInvocationArgs;
    }


    protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
        return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
    }

    // As above, but in this case we are given the join point.
    protected Object invokeAdviceMethod(JoinPoint jp, JoinPointMatch jpMatch, Object returnValue, Throwable t) throws Throwable {

        return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
    }

    protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
        Object[] actualArgs = args;
        if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
            actualArgs = null;
        }
        try {
            //ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
            // TODO AopUtils.invokeJoinpointUsingReflection
            return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
        } catch (IllegalArgumentException ex) {
            throw new Exception("Mismatch on arguments to advice method [" +
                    this.aspectJAdviceMethod + "]; pointcut expression [" +
                    this.pointcut.getPointcutExpression() + "]", ex);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }

    /**
     * Overridden in around advice to return proceeding join point.
     */
    protected JoinPoint getJoinPoint() {
        return currentJoinPoint();
    }

    /**
     * Get the current join point match at the join point we are being dispatched on.
     */
    protected JoinPointMatch getJoinPointMatch() {
        MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
        if (!(mi instanceof ProxyMethodInvocation)) {
            throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
        }
        return getJoinPointMatch((ProxyMethodInvocation) mi);
    }

    // 注意不能使用JoinPointMatch.getClass().getName()作为key
    // 因为 Spring AOP does all the matching at a join point, and then all the invocations.
    // 应该使用 JoinPointMatch as the key
    protected JoinPointMatch getJoinPointMatch(ProxyMethodInvocation pmi) {
        return (JoinPointMatch) pmi.getUserAttribute(this.pointcut.getExpression());
    }


    @Override
    public String toString() {
        return getClass().getName() + ": advice method [" + this.aspectJAdviceMethod + "]; " +
                "aspect name '" + this.aspectName + "'";
    }

    private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
        inputStream.defaultReadObject();
        try {
            this.aspectJAdviceMethod = this.declaringClass.getMethod(this.methodName, this.parameterTypes);
        } catch (NoSuchMethodException ex) {
            throw new IllegalStateException("Failed to find advice method on deserialization", ex);
        }
    }


    /**
     * 用来排除指定方法的MethodMatcher,如排除静态方法
     */
    private static class AdviceExcludingMethodMatcher extends StaticMethodMatcher {

        private final Method adviceMethod;

        public AdviceExcludingMethodMatcher(Method adviceMethod) {
            this.adviceMethod = adviceMethod;
        }

        @Override
        public boolean matches(Method method, Class targetClass) {
            return !this.adviceMethod.equals(method);
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof AdviceExcludingMethodMatcher)) {
                return false;
            }
            AdviceExcludingMethodMatcher otherMm = (AdviceExcludingMethodMatcher) other;
            return this.adviceMethod.equals(otherMm.adviceMethod);
        }

        @Override
        public int hashCode() {
            return this.adviceMethod.hashCode();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy