在后端编程中接触到了拦截器,用户权限的验证,统计一个函数的运行时间,这些都可以拦截器来实现,拦截器是AOP的一种实现形式。觉得这种东西是必不可少的。所以就升入了解一哈,顺便了解一哈设计模式,在将来的编程有大用。
AOP:Aspect-Oriented Programming,可以理解为面向切面编程,是对OOP的一种补充。
首先先看看MVC模式,Model + Controller + View,在熟悉不过,其实在JavaWeb中可以细分为下面几层
注意以下几点:
这时候,AOP就要登场了,它就像一个切面插在这些层面之间,进行你想进行的一系列操作。
首先看一段bad code
public interface Calculator {
void calculate(int a, int b);
}
public class CalculatorImpl implements Calculator {
@Override
public void calculate(int a, int b) {
before();
System.out.println(a + b);
after();
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}
为什么说这些代码写得差呢?把before()
与after()
方法写死在calculate()
里面,如果有许多方法要进行这样的before()``after()
,那每个函数都要改,显然是不可能的(例如统计一个函数运行时间)。
这样,比较简单就是动态代理。
这2个类还是不变的
public interface Calculator {
void calculate(int a, int b);
void calculate2(int a, int b); // 测试用啦,其实都一样
}
public class CalculatorImpl implements Calculator {
@Override
public void calculate(int a, int b) {
System.out.println(a + b);
}
@Override
public void calculate2(int a, int b) {
System.out.println(a + b);
}
}
下面就是重点:
首先看一哈JDK里面的一个接口java.lang.reflect.InvocationHandler
,
进入源码:
/**
* {@code InvocationHandler} is the interface implemented by
* the invocation handler of a proxy instance.
*
* Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* 每一个proxy instance都会与一个invocation handler相关联。
* 当proxy instance调用一个方法时,这个方法就会被相关联的
* invocation handler的invoke()调用
*/
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
我们实现这个接口:
public abstract class AroundAdvice implements InvocationHandler {
private Object targetObject;
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
/**
* @param proxy 被代理的对象(也就是targerObject)
* @param method 被代理的接口的方法
* @param args 被代理的接口的方法的参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
handleBefore(targetObject, method, args);
Object result = method.invoke(targetObject, args);
handleAfter(targetObject, method, args);
return result;
}
public Object getProxy() {
return Proxy.newProxyInstance
(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), //需要实现的接口
this
);
}
public abstract void handleBefore(Object targetObject, Method method, Object[] args);
public abstract void handleAfter(Object targetObject, Method method, Object[] args);
}
注意以下几点:
invoke(Object proxy, Method method, Object[] args)
的每个参数的含义(若觉得我写的不清楚,去看源码)Object result = method.invoke(targetObject, args)
使用反射,调用method
,关于method
到底是那个方法,后文再说。getProxy()
方法,返回一个Proxy
,Proxy.newProxyInstance
为JDK自带的方法。public static Object newProxyInstance
(
ClassLoader loader, // 类加载器
Class>[] interfaces, // 需要实现的接口
InvocationHandler h // 一个InvocationHandler对象
)
throws IllegalArgumentException
最后:
public class AroundAdviceImpl extends AroundAdvice {
@Override
public void handleBefore(Object targetObject, Method method, Object[] args) {
System.out.println("Before");
}
@Override
public void handleAfter(Object targetObject, Method method, Object[] args) {
System.out.println("After");
}
}
调用的时候这样就可以了:
public class Main {
public static void main(String args[]) {
AroundAdvice aop = new AroundAdviceImpl();
aop.setTargetObject(new CalculatorImpl());
Calculator calcImpl = (Calculator) aop.getProxy();
calcImpl.calculate(1, 2);
calcImpl.calculate2(1, 35);
}
}
aop.getProxy()
产生一个Proxy
通过代理调用calculate()
,而不是直接调用Calculator
的calculate()
方法。
输出如下:
Before
3
After
Before
36
After
也就是说AroundAdviceImpl
是Calculator
的代理,对于Calculator
的每个方法都进行一个封装(先输出Before,在输出After)。此时,不就是AOP的雏形吗?插入一个切片在一层模型上面或者下面(这里可不能算一层,只是一个例子)。
当然动态代理也有缺陷:newProxyInstance()
函数第二个参数要是一个接口。(所以有个框架Retrofit就是用的这种技术)
顺便说一哈Jfinal里面的AOP,Jfinal使用Intercepter
来对Controller层和Service层进行切面处理,@Before 与 @Clear 来标识要处理的函数。
AbstractObject
:目标对象与代理对象的统一接口,这样使用目标对象的地方就可以使用代理对象。(上述列子的Calculator
)RealObject
:目标对象,可以是一个接口,抽象类或具体实现类。(上述例子的CalculatorImpl
)ProxyObject
:代理对象,通过代理对象的方法调用目标对象的方法,持有一个RealObject
的引用。(上述例子的AroundAdvice
)这样,我们在使用RealObject
里面的方法的时候,就不会去直接操作RealObject
。由ProcyObject
代表RealObject
来进行操作。(或者说:而是将其委托给ProcyObject
来进行。)(我的理解)
而动态代理,就是在运行的时候,进行代理。而不是在编译的时候就确定了。
Jfinal里面是使用@Before
+ Intercepter
来实现AOP的,下面我们也来简单实现。
AOP
的注解声明(对于注解不熟悉的看注解基本)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AOP {
Class extends AroundAdvice> value();
}
ElementType.METHOD
首先我们看看怎么使用这个注解:
public class CalculatorImpl implements Calculator {
@AOP(AroundAdviceImpl.class)
@Override
public void calculate(int a, double b) {
System.out.println(a + b);
}
}
只需要在calculate
方法上面加上@AOP(AroundAdviceImpl.class)
。参数AroundAdviceImpl.class
就是当调用calculate
方法之前或者之后,先去调用AroundAdviceImpl.class
里面的方法。
前文说到,注解需要的参数是一个AroundAdvice
接口或者其子类。对于AroundAdvice
:
// handler每个参数后面再解释
public interface AroundAdvice {
void handle(Class> targetObject, Method method, Object... args);
}
对于AroundAdvice
的一个实现类AroundAdviceImpl
,也就是CalculatorImpl
注解具体传入的参数:
public class AroundAdviceImpl implements AroundAdvice {
@Override
public void handle(Class> targetObject, Method method, Object... args) {
try {
System.out.println("Before");
method.invoke(targetObject.newInstance(), args);
System.out.println("After");
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
}
}
}
method.invoke(targetObject.newInstance(),args);
就是在调用calculate
,具体为什么,后面再说。AroundAdviceImpl
就是一个Intercepter
,在对calculate
之前调用System.out.println("Before");
,在对calculate
之后调用System.out.println("After");
对注解有所了解朋友,明白注解实则是Java反射机制,下面我们就看看注解处理器(Annotation Processor)。
public class Processor {
/**
* 调用clazz注解中的类的handler()方法
*
* realClass 要被代理的类,存在@AOP()注解的类
* proxyClass 代理类,即AOP注解里面的那个类
* targetMethod 要被代理类调用的方法,加AOP注解的方法
* handleMethod handle方法,即AroundAdvice接口的方法
* args targetMethod的参数
*/
public static void process(Class> realClass, Object... args) {
Method[] methods = realClass.getMethods();
for (Method targetMethod : methods) {
// 得到加上了AOP注解的函数
AOP aop = targetMethod.getAnnotation(AOP.class);
if (aop != null) {
Method handleMethod;
//@AOP()中的类 即 AroundAdviceImpl.class
Class extends AroundAdvice> proxyClass = aop.value();
try {
handleMethod = proxyClass.getDeclaredMethod("handle", Class.class, Method.class, args.getClass());
handleMethod.invoke(proxyClass.newInstance(), realClass, targetMethod, args);
System.out.println("Processor " + handleMethod);
System.out.println("Processor " + proxyClass);
System.out.println("Processor " + realClass);
System.out.println("Processor " + targetMethod);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) {
e.printStackTrace();
}
}
}
}
}
// Run
@Test
public void test() {
Processor.process(CalculatorImpl.class, 12, 3.0);
}
那好,我们运行一哈,看看Processer
里面的参数是啥子意思:
15.0
aop.CalculatorImpl#calculate run in 2ms
Processor public void aop.AroundAdviceImpl.handle(java.lang.Class,java.lang.reflect.Method,java.lang.Object[])
Processor class aop.AroundAdviceImpl
Processor class aop.CalculatorImpl
Processor public void aop.CalculatorImpl.calculate(int,double)
在上面注释就已经解释了各个参数的意义,大家可以去看看。
所以,在handle(Class> targetObject, Method method, Object... args)
方法里面三个参数都是从上面5个参数中传进来的。其类型是:
targetObject
:一个CalculatorImpl
对象,就是我们希望进行AOP的方法所属的对象method
:CalculatorImpl.calculate(int,int)
,我们希望进行AOP的方法args
:CalculatorImpl.calculate(int,int)
的参数AroundAdviceImpl
是CalculatorImpl
的代理类,Processor
是注解处理器。
这样,一个依靠注解的AOP就简单实现了。有下面几个问题,我觉得不是很妥:
targetMethod.invoke(targetClass.newInstance(), clazz, method, parameters);
传到handle()
里面。method.invoke()
的第一个参数,应该是method所属的一个实例对象,但是我是传递的时候使用了newInstance()
,我感觉这样不是很合理,应该传被实例化过的对象。还有一个问题:
Proxy
与Delegate
2中模式不是很清楚。