背景
首先根据一个例子,阐述一下AOP(面向切面编程)使用的背景。
假设有一个业务方法,流程大致如下:
但是这样的代码有两个有待优化的地方,其一,我们希望业务类只专注于业务逻辑的处理,打印log这样的非业务逻辑是不是可以放在别的地方?其二,如果这样的业务方法有很多,每个方法都需要打印log并计算执行时间,岂不是就会出现很多重复代码?
这就是AOP派上用场的地方了。
AOP是怎么工作的呢?首先把以上的非业务逻辑(即打印log、计算执行时间之类的代码)移到别的地方,然后在运行期间,调用到业务方法时,再动态地向业务方法里织入这些被移走的代码。
具体的实现则要用到动态代理技术。
动态代理
Spring使用了JDK动态代理和CGLib动态代理两种代理机制实现AOP。这里暂时只讲JDK动态代理。
JDK动态代理的关键在于Proxy类和InvocationHandler接口。
Proxy的静态方法newProxyInstance()可以创建一个动态代理对象,该方法有三个参数:
1.一个类加载器,用于定义代理对象
2.代理对象需要实现的所有接口
3.一个实现了InvocationHandler接口的类。
这个InvocationHandler接口里只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args)
Proxy类创建的动态代理对象会实现参数2的所有接口,而当我们调用这些接口的任何一个方法时,其实它执行的都是InvocationHandler的invoke方法。注意这个方法里的第二个参数method,我们调用代理对象实现的任何一个方法时,都会把这个方法对应的Method实例传入invoke方法里(java的反射机制)。
举一个例子。
//Study.java
public interface Study {
public void signIn();
}
//Student.java
public class StudentA implements Study {
public void signIn(){
System.out.println("A has signed in.");
}
}
一个学生类,实现了Study接口,其中有一个签到方法。现在我通过Proxy创建一个代理:
StudentA a = new StudentA();
Study proxy = Proxy.newProxyInstance(a.getClass().getClassLoader(),
a.getClass().getInterfaces(),
new MyInvocationHandler(a));
proxy.signIn();
因为代理对象proxy是实现了StudentA所有接口的,因此可以直接调用signIn这个方法。但是前面我们说了,调用的这些方法,其实执行的都是InvocationHandler接口的实现类(这个例子里是MyInvocationHandler)的invoke方法。
这个MyInvocationHandler是我们自定义的一个类,我们可以这样写:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target){
this.target = target;
}
//实现InvocationHandler的invoke方法
public Object invoke(Object proxy, Method method, Object[] args){
Object object = method.invoke(target,args);
return object;
}
}
前面说了,invoke方法的第二个参数method,其实对应的就是我们调用proxy的那个方法,也就signIn。而又因为我们在创建MyInvocationHandler实例时传入了StudentA的实例a,这里调用method.invoke()方法,其实就是调用了a.signIn()。
通过一个代理对象去调用方法,在这个简单的例子里并没有什么意义,但是如果我们套用在最开始的那个流程图上呢?Student类即是我们的业务类,signIn()方法就是我们的业务逻辑代码,而非业务逻辑代码(打印log)我们则写进MyInvocationHandler的invoke类里,变成了这样:
public Object invoke(Object proxy, Method method, Object[] args){
//打印log,省略具体代码
Object object = method.invoke(target,args); //调用业务逻辑代码
//打印log并计算执行时间,省略具体代码
return object;
}
这样一来,我们便通过创建动态代理实现了AOP,不仅将围绕业务逻辑的非业务逻辑移除了出来,还避免了非业务逻辑代码的重复。把非业务逻辑动态地添加到业务类中,这一过程就属于AOP的“织入(Weaving)”。