Aop implementation based on JDK dynamic proxy

The JDK’s dynamic proxy is interface-based and has some limitations. You must provide an interface to implement it

Getting started with the JDK dynamic Proxy

First, the proxy class must implement the InvocationHandler interface

A propped class calls a method through the method.invoke() statement inside the method

From this we can implement a utility class like this

Note that although the parameter proxy and instance have the same method, if the first parameter of the Invoke method cannot be passed to proxy, a stack overflow will result.

Call the proxy class invoke method, and the instance is InvocationHandler, so is the recursive call, so will appear the above said Java. Lang. StackOverflowError error.

/ * * * *@param<T> Due to the dynamic proxy nature of the JDK, an implementation interface is required, which is used here for *@seeDynamicProxy#getProxyInstance() type conversion */
public class DynamicProxy<T> implements InvocationHandler {
 // Due to the dynamic proxy nature of the JDK, an implementation interface is required, which is used here for type conversion
    private final Class<T> instanceInterfaceClass;
    // The proxied class
    private final Object instance;
    /** * Each method invoked by a propeted class is called by the method.invoke() statement within the method. * Note that while proxy and instance have the same method, if the first parameter of the invoke method is not passed to proxy, the stack overflows@paramProxy Indicates the current proxy class *@paramMethod Current method *@paramArgs Current method argument list *@returnMethod returns the value *@throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = null;
        try {
            
            res = method.invoke(instance, args);
            
        } catch (IllegalAccessException e) {
           // Handle exceptions
        }
        return rs;
    }

    /** * get the proxy class *@returnThe proxy class * /
    public T getProxyInstance(a){ Class<? > instanceClass = instance.getClass();return (T) Proxy.newProxyInstance(instanceClass.getClassLoader(), instanceClass.getInterfaces(), this);
    }

Copy the code

Finally get the proxy class by instantiating the class and calling the getProxyInstance method

Add a callback for a particular time node

Since the corresponding method we call has a proxy added, we can also add a callback before the method is called, after the call, after the exception is thrown, and after the return

Before a method is called, we can usually use it for parameter validation, or parameter modification, so we can create a mapping like this

Object[]->Object[] Function<Object[],Object[]>

After the call (before the return value), we can normally process the return value, modify it, which means we can create such a mapping

Function<Object,Object>

After an Exception is thrown, we can consume the Exception information, so we can create a mapping Exception ->void

Its callback function (with an interface of) Consumer

Once the value is returned, you can log some information. You could theoretically use the Consumer interface but you would need to create your own entity class

So I choose to make an interface myself

Of course we can’t delegate all methods, so we can add a filter that controls whether methods are enhanced or not

Function<Method,Boolean>

/** * The callback after the return value, including the proxied instance, the proxy class, the current method, the return value, and the argument list */
@FunctionalInterface
public interface AfterResultCallback{
    / * * * *@paramInstance Proxied instance *@paramProxy Current proxy *@paramMethod Enhanced method *@paramRes returns the value *@paramArg parameter *@throws InvocationTargetException
     * @throws IllegalAccessException
     */
    void callback(Object instance, Object proxy, Method method,Object res,Object... arg) throws InvocationTargetException, IllegalAccessException;
}
Copy the code

So the above callback utility class can be rewritten as

/ * * * *@param<T> Due to the dynamic proxy nature of the JDK, an implementation interface is required, which is used here for *@seeDynamicProxy#getProxyInstance() type conversion */
public class DynamicProxy<T> implements InvocationHandler {
    // Exception callback
    private Consumer<Exception> exceptionCallback;
    // A callback on a method call, which can be used to modify or validate the input parameter
    private Function<Object[],Object[]> beforeInvokeCallback;
    // A callback before the method returns a value after execution, which can be modified (without circumventing type detection)
    // The function that returns T cannot be changed to return R.
    private Function<Object,Object> beforeResultCallback;
    // The callback after the return value
    //Object instance, Object proxy, Method method,Object res,Object... arg)
    private AfterResultCallback afterResultCallback;
    // The method intercepts the condition
    private Function<Method,Boolean> methodVerify;

    // Due to the dynamic proxy nature of the JDK, an implementation interface is required, which is used here for type conversion
    private final Class<T> instanceInterfaceClass;
    // The proxied class
    private final Object instance;

