一、AOP是什么
AOP,即面向切面编程。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制、异常处理等,封装起来,便于减少系统重复的代码,降低模块之间的耦合度。
AOP注重的是许多解决问题的方法中的共同点。
二、AOP的实现
AOP的实现可以分为两种:1.静态织入 2.动态代理
(1)静态AOP
静态织入就是在编译期,切面直接以字节码的形式编译到目标字节码文件中。这种方式对系统的性能没有影响,但是灵活性不够。
(2)动态AOP
为了实现源码组成无关性,AOP往往通过预编译方式(如AspectJ)和运行期动态代理模式(如Spring AOP)实现。
spring动态AOP有两种方式:
- JDK动态代理:通过反射和动态编译实现
- cglib动态代理:通过修改字节码来实现代理
这里我们主要讨论JDK动态代理的方式。
JDK动态代理的两个核心就是InvokationHandler和Proxy,下面我用一个简单的例子来说明JDK动态代理的实现。
由于JDK动态代理只能创建指定接口的动态代理,所以下面先提供一个Dog接口:
interface Dog {
// info()方法声明
public void info();
// run()方法声明
public void run();
}
然后为接口Dog创建一个实现方法:
class GunDog implements Dog {
// info方法实现,仅仅打印一个字符串
@Override
public void info() {
System.out.println("我是一只猎狗");
}
// run方法实现,仅仅打印一个字符串
@Override
public void run() {
System.out.println("我奔跑迅速");
}
}
在DogUtil中创建两个通用的方法(公共代码):
class DogUtil {
// 第一个拦截器方法
public void method1() {
System.out.println("-----------模拟第一个通用方法-----------");
}
// 第二个拦截器方法
public void method2() {
System.out.println("-----------模拟第二个通用方法-----------");
}
}
假设info()和run()代表两个要调用公共代码的方法,但是不能以硬编码的方式来进行调用,应该怎么办呢?可以借助Proxy和InvocationHandler来实现:当程序调用info()和run()方法时,系统将会"自动"将method1()和method2()两个通用方法插入到info()和run()方法。
class MyInvokationHandlerPro implements InvocationHandler {
// 需要被代理的对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Exception {
DogUtil du = new DogUtil();
// 执行DogUtil对象中method1方法
du.method1();
// 以target作为主调来执行method方法
Object result = method.invoke(target, args);
// 执行DogUtil对象中method2方法
du.method2();
return result;
}
}
上面程序中的类是InvokationHandler的一个实现类,该实现类的invoke方法将会作为代理的方法实现,其中invoke方法包含了一行关键的代码,这行代码以target作为主调通过反射来执行method方法,这就实现了target对象的原有方法。
有了代理处理类以及需要被代理的对象target,我们还需要一段代码为指定的target生成动态代理:
class MyProxyFactory {
// 为指定target生成动态代理对象
public static Object getProxy(Object target) throws Exception {
// 创建一个MyInvokationHandler对象
MyInvokationHandlerPro handler = new MyInvokationHandlerPro();
// 为MyInvokationHandler设置target对象
handler.setTarget(target);
// 创建,并返回一个动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), handler);
}
}
上面的类是一个动态代理工厂类,它提供了一个getProxy方法,为target对象生成了一个动态代理对象。这个动态代理实现了与target相同的接口,所以动态代理对象可以当成target对象来使用。通俗一点说,就像为明星忙碌的经纪人,他们就是活生生的代理,帮助明星打理事务。
当程序调用动态代理对象的指定方法的时候,实际上就是执行MyInvokationHandlerPro 的invoke方法。
下面提供主程序来测试这种动态代理的效果:
public class TestProxy {
public static void main(String[] args) throws Exception {
// 创建一个原始的GunDog对象,作为target
Dog target = new GunDog();
// 以指定的target来创建动态代理
Dog dog = (Dog) MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}
程序的执行结果为:
-----------模拟第一个通用方法-----------
我是一只猎狗
-----------模拟第二个通用方法-----------
-----------模拟第一个通用方法-----------
我奔跑迅速
-----------模拟第二个通用方法-----------
上面程序中的dog对象实际上就是动态代理的对象,只是该动态代理对象也实现Dog接口,所以也可以当成Dog对象使用。
这种动态代理在AOP被称为AOP代理。AOP代理可以替代目标对象,AOP代理包含了目标对象的全部方法。但是AOP代理中的方法与目标对象的方法存在差异:AOP代理里面的方法可以在执行目标方法之前,插入一些通用处理。
三、AOP中的"成员"介绍
连接点(JoinPoint)
在AOP表示为"在哪里做",它用来定义在程序的什么地方能够通过AOP加入额外的逻辑。
切入点(Pointcut)
在AOP表示为"在哪里做的集合"。通过创建切入点,我们可以精确地控制程序中什么组件接到什么通知,而一个典型的切入点就是对某一个类的所有方法调用的集合。
通知、增加(Advice)
在AOP表示为"做什么"。增强即为在连接点上执行的行为,包括前置增强、后置增强、环绕增强。
方面/切面(Aspect)
在AOP表示为"在哪里做和做什么的集合"。切面是通知和切点的集合,通知和切点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。
顾问(Advisor)
在Spring中,一个advisor就是一个aspect的完整的模块化标识。一般地,一个advisor包括通知和切入点,是通知和切入点的配置器。
目标对象(Target Object)
在AOP表示为"对谁做"。目标对象是需要被织入横切关注点的对象,也就是切入点选择的对象、需要被增强的对象。由于Spring AOP通过代理模式实现,从而这个对象永远是被代理对象。
AOP代理(AOP Proxy)
AOP框架使用代理模式创建的对象,从而实现在连接点处插入增加。Spring中,可以通过JDK动态代理或者CGLIB代理实现AOP代理,通过拦截器模型应用切面。
织入(Weaving)
织入是一个过程,是将切面运用到目标对象从而创建出AOP代理对象的过程。织入是在编译时完成的,它通常是作为编译过程中的一个额外的步骤。
引入(introduction)
在AOP表示为"做什么(新增什么)"。引入也称为内部类型声明,为已有的类添加额外新的字段或方法。
四、AOP开发步骤
1.功能横切:找出横切关注点
2.分离关注点:各自独立地实现这些横切关注点所需要完成的功能。
3.根据切入点匹配Target中的方法
4.根据AOP的配置寻找对应的Advice,然后进行重组/织入/结合。