一、 引言
AOP(Aspect-Oriented Programming,面向切面的编程),是一种新型的编程范式,主张关注软件流程中的一个切面,将同样功能的代码整合打包在一起,降低系统的耦合性,增强其扩展性。
传统的软件设计,往往采取事件驱动模型带来类似的效果,通过在可能的事件切入点插入事件回调函数,将对应位置插入外置代码。
函数式编程,也有类似的解决方案,通过函数传递,将对应位置的扩展上新的功能。
Java作为一门严谨的传统式开发语言,以安全性和可靠性为第一标准,语言并没有过多的新特性支持,Java8仅支持到lambda表达式,为了使Java具有更强大的编程模型,Spring等框架使用gclib库实现了面向切面的编程模型。
二、 CGLIB 和 ASM
CGLIB 是一个强大的,高性能,高质量的Code生成类库,被广泛的用作动态代理技术。CGLIB 包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类,新的字节码可以被Java虚拟机直接加载运行。
其实动态代理并不是CGLIB的专利,早在JDK1.3版起,就引入了动态代理库,Spring AOP 编程时就可以进行选择,使用JDK提供的动态代理库,或者是引入CGLIB库。
下面举一个实例,来说明一些如何使用CGLIB库,将我们本来应该正常执行的函数调用,进行截断操作。
package com.abs.testcglib;
public class Service {
String name;
public Service(String name) {
this.name = name;
}
public void sayHello() {
System.out.println("Hello "+name);
}
}
首先,我们创建一个服务类,其中有一个sayHello()
方法,我们希望将这个方法截断,以添加其余组件的一些处理功能,例如持久化组件希望在此添加一条记录一类的功能。
package com.abs.testcglib;
public class Main {
public static void main(String[] args) {
Service s = new Service("Sxf");
s.sayHello();
}
}
在Main函数中调用一下,可以看的Hello Sxf
的输出。
但我们怎么截断呢?首先就要创建一个代理类,所谓代理,就是你让这个代理类,代你调用这个类的函数。
创建一个代理类:
package com.abs.testcglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("方法名:"+method.getName());
Service a = (Service) o;
a.name = "Wah";
System.out.println("哈哈,我要改名");
Object result = methodProxy.invokeSuper(o, args);
return result;
}
}
这个代理类的功能,就是将传统的Java直接的函数调用,包上一次外壳,因为Java本身的函数调用是系统完成的,很难由你大段他,但代理类不同,你可以明确的看的调用了哪个函数,并且可以根据这点,轻松的在函数调用前后,插入你希望插入的代码。
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
这个函数大概是整个代理调用中最关键的一个函数了,o这个参数表示了该函数所在的对象,args是调用的参数,Method则是反射到的方法。最后一个则是代理的实例。
我们对函数的打断功能,则都在这个函数里实现。
当然,由于是通过代理实现,对象的构建也有所不同,所以我们自己写一个static函数作为构造函数使用。
package com.abs.testcglib;
import net.sf.cglib.proxy.Enhancer;
public class Service {
String name;
public Service(String name) {
this.name = name;
}
public void sayHello() {
System.out.println("Hello "+name);
}
public static Service getProxyInstance(CglibProxy myProxy, String name) {
Enhancer en = new Enhancer();
// 设置父类和回调
en.setSuperclass(Service.class);
en.setCallback(myProxy);
// 调用其构造函数,需要传入对应的Class列表和参数Object列表
return (Service) en.create(new Class[] {String.class}, new Object[] {name});
}
}
而Main函数中也应该这样使用该对象:
package com.abs.testcglib;
public class Main {
public static void main(String[] args) {
Service s = new Service("Sxf");
s.sayHello();
Service s2 = Service.getProxyInstance(new CglibProxy(), "Sxf");
s2.sayHello();
}
}
我们发现,两种方式创建出的对象,使用上几乎一样,唯一不同的就是构造函数时,我们进行了部分修改,其余部分,不影响我们的对象正常传递,存储等功能。
最终效果:
三、 Spring AOP 的实现
其实看来刚才CGLIB的实现,再看著名的Spring框架,就会发现两者的实现方式几乎完全一样,只不过Spring框架多增加了一些概念和功能。
下面我们写一个Target 类,这是一个被代理的目标对象,其中有一个execute()
方法,现在使用 AOP 对 execute()
方法做日志输出。在执行execute()
方法前,做日志输出。
public class Target {
public void execute(String name){
System.out.println("executeMethod is here" + name);
}
}
通知可以拦截目标对象的 execute()方法,并执行日志输出。创建通知的代码如下:
public class LoggerExecute implements MethodInterceptor {
public Object invoke(MethodInvocation arg0) throws Throwable {
before();
arg0.proceed();
return null;
}
private void before() {
System.out.println("executeMethod is exe!");
}
}
创建代理的方法也几乎一样:
public static void main(String[] args) {
//创建目标对象
Target target = new Target();
//创建代理
ProxyFactory di=new ProxyFactory();
di.addAdvice(new BeforeExecute());
di.setTarget(target);
Target proxy=(Target)di.getProxy();
//代理执行execute()方法
proxy.execute(" ni hao");
}
当然Spring的切入点和其配置文件关联十分紧密,用Spring框架能够将系统的更多固定参数丢到配置文件中去,或者直接使用注解也可以。