    / * * * *@paramProxy Indicates the current proxy class *@paramMethod Current method *@paramArgs Current method argument list *@returnMethod returns the value *@throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // If there is no interception condition, or the condition is not met, execute directly
        if (methodVerify == null| |! methodVerify.apply(method)) {return method.invoke(instance, args);
        }
        Object res = null;
        try {
            // If a callback exists, then call
            if(beforeInvokeCallback ! =null) {
                args = beforeInvokeCallback.apply(args);
            }
            res = method.invoke(instance, args);
            if(beforeResultCallback ! =null){
                res = beforeResultCallback.apply(res);
            }
            if(afterResultCallback ! =null){ afterResultCallback.callback(instance, proxy, method, res, args); }}catch (IllegalAccessException e) {
            if(exceptionCallback ! =null) { exceptionCallback.accept(e); }}return res;
    }

    /** * get the proxy class *@returnThe proxy class * /
    public T getProxyInstance(a){ Class<? > instanceClass = instance.getClass();return (T) Proxy.newProxyInstance(instanceClass.getClassLoader(), instanceClass.getInterfaces(), this);
    }

    / * * * *@paramInstanceInterfaceClass proxy interface *@paramInstance proxied class */
    public DynamicProxy(Class<T> instanceInterfaceClass, Object instance) {
        this.instanceInterfaceClass = instanceInterfaceClass;
        this.instance = instance;
    }

    // All of the following are registered callback functions built with Builder mode
    public DynamicProxy<T> setExceptionCallback(Consumer<Exception> exceptionCallback) {
        this.exceptionCallback = exceptionCallback;
        return this;
    }

    public DynamicProxy<T> setBeforeInvokeCallback(Function<Object[], Object[]> beforeInvokeCallback) {
        this.beforeInvokeCallback = beforeInvokeCallback;
        return this;
    }

    public DynamicProxy<T> setBeforeResultCallback(Function<Object, Object> beforeResultCallback) {
        this.beforeResultCallback = beforeResultCallback;
        return this;
    }

    public DynamicProxy<T> setAfterResultCallback(AfterResultCallback afterResultCallback) {
        this.afterResultCallback = afterResultCallback;
        return this;
    }

    public DynamicProxy<T> setMethodVerify(Function<Method,Boolean> methodVerify) {
        this.methodVerify = methodVerify;
        return this; }}Copy the code

Aop function implementation

Now that we have the dynamic proxy utility class in place, how do we weave enhanced methods into enhanced methods

Section method annotation

Annotations allow us to annotate the callbacks of each stage in the section and map the corresponding methods to the interfaces we have above through reflection

Invoke (method.invoke())

@AfterResult @BeforeInvoke @BeforeResult @ExceptionCallBack

After the return value, before the original method call, after the return value, after the exception thrown

They’re empty annotations, they’re just for annotations, they don’t carry data, so I’ll just give you one example

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterResult {
}
Copy the code
Filter method pattern annotation

In simple terms, only the return value type and method name are controlled

Later, it can be extended to detect method signature

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MethodPatten {
    // Return value type
    Class[] res() default{Object.class};
    // Method name, if empty, pass all methods
    String[] methodName() default {""};
}

Copy the code
Initialize the Aop component

All methods of scanning sections will annotate annotations and methods are established relationships

 // Section method
    private Object aspect;
    private Object instance;
    private Class<T> instanceInterface;
    // The relationship between the section method and the corresponding annotation
    private Map<Class,Method> annotationToMethod;

    / * * *@paramAspect section class *@paramInstance Proxied instance */
    public AOP(Object aspect, Object instance) {
        this.aspect = aspect;
        this.instance = instance;
        this.annotationToMethod = new HashMap<>();
        Method[] methods = aspect.getClass().getMethods();
        // Just get the first annotation as the key and the method as the value
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            if (annotations.length > 0) {// Only the first scanned one is retained
                AnnotationType () must be used, or if getClass is used, the obtained proxy class
                annotationToMethod.putIfAbsent(annotations[0].annotationType(),method); }}}Copy the code
Gets an instance of the method filter interface
// Get the interception condition
    private Function<Method,Boolean> getMethodVerify(a){
        // If no MethodPattern condition is specified, none is intercepted
        MethodPatten patten = aspect.getClass().getAnnotation(MethodPatten.class);
        if (patten == null) {
            return (m) -> false;
        }

        return (m) -> {
            // Verify that the return value of the enhanced method (propped class) meets the condition
            boolean isResPass = false;
            for (Class re : patten.res()) {
                if (re.equals(m.getReturnType())){
                    isResPass = true;
                    break; }}// Verify that the method name of the enhanced method (the propped class) matches the condition
            boolean isMethodNamePass =false;
            for (String s : patten.methodName()) {
                // If the method name specified in the condition annotation is empty, it is allowed directly
                if (s.isEmpty() || s.equals(m.getName())){
                    isMethodNamePass = true;
                    break; }}// All passes are allowed
            return isMethodNamePass && isResPass;
        };
    }

