AOP面向切面编程

 AOP(Aspect-Oriented Programming,面向切面的编程),它是能够经过预编译方式和运行期动态代理实如今不修改源代码的状况下给程序动态统一添加功能的一种技术。它是一种新的方法论,它是对传统OOP编程的一种补充。java

  OOP是关注将需求功能划分为不一样的而且相对独立,封装良好的类,并让它们有着属于本身的行为,依靠继承和多态等来定义彼此的关系;AOP是但愿可以将通用需求功能从不相关的类当中分离出来,可以使得不少类共享一个行为,一旦发生变化,没必要修改不少类,而只须要修改这个行为便可。程序员

  AOP是使用切面(aspect)将横切关注点模块化,OOP是使用类将状态和行为模块化。在OOP的世界中,程序都是经过类和接口组织的,使用它们实现程序的核心业务逻辑是十分合适。可是对于实现横切关注点(跨越应用程序多个模块的功能需求)则十分吃力,好比日志记录,验证。编程

/*计算器接口*/
public interface Calculator
{
    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;
}
/*计算器接口的实现类*/
public class ArithmeticCalculator implements Calculator
{
    @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;
    }
    /*示意代码 暂时不考虑除数0的状况*/
    @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;
    }
}

   大多数应用程序都有一个通用的需求,即在程序运行期间追踪正在发生的活动。为了给计算机添加日志功能,ArithmeticCalculator类改变以下:设计模式

/*计算器接口的实现类,添加记录日志功能*/
public class ArithmeticCalculator implements Calculator
{
    @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;
    }
    /*示意代码 暂时不考虑除数0的状况*/
    @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;
    }
}

  若ArithmeticCalculator规定只能计算正数时,又须要添加参数验证方法:框架

/*计算器接口的实现类,添加记录日志功能*/
public class ArithmeticCalculator implements Calculator
{
    @Override
    public double add(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
         /*同上*/
    }
    @Override
    public double sub(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
         /*同上*/
    }
    /*示意代码 暂时不考虑除数0的状况*/
    @Override
    public double div(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
         /*同上*/
    }
    @Override
    public double mul(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
        /*同上*/
    }
    
    private void argsValidatior(double arg)throws Exception
    {
        if(arg < 0)
            throw new Exception("参数不能为负数");
    }
}

   上面的程序一个很直观的特色就是,好多重复的代码,而且当加入愈来愈多的非业务需求(例如日志记录和参数验证),原有的计算器方法变得膨胀冗长。这里有一件很是痛苦的事情,没法使用原有的编程方式将他们模块化,从核心业务中提取出来。例如日志记录和参数验证,AOP里将他们称为横切关注点(crosscutting concern),它们属于系统范围的需求一般须要跨越多个模块。
  在使用传统的面向对象的编程方式没法理想化的模块化横切关注点,程序员不能不作的就是将这些横切关注点放置在每个模块里与核心逻辑交织在一块儿,这将会致使横切关注点在每个模块里处处存在。使用非模块化的手段实现横切关注将会致使,代码混乱,代码分散,代码重复。你想一想看若是日志记录须要换一种显示方式,那你要改多少代码,一旦漏掉一处(几率很高),将会致使日志记录不一致。这样的代码很维护。种种缘由代表,模块只须要关注本身本来的功能需求,须要一种方式来将横切关注点冲模块中提取出来。ide

  忍无可忍的大牛们提出了AOP,它是一个概念,一个规范,自己并无设定具体语言的实现,也正是这个特性让它变的很是流行,如今已经有许多开源的AOP实现框架了。本次不是介绍这些框架的,咱们将不使用这些框架,而是使用底层编码的方式实现最基本的AOP解决上面例子出现的问题。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP能够说也是这种目标的一种实现。AOP可使用"代理模式"来实现。模块化

 代理模式的原理是使用一个代理将对象包装起来,而后用该代理对象取代原始的对象,任何对原始对象的调用首先要通过代理。代理对象负责决定是否以及什么时候将方法调用信息转发到原始对象上。与此同时,围绕着每一个方法的调用,代理对象也能够执行一些额外的工做。能够看出代理模式很是适合实现横切关注点。函数

  因为本人只了解Java,因此姑且认为代理模式有两种实现方式,一种是静态代理、另外一种是动态代理。他们的区别在于编译时知不知道代理的对象是谁。在模块比较多的系统中,静态代理是不合适也很是低效的,由于静态代理须要专门为每个接口设计一个代理类,系统比较大成百上千的接口是很正常的,静态代理模式太消耗人力了。动态代理是JDK所支持的代理模式,它能够很是好的实现横切关注点。this

