Java核心库实现AOP过程

这篇文章是关于Java的一个疑难杂症,经过利用Java核心库实现简单的AOP方法,并把实例代码作了分析对照,如下是所有内容:编程

Spring是一个十分火热开源框架,而AOP(面向切面编程)则是Spring最重要的概念之一,为了更好的理解和学习AOP的思想,使用核心库来实现一次不失为一个好方法。bash

首先介绍一下AOP的概念,AOP(Aspect Oriented Programming),即面向切面编程,所谓的面向切面编程,就是从一个横切面的角度去设计代码的思想,传统的OOP思想是用封装继承和多态构造一种纵向的层次关系,但不适合定义横向的关系,而AOP思想则对此进行了很好的补充。框架

例如日志管理代码每每横向的散布在不少对象层次中,但跟它对应的对象的核心功能能够说是毫无关系,还有不少相似的代码,如权限验证,调试输出,事务处理等,也都是如此,这样的话就不利于代码的复用和管理了。ide

这时候AOP技术就应运而生了,它利用“横切”技术,深刻封装对象的内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减小系统的重复代码,下降模块之间的耦合度,并有利于后续的可操做性和可维护性。函数

那么AOP又是如何实现的呢?学习

答案是动态代理(关于代理会有另外篇章作详细介绍,这里就不赘述了)。实现动态代理有两种方式,一种是JDK动态代理,一种是CGLib动态代理。测试

那么分别使用两种方式来作一个简单的栗子。ui

先设计一个场景,假设咱们有一个计算接口ICalculator和实现了该接口的计算器类CalculatorImpl。this

public interface ICalculator {
//加法运算
public int add(int a,int b);
//减法
public int subtract(int a,int b);
//乘法
public int multiply(int a,int b);
//除法
public int devide(int a,int b);
} 
public class CalculatorImpl implements ICalculator{
@Override
public int add(int a, int b) {
 return a + b;
}
@Override
public int subtract(int a, int b) {
 return a - b;
}
@Override
public int multiply(int a, int b) {
 return a * b;
}
@Override
public int devide(int a, int b) {
 return a / b;
}
} 
复制代码

如何在不改动原来计算器类内部代码的状况下记录计算器各个方法使用的总次数呢?spa

有了动态代理后,其实就很简单了,先建立一个类并实现InvocationHandler接口,覆盖invoke方法,

public class TestHandler implements InvocationHandler {
private Object targetObject;
private int useTimes;
//绑定委托对象,并返回代理类

public Object bind(Object targetObject){
 this.targetObject = targetObject;
 return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 //do something
 before();
 Object result = method.invoke(targetObject,args);
 after();
 return result;
}
private void before(){
 System.out.println("we can do something before calculate.");
}
private void after(){
 useTimes++;
 System.out.println("已使用:"+useTimes+"次");
}
} 
复制代码

别看代码好像有点多,其实主要的方法就是invoke方法,里面的Object result = method.invoke(targetObject,args);至关于继续用原来的参数执行原来方法。这里的before和after为自定义的函数,能够在目标代码执行先后作一些咱们想要作的事情,好比这里的使用次数统计。

在bind方法里,传入目标代理对象,并返回一个代理类实例。接下来咱们看看如何使用:

public class TestProxy {
 public static void main(String[] args) {
  TestHandler proxy = new TestHandler();
  ICalculator calculator = (ICalculator)proxy.bind(new CalculatorImpl());
  int result = calculator.add(1,2);
  System.out.println("result is:"+result);
  result = calculator.subtract(3,2);
  System.out.println("result is:"+result);
  result = calculator.multiply(4,6);
  System.out.println("result is:"+result);
  result = calculator.devide(6,2);
  System.out.println("result is:"+result);
 }
} 
复制代码

咱们先定义一个TestHandler,而后经过bind方法来得到一个代理实例,以后咱们就能够直接使用这个实例了。运行结果以下:

we can do something before calculate.
已使用:1次
result is:3
we can do something before calculate.
已使用:2次
result is:1
we can do something before calculate.
已使用:3次
result is:24
we can do something before calculate.
已使用:4次
result is:3 
复制代码

这样咱们就实现了不修改CalculatorImpl内部代码的状况下对代码进行扩展。

接下来用CGLib的方式来实现一次。

先建立一个类来实现MethodInterceptor接口,并覆盖intercept方法。其余代码跟使用JDK代理大同小异,仅仅是获取代理对象的过程有所差别。

public class CGLibProxy implements MethodInterceptor {
private int useTimes;
private Object target;

public Object getInstance(Object target){
 this.target=target;
 Enhancer enhancer =new Enhancer();
 enhancer.setSuperclass(this.target.getClass());
 enhancer.setCallback(this);
 return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
 before();
 Object result = methodProxy.invokeSuper(o,objects);
 after();
 return result;
}
private void before(){
 System.out.println("we can do something before calculate.");
}
private void after(){
 useTimes++;
 System.out.println("已使用:"+useTimes+"次");
}
} 
复制代码

测试一下:

public class TestCGLibProxy {
public static void main(String[] args) {
 CGLibProxy cgLibProxy = new CGLibProxy();
 ICalculator calculator = (ICalculator) cgLibProxy.getInstance(new CalculatorImpl());
 int result = calculator.add(1,2);
 System.out.println("result is:"+result);
 result = calculator.subtract(3,2);
 System.out.println("result is:"+result);
 result = calculator.multiply(4,6);
 System.out.println("result is:"+result);
 result = calculator.devide(6,2);
 System.out.println("result is:"+result);
}
} 
复制代码

运行结果以下:

we can do something before calculate.
已使用:1次
result is:3
we can do something before calculate.
已使用:2次
result is:1
we can do something before calculate.
已使用:3次
result is:24
we can do something before calculate.
已使用:4次
result is:3 
复制代码

如今咱们获得了一样的结果。(须要导入两个包,cglib-2.2.2.jar asm-3.3.jar)

两种方法各有所长,JDK代理须要先设置一个接口,而后才能实现代理,这是它的缺点,也是它的优势,缺点是这样会麻烦一点,并且没法对那些已经封装好的,没有实现接口的类进行代理,而CGLib代理的方式不须要使用接口。但也正是由于如此,JDK代理的方式仅仅拦截类中覆盖接口的方法,而CGLib则会拦截类的全部方法调用。二者各有利弊,因此须要具体状况具体分析。在Spring中也是混杂使用了两种代理模式。

相关文章
相关标签/搜索