最近跳槽了,新公司使用了 AOP 相关的技术,于是查点资料,复习一下。之前,多少知道点,但没怎么在实际项目中使用过~
package com.cap.aop;
public interface ICalculator {
public double add(double num1, double num2) throws Exception;
public double sub(double num1, double num2) throws Exception;
public double div(double num1, double num2) throws Exception;
public double mul(double num1, double num2) throws Exception;
}
package com.cap.aop;
/**
* 加减乘除
* */
public class Calculator implements ICalculator {
@Override
public double add(double num1, double num2) {
double result = num1 + num2;
return result;
}
@Override
public double sub(double num1, double num2) {
double result = num1 - num2;
return result;
}
@Override
public double div(double num1, double num2) {
double result = num1 / num2;
return result;
}
@Override
public double mul(double num1, double num2) {
double result = num1 * num2;
return result;
}
}
定义 ICalculator 接口和 Calculator 类,并且 Calculator 也继承 ICalculator。
若要为这个类添加“日志”功能该如何做?日志在实际项目中很有必要,比如数据库日志,业务日志等等,通过日志就能知道数据库和业务存在的问题,这要比调试程序容易多了,此外还有性能统计,安全控制,事务处理,异常处理等等都是类似问题。
我们最可能想到的是,在类的每个方法内都写日志相关的代码,或是在该类的基类中写,在其子类中继承。
package com.cap.aop;
/**
* 加减乘除,原始方式
* */
public class CalculatorOriginalWay implements ICalculator {
@Override
public double add(double num1, double num2) {
System.out.println("the method [add()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 + num2;
System.out.println("the method [add()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double sub(double num1, double num2) {
System.out.println("the method [sub()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 - num2;
System.out.println("the method [sub()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double div(double num1, double num2) {
System.out.println("the method [div()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 / num2;
System.out.println("the method [div()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double mul(double num1, double num2) {
System.out.println("the method [mul()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 * num2;
System.out.println("the method [mul()]" + "end with result (" + result
+ ")");
return result;
}
}
这样做的缺点显而易见,重复代码太多,耦合也不好。要是该类只针对正数运算呢,还要对变量做检查,代码如下所示:
package com.cap.aop;
/**
* 加减乘除,只对正数
* */
public class CalculatorForPositiveNumber implements ICalculator {
@Override
public double add(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [add()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 + num2;
System.out.println("the method [add()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double sub(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [sub()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 - num2;
System.out.println("the method [sub()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double div(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [div()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 / num2;
System.out.println("the method [div()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double mul(double num1, double num2) throws Exception {
this.argsValidatior(num1);
this.argsValidatior(num2);
System.out.println("the method [mul()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = num1 * num2;
System.out.println("the method [mul()]" + "end with result (" + result
+ ")");
return result;
}
private void argsValidatior(double arg) throws Exception {
if (arg < 0)
throw new Exception("参数不能为负数");
}
}
这也仅仅是一个类而已,实际项目中那么多类,要是都这么干,显然不现实,那么如何改进?——设计模式“装饰者模式”,在不必改变原类文件和继承的情况下,动态地扩展一个对象的功能。
package com.cap.aop;
/**
* 加减乘除,装饰者模式
*/
public class CalculatorDecorator implements ICalculator {
private ICalculator cal;
public CalculatorDecorator(ICalculator iCalculator) {
cal = iCalculator;
}
@Override
public double add(double num1, double num2) throws Exception {
System.out.println("the method [add()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.add(num1, num2);
System.out.println("the method [add()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double sub(double num1, double num2) throws Exception {
System.out.println("the method [sub()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.sub(num1, num2);
System.out.println("the method [sub()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double div(double num1, double num2) throws Exception {
System.out.println("the method [div()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.div(num1, num2);
System.out.println("the method [div()]" + "end with result (" + result
+ ")");
return result;
}
@Override
public double mul(double num1, double num2) throws Exception {
System.out.println("the method [mul()]" + "begin with args (" + num1
+ "," + num2 + ")");
double result = cal.mul(num1, num2);
System.out.println("the method [mul()]" + "end with result (" + result
+ ")");
return result;
}
}
这个方法比上面的强点,用原来的类 Calculator 创建一个新类 CalculatorDecorator,也继承 ICalculator 接口,这样,在对原来的类不做任何修改的情况下,在新类中添加日志功能。但该方法也有弱点,需要为每个类都应用“装饰者模式”,工作量也不小。如何解决?——代理。
JDK 从 1.3 版本开始,引入了动态代理。JDK 动态代理非常简单,但动态代理的对象必须是一个或多个接口。
若想代理类,就需要使用 cglib 包。cglib 是一个强大的、高性能的代码生成包,cglib 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类,cglib 被许多 AOP 框架使用,例如 Spring 的 AOP;Hibernate 使用 cglib 来代理单端 single-ended(多对一和一对一)关联;EasyMock 通过使用模仿(moke)对象来测试 java 包……它们都通过 cglib 来为那些没有接口的类创建模仿(moke)对象。
package com.cap.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 加减乘除,JDK 代理<br/>
* 只能代理接口,不能代理类
*
* */
public class CalculatorInvocationHandler implements InvocationHandler {
// 动态代理只有在运行时才知道代理谁,所以定义为Object类型
private Object target = null;
/**
* 通过构造函数传入原对象
*
* @param target
* 要代理的对象
*/
public CalculatorInvocationHandler(Object target) {
this.target = target;
}
/**
* InvocationHandler 接口的 invoke 方法,调用代理的方法
*
* @param proxy在其上调用方法的代理实例
* @param method拦截的方法
* @param args拦截的参数
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("JDK proxy...");
// 日志开始
System.out.println("the method [" + method.getName() + "]"
+ "begin with args (" + Arrays.toString(args) + ")");
Object result = method.invoke(this.target, args);
// 日志结束
System.out.println("the method [" + method.getName() + "]"
+ "end with result (" + result + ")");
return result;
}
/**
* 获取代理类
*/
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new CalculatorInvocationHandler(target));
}
}
package com.cap.aop;
import java.lang.reflect.Method;
import java.util.Arrays;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 加减乘除,cglib 代理<br/>
* 能代理接口和类,不能代理final类
*/
public class CalculatorInterceptor implements MethodInterceptor {
private Object target;
public CalculatorInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy invocation) throws Throwable {
System.out.println("cglib proxy...");
// 日志开始
System.out.println("the method [" + method.getName() + "]"
+ "begin with args (" + Arrays.toString(args) + ")");
Object result = invocation.invoke(target, args);
// 日志结束
System.out.println("the method [" + method.getName() + "]"
+ "end with result (" + result + ")");
return result;
}
public Object proxy() {
return Enhancer.create(target.getClass(), new CalculatorInterceptor(
target));
}
}
package com.cap.aop;
public class Client {
public static void main(String[] args) throws Exception {
ICalculator calculatorProxy = (ICalculator) new CalculatorInvocationHandler(
new Calculator()).getProxy();
calculatorProxy.add(10, 10);
Calculator calculator = (Calculator) new CalculatorInterceptor(
new Calculator()).proxy();
calculator.add(20, 20);
}
}
运行结果:
JDK proxy...
the method [add]begin with args ([10.0, 10.0])
the method [add]end with result (20.0)
cglib proxy...
the method [add]begin with args ([20.0, 20.0])
the method [add]end with result (40.0)
利用 AOP 框架,你只需要利用注释和 Aspect 就可以完成上面的所有工作。
AOP,Aspect Oriented Programming,称为“面向切面编程”,AOP 是 OOD/OOP 和 GoF 的延续,GoF 孜孜不倦的追求是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP 的目标也是一样。
AOP 是一种通过预编译和运行时动态代理实现在不修改源代码的情况下给程序动态统一添加功能的技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。在 Spring 中,通过分离应用的业务逻辑与系统级服务,应用对象只完成业务逻辑,不负责(甚至是意识)其它的系统级关注点,例如日志、事务、审计等等。
本文最开始的“日志功能”,就是一个切面。
AOP 主要应用在日志记录,性能统计,安全控制,事务处理,异常处理等等,将它们从业务逻辑代码中分离,将它们独立到非业务逻辑的方法中,进而在改变这些行为时不影响业务逻辑的代码。
AOP(面向切面编程)与 OOP(面向对象编程)字面上虽然类似,但却是面向不同领域的两种设计思想。
OOP 针对业务中的实体及其属性和行为进行抽象封装,以便划分逻辑单元。而 AOP 则是针对业务中的“切面”进行提取,它面对的是处理过程中的某个步骤或阶段,以获得各部分之间低耦合性的隔离效果。因此,这两种设计思想有着本质的差异。
简单来说,对“雇员”这个业务实体进行封装,是 OOP 的任务,我们可以建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用 AOP 对“雇员”进行封装将无从谈起;权限检查也是如此,它是 AOP 的领域。
换而言之,OOD/OOP 面向名词领域,AOP 面向动词领域。
很多人在初次接触 AOP 的时候可能会说,AOP 能做到的,一个定义良好的 OOP 接口也能,这个观点是值得商榷。AOP 和定义良好的 OOP 可以说都是用来解决并且实现需求中的横切问题。但对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法回避的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟;AOP 则不会,你只需要修改相应的 Aspect,再重新 weave(编织)即可。 AOP 也绝对不会取代 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,扬长避短。
下面概念在 AspectWerkz 的注释中都有所体现。