/*使用动态代理须要实现InvocationHandler接口*/
public class ArithmeticCalculatorInvocationHandler implements InvocationHandler
{
    /*要代理的对象,动态代理只有在运行时才知道代理谁,因此定义为Object类型,能够代理任意对象*/
    private Object target = null;
    
    /*经过构造函数传入原对象*/
    public ArithmeticCalculatorInvocationHandler(Object target)
    {
        this.target = target;
    }
    /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        /*原对象方法调用前处理日志信息*/
        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(this.target.getClass().getClassLoader(), this.getClass().getInterfaces(), this);
    }
}

场景类调用:编码

public class Client
{
    public static void main(String[] args) throws Exception
    {
        /*得到代理*/
        Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler(
                 new ArithmeticCalculator()).getProxy();
        /*调用add方法*/
        arithmeticCalculatorProxy.add(10, 10);
    }
}

控制台的输出:

能够看到使用动态代理实现了横切关注点。

若须要添加参数验证功能,只须要再建立一个参数验证代理便可:

public class ArithmeticCalculatorArgsInvocationHandler implements
        InvocationHandler
{
    /*要代理的对象,动态代理只有在运行时才知道代理谁,因此定义为Object类型,能够代理任意对象*/
    private Object target = null;
    
    /*经过构造函数传入原对象*/
    public ArithmeticCalculatorArgsInvocationHandler(Object target)
    {
        this.target = target;
    }
    /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        System.out.println("begin valid method ["+method.getName()+"] with args "+Arrays.toString(args));
        
        for(Object arg : args)
        {
            this.argValidtor((Double)arg);
        }
        
        Object result = method.invoke(this.target, args);
        
        return result;
    }
    
    /*获取代理类*/
    public Object getProxy()
    {
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
    }
    
    private void argValidtor(double arg) throws Exception
    {
        if(arg < 0)
            throw new Exception("参数不能为负数!");
    }
}

场景类调用:

public class Client
{
    public static void main(String[] args) throws Exception
    {
        /*得到代理*/
        Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler(
                 new ArithmeticCalculator()).getProxy();
        
        Calculator argValidatorProxy = (Calculator)new ArithmeticCalculatorArgsInvocationHandler(arithmeticCalculatorProxy).getProxy();
        /*调用add方法*/
        argValidatorProxy.add(10, 10);
    }
}

控制台输出:

输入一个负数数据:

public class Client
{
    public static void main(String[] args) throws Exception
    {
        /*得到代理*/
        Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler(
                 new ArithmeticCalculator()).getProxy();
        
        Calculator argValidatorProxy = (Calculator)new ArithmeticCalculatorArgsInvocationHandler(arithmeticCalculatorProxy).getProxy();
        /*调用add方法*/
        argValidatorProxy.add(-10, 10);
    }
}

控制台输出:

不知道你有没有使用过Struts2,这个结构和Struts2的拦截器很是类似,一个个Action对象比如咱们的原对象业务核心,一个个拦截器比如是这里的代理,通用的功能实现成拦截器,让Action能够共用,Struts2的拦截器也是AOP的优秀实现。

相关文章
相关标签/搜索