大二学生党,理解深度广度不够,敬请谅解。。。。最近在JFinal的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 <i>invocation handler</i> of a proxy instance. * * <p>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 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
:目标对象与代理对象的统一接口,这样使用目标对象的地方就可以使用代理对象RealObject
:目标对象,可以是一个接口,抽象类或具体实现类ProxyObject
:代理对象,通过代理对象的方法调用目标对象的方法 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, int 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, Parameter[] args);
}
对于AroundAdvice
的一个实现类AroundAdviceImpl
,也就是CalculatorImpl
注解具体传入的参数:
public class AroundAdviceImpl implements AroundAdvice {
@Override
public void handle(Class<?> targetObject, Method method, Parameter[] args) {
try {
System.out.println("Before");
//Todo :具体的参数并不是在这里传进来的
//method.invoke(targetObject.newInstance(), args[0], args[1]);
method.invoke(targetObject.newInstance(), 1, 3);
System.out.println("After");
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
}
}
}
method.invoke(targetObject.newInstance(), 1, 3);
就是在调用calculate
,具体为什么,后面再说。AroundAdviceImpl
就是一个Intercepter,在对calculate
之前调用System.out.println("Before");
,在对calculate
之后调用System.out.println("After");
对注解有所了解朋友,明白注解实则是Java反射机制,下面我们就看看注解处理器(Annotation Processor)。
public class Processor {
/** * 调用clazz注解中类的handler()方法 * * @param clazz 存在@AOP()注解的类 */
public static void process(Class<?> clazz) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
AOP aop = method.getAnnotation(AOP.class);
if (aop != null) {
Method targetMethod;
//@AOP()中的类 即 AroundAdviceImpl.class
Class<? extends AroundAdvice> targetClass = aop.value();
Parameter[] parameters = method.getParameters();
try {
targetMethod = targetClass.getDeclaredMethod("handle", Class.class, Method.class, Parameter[].class);
targetMethod.invoke(targetClass.newInstance(), clazz, method, parameters);
System.out.println("Processor " + targetMethod);
System.out.println("Processor " + targetClass);
System.out.println("Processor " + clazz);
System.out.println("Processor " + method);
System.out.println("Processor " + Arrays.toString(parameters));
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) {
e.printStackTrace();
}
}
}
}
}
里面变量这些反射,光说的话是不好明白的。等哈在说,这段代码,先看最终的调用:
public class Main {
public static void main(String args[]) {
Processor.process(CalculatorImpl.class);
}
}
那好,我们运行一哈,看看Processer
里面的参数是啥子意思:
Processor public void aop.AroundAdviceImpl.handle(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Parameter[])
Processor class aop.AroundAdviceImpl
Processor class aop.CalculatorImpl
Processor public void aop.CalculatorImpl.calculate(int,int)
Processor [int arg0, int arg1]
targetMethod
:就是指拦截器的handler
方法targetClass
:就是我们所谓的拦截器AroundAdviceImpl
clazz
:CalculatorImpl
,也就是原始的类,我们希望进行AOP的方法所属的对象method
:CalculatorImpl.calculate(int,int)
,我们希望进行AOP的方法parameters
:CalculatorImpl.calculate(int,int)
的参数所以,在handle(Class<?> targetObject, Method method, Parameter[] args)
方法里面三个参数都是从上面5个参数中传进来的。其类型是:
targetObject
:一个CalculatorImpl
对象,就是我们希望进行AOP的方法所属的对象method
:CalculatorImpl.calculate(int,int)
,我们希望进行AOP的方法args
:CalculatorImpl.calculate(int,int)
的参数这样,一个依靠注解的AOP就简单实现了。有下面几个问题,我觉得不是很妥:
method.invoke(targetObject.newInstance(), 1, 3);
其实真正的参数不是在这里实现的,可以在Processor.process()
这个函数里面传值,然后再通过targetMethod.invoke(targetClass.newInstance(), clazz, method, parameters);
传到handle()
里面。method.invoke()
的第一个参数,应该是method所属的一个实例对象,但是我是传递的时候使用了newInstance()
,我感觉这样不是很合理,应该传被实例化过的对象。Processor
估计是CalculatorImpl
的代理,感觉是,其实我也不是很清楚。。。。。建议大家去看看jFinal的InterceptorManager
+ @Before
的源码,他写的确实好。而且本文写的程序并没有实际的用途。