Copy the code
Gets a callback instance before a function call
// The corresponding weaving method must be the same length as the parameter list and must return the same value
    private Function<Object[],Object[]> getBeforeInvokeCallback(){
        Function<Object[],Object[]> res = null;
        // Get the corresponding method
        Method method = annotationToMethod.get(BeforeInvoke.class);
        if (method == null) {return null;
        }

        res = (arg) -> {
            try {
                // Force prevents expansion
                // This method is a section-enhanced method that is called to handle the ARG
                return (Object[]) method.invoke(aspect,(Object)arg);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        };
        return res;
    }
   
Copy the code
Gets an exception throw callback instance
  private Consumer<Exception> getExceptionCallback(a){
        Method method = annotationToMethod.get(ExceptionCallback.class);
        if (method == null) {return null;
        }
        Consumer<Exception> res = (e) -> {
            try {
                method.invoke(aspect, e);
            } catch (IllegalAccessException illegalAccessException) {
                illegalAccessException.printStackTrace();
            } catch(InvocationTargetException invocationTargetException) { invocationTargetException.printStackTrace(); }};return res;
    }
  
Copy the code
Gets the callback before the return value
 private Function<Object,Object> getBeforeResultCallback(a){
        Method method = annotationToMethod.get(BeforeResult.class);
        if (method == null) {return null;
        }
        Function function = (res) -> {
            try {
                return method.invoke(aspect, res);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        };
        return function;
    }
   
Copy the code
The callback after getting the return value
   private AfterResultCallback getAfterResultCallback(a){
        Method method = annotationToMethod.get(AfterResult.class);
        if (method == null) {return null;
        }
        AfterResultCallback afterResultCallback = (proxied,proxy,proxyMethod,res,arg) ->{
            method.invoke(aspect, proxied,proxy,proxyMethod,res,arg);
        };
        return afterResultCallback;
    }

Copy the code
Getting proxy classes
public T getInstance(a){
        // Use the utility class to get the proxy class and register the callback function that returns the proxy class
        DynamicProxy<T> proxy = new DynamicProxy<>(instanceInterface, instance);
        proxy.setMethodVerify(getMethodVerify())
                .setBeforeInvokeCallback(getBeforeInvokeCallback())
                .setExceptionCallback(getExceptionCallback())
                .setBeforeResultCallback(getBeforeResultCallback())
                .setAfterResultCallback(getAfterResultCallback());
        return proxy.getProxyInstance();
    }
Copy the code

Aop functional testing

interface

public interface I {
    Object dosome(Integer integer1,Integer i2);
    default double add(Integer integer1,Integer integer2,Integer i3){
        returninteger1+integer2+i3; }}Copy the code

class

public class Impl implements I {
    @Override
    public Object dosome(Integer integer1,Integer integer2) {
        return(Object) Integer.max(integer1, integer2); }}Copy the code

section

@MethodPatten(res = double.class)
public class aspect{
    @BeforeInvoke
    publicObject[] beforeinvoke(Object... objects){ System.out.println("Parameter list is"+ Arrays.toString(objects));
        return objects;
    }
    @BeforeResult
    public Object before(Object o){
        System.out.println("Double Max");
        return Double.MAX_VALUE;
    }
    @AfterResult
    public void after(Object instance, Object proxy, Method method, Object res, Object... arg){
        System.out.println("instance is "+instance.getClass());
        System.out.println("proxy is "+proxy.getClass());
        System.out.println("method is "+method.getName());
        System.out.println("return value is "+res);
        System.out.println("arg is "+Arrays.toString(arg)); }}Copy the code

The test method

public static void main(String[] args)throws InterruptedException {
        AOP<I> aop = new AOP<>(new aspect(), new Impl());
        I i = aop.getInstance();
        System.out.println(i.add(100.10.2000));
        System.out.println("\n"+i.dosome(100.312));
    }
Copy the code

Console output

You can see that methods that only intercept the return value of double

Double Max instance is class myspring.aop.test. Impl proxy is class com.sun.proxy.$Proxy6
method is add
returnValue is 1.7976931348623157E308 ARg is [100, 10, 2000] 1.7976931348623157E308 312Copy the code