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