关闭

理解AOP

标签: AOP
240人阅读 评论(0) 收藏 举报
分类:

Aspect Oriented Programming  面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。

具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。

AOP 在Spring框架中被作为核心组成部分之一,的确Spring将AOP发挥到很强大的功能。最常见的就是事务控制。工作之余,对于使用的工具,不免需要了解其所以然。学习了一下,写了些程序帮助理解。

AOP 主要是利用代理模式的技术来实现的。

1、静态代理:就是设计模式中的proxy模式

a、业务接口

/**
 * 抽象主题角色:声明了真实主题和代理主题的共同接口。
 * 
 * @author yanbin
 * 
 */
public interface ITalk {

    public void talk(String msg);

}

b、业务实现

/**
 * 真实主题角色:定义真实的对象。
 * 
 * @author yanbin
 * 
 */
public class PeopleTalk implements ITalk {

    public String username;
    public String age;

    public PeopleTalk(String username, String age) {
        this.username = username;
        this.age = age;
    }

    public void talk(String msg) {
        System.out.println(msg + "!你好,我是" + username + ",我年龄是" + age);
    }

    public String getName() {
        return username;
    }

    public void setName(String name) {
        this.username = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

}

c、代理对象

/**
 * 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口。
 * 
 * @author yanbin
 * 
 */
public class TalkProxy implements ITalk {

    private ITalk talker;

    public TalkProxy(ITalk talker) {
        // super();
        this.talker = talker;
    }

    public void talk(String msg) {
        talker.talk(msg);
    }

    public void talk(String msg, String singname) {
        talker.talk(msg);
        sing(singname);
    }

    private void sing(String singname) {
        System.out.println("唱歌:" + singname);
    }

}

d、测试类

/**
 * 代理测试类,使用代理
 *
 * @author yanbin
 * 
 */
public class ProxyPattern {

    public static void main(String[] args) {
        // 不需要执行额外方法的。
        ITalk people = new PeopleTalk("AOP", "18");
        people.talk("No ProXY Test");
        System.out.println("-----------------------------");

        // 需要执行额外方法的(切面)
        TalkProxy talker = new TalkProxy(people);
        talker.talk("ProXY Test", "代理");
    }

}

从这段代码可以看出来,代理模式其实就是AOP的雏形。 上端代码中talk(String msg, String singname)是一个切面。在代理类中的sing(singname)方法是个后置处理方法。

这样就实现了,其他的辅助方法和业务方法的解耦。业务不需要专门去调用,而是走到talk方法,顺理成章的调用sing方法

再从这段代码看:1、要实现代理方式,必须要定义接口。2、每个业务类,需要一个代理类。

2、动态代理:jdk1.5中提供,利用反射。实现InvocationHandler接口。

业务接口还是必须得,业务接口,业务类同上。

a、代理类:

/**
 * 动态代理类
 * 
 * @author yanbin
 * 
 */
public class DynamicProxy implements InvocationHandler {

    /** 需要代理的目标类 */
    private Object target;

    /**
     * 写法固定,aop专用:绑定委托对象并返回一个代理类
     * 
     * @param delegate
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     * @param Object
     *            target:指被代理的对象。
     * @param Method
     *            method:要调用的方法
     * @param Object
     *            [] args:方法调用时所需要的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        // 切面之前执行
        System.out.println("切面之前执行");
        // 执行业务
        result = method.invoke(target, args);
        // 切面之后执行
        System.out.println("切面之后执行");
        return result;
    }

}

b、测试类

/**
 * 测试类
 * 
 * @author yanbin
 * 
 */
public class Test {

    public static void main(String[] args) {
        // 绑定代理,这种方式会在所有的方法都加上切面方法
        ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk());
        iTalk.talk("业务说明");
    }
}

输出结果会是:

切面之前执行
people talk业务说法
切面之后执行

说明只要在业务调用方法切面之前,是可以动态的加入需要处理的方法。

从代码来看,如果再建立一个业务模块,也只需要一个代理类。ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk());  将业务接口和业务类绑定到动态代理类。

但是这种方式:还是需要定义接口。

3、利用cglib

CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强。采用的是继承的方式。不细说,看使用

a、业务类

/**
 * 业务类
 * 
 * @author yanbin
 * 
 */
public class PeopleTalk {

    public void talk(String msg) {
        System.out.println("people talk" + msg);
    }

}

b、cglib代理类

/**
 * 使用cglib动态代理
 * 
 * @author yanbin
 * 
 */
public class CglibProxy implements MethodInterceptor {

    private Object target;

    /**
     * 创建代理对象
     * 
     * @param target
     * @return
     */
    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 proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        System.out.println("事物开始");
        result = methodProxy.invokeSuper(proxy, args);
        System.out.println("事物结束");
        return result;
    }

}

c.测试类

/**
 * 测试类
 * 
 * @author yanbin
 * 
 */
public class Test {

    public static void main(String[] args) {
        PeopleTalk peopleTalk = (PeopleTalk) new CglibProxy().getInstance(new PeopleTalk());
        peopleTalk.talk("业务方法");
        peopleTalk.spreak("业务方法");
    }

}

最后输出结果:

事物开始
people talk业务方法
事物结束
事物开始
spreak chinese业务方法
事物结束

由于篇幅有限,这篇主要对AOP的原理简单实现做了演示和阐述,有助自己理解。至于Spring的AOP实现上面无外乎其右,不过实现方面复杂的多。

 

======================================================================================================================

 

1、问题 


问题:想要添加日志记录、性能监控、安全监测 

 

2、最初解决方案 

2.1、最初解决方案

缺点:太多重复代码,且紧耦合

 

2.2、抽象类进行共性设计,子类进行个性设计,此处不讲解,缺点一荣俱荣,一损俱损

 

2.3、使用装饰器模式/代理模式改进的解决方案

装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说, 装饰器模式相比生成子类更为灵活。
代理模式:为其他对象提供一种代理以控制对这个对象的访问。


 
缺点:紧耦合,每个业务逻辑需要一个装饰器实现或代理
 
2.4、JDK动态代理解决方案(比较通用的解决方案) 
Java代码 复制代码 收藏代码
  1. public class MyInvocationHandler implements InvocationHandler {    
  2.     private Object target;   
  3.     public MyInvocationHandler(Object target) {   
  4.         this.target = target;   
  5.     }   
  6.     @Override  
  7.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
  8.         //1.记录日志    2.时间统计开始      3.安全检查   
  9.         Object retVal = method.invoke(target, args);   
  10.         //4.时间统计结束   
  11.         return retVal;      
  12.     }   
  13.     public static Object proxy(Object target) {   
  14.         return Proxy.newProxyInstance(target.getClass().getClassLoader(),    
  15.                 target.getClass().getInterfaces(), new MyInvocationHandler(target));   
  16.     }   
  17. }  

 编程模型 

Java代码 复制代码 收藏代码
  1.   //proxy     在其上调用方法的代理实例    
  2.   //method 拦截的方法   
  3.   //args       拦截的参数   
  4.   Override   
  5.    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
  6.        Object retVal=null;   
  7.        //预处理   
  8.        //前置条件判断   
  9.        boolean ok = true;           
  10.        if(!ok) {//不满足条件   
  11.            throw new RuntimeException("你没有权限");   
  12.        }   
  13.        else {//反射调用目标对象的某个方法   
  14.            retVal = method.invoke(target, args);   
  15.        }   
  16.        //后处理   
  17.        return retVal;   
  18.    }   
  19.    

缺点:使用麻烦,不能代理类,只能代理接口  

 

CGLIB动态代理解决方案(比较通用的解决方案)
Java代码 复制代码 收藏代码
  1. public class MyInterceptor implements MethodInterceptor  {     
  2.     private Object target;   
  3.     public MyInterceptor(Object target) {   
  4.         this.target = target;   
  5.     }   
  6.     @Override  
  7.     public Object intercept(Object proxy, Method method, Object[] args,    
  8.                                          MethodProxy invocation) throws Throwable {   
  9.         //1.记录日志 2.时间统计开始   3.安全检查   
  10.         Object retVal = invocation.invoke(target, args);   
  11.         //4.时间统计结束   
  12.         return retVal;      
  13.     }   
  14.     public static Object proxy(Object target) {   
  15.         return Enhancer.create(target.getClass(), new MyInterceptor(target));   
  16.     }   
  17. }  

 编程模型

Java代码 复制代码 收藏代码
  1.  //proxy 在其上调用方法的代理实例    method拦截的方法    args  拦截的参数   
  2.  //invocation 用来去调用被代理对象方法的   
  3. @Override  
  4. public Object intercept(Object proxy, Method method, Object[] args,    
  5.                                        MethodProxy invocation) throws Throwable {   
  6.     //预处理   
  7.     //前置条件判断   
  8.     boolean ok = true;           
  9.     if(!ok) {//不满足条件   
  10.         throw new RuntimeException("出错了");   
  11.     }   
  12.     else {//调用目标对象的某个方法   
  13.         Object retVal = invocation.invoke(target, args);   
  14.     }   
  15.     //后处理   
  16.     return retVal;   
  17. }  
优点:能代理接口和类
缺点:使用麻烦,不能代理final

 

动态代理本质 

本质:对目标对象增强
           最终表现为类(动态创建子类),看手工生成(子类)还是自动生成(子类)
代理限制:
           只能在父类方法被调用之前或之后进行增强(功能的修改),不能在中间进行修改,要想在方法调用中增强,需要ASM(java 字节码生成库)
其他动态代理框架
jbossjavassist hibernate 3.3中默认为javassist
                           (hibernate 3.3之前中默认为cglib

 


 
2.5、AOP解决方案(通用且简单的解决方案)
Java代码 复制代码 收藏代码
  1. @Aspect  
  2. public class PayEbiAspect {       
  3.     @Pointcut(value="execution(* pay(..))")   
  4.     public void pointcut() {}   
  5.     @Around(value="pointcut()")   
  6.     public Object around(ProceedingJoinPoint pjp) throws Throwable {   
  7.         //1.记录日志   
  8.         //2.时间统计开始   
  9.         //3.安全检查   
  10.         Object retVal = pjp.proceed();//调用目标对象的真正方法   
  11.         //4.时间统计结束   
  12.         return retVal;   
  13.     }   
  14. }  
编程模型
Java代码 复制代码 收藏代码
  1. //2 切入点   
  2. @Pointcut(value="execution(* *(..))")   
  3. public void pointcut() {}   
  4. //3 拦截器的interceptor   
  5. @Around(value="pointcut()")   
  6. public Object around(ProceedingJoinPoint pjp) throws Throwable {   
  7.     Object retVal=null;   
  8.     //预处理   
  9.     //前置条件判断   
  10.     boolean ok = true;   
  11.     if(!ok) {//不满足条件   
  12.        throw new RuntimeException("你没有权限");   
  13.     }   
  14.     else {//调用目标对象的某个方法   
  15.          retVal = pjp.proceed();    
  16.     }   
  17.     //后处理   
  18.     return retVal;   
  19. }  
 
缺点:依赖AOP框架 
 
AOP入门
概念:
 
n关注点:可以认为是所关注的任何东西,比如上边的支付组件;
n关注点分离:将问题细化为单独部分,即可以理解为不可再分割的组件,如上边的日志组件和支付组件;
n横切关注点:会在多个模块中出现,使用现有的编程方法,横切关注点会横越多个模块,结果是使系统难以设计、理解、实现和演进,如日志组件横切于支付组件。
 
织入:横切关注点分离后,需要通过某种技术将横切关注点融合到系统中从而完成需要的功能,因此需要织入,织入可能在编译期、加载期、运行期等进行。
 
nAOP是什么(Aspect   Oriented   Programming)
 AOP是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
 AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
 AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
nAOP能干什么,也是AOP带来的好处
1:降低模块的耦合度
2:使系统容易扩展
3:设计决定的迟绑定:使用AOP,设计师可以推迟为将来的需求作决定,因为它
可以把这种需求作为独立的方面很容易的实现。
4:更好的代码复用性

 
AOP基本概念
 连接点(Joinpoint):
    表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring只支持方法执行连接点,AOP中表示为“在哪里做”
切入点(Pointcut):
    选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,AOP中表示为“在哪里做的集合”
增强(Advice):或称为增强
    在连接点上执行的行为,增强提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段;包括前置增强(before advice)、后置增强 (after advice)、环绕增强 (around advice),在Spring中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入增强 ;AOP中表示为“做什么”;
方面/切面(Aspect):
      横切关注点的模块化,比如上边提到的日志组件。可以认为是增强、引入和切入点的组合;在Spring中可以使用Schema@AspectJ方式进行组织实现;AOP中表示为“在哪里做和做什么集合”;
目标对象(Target Object):
    需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被增强的对象,从而也可称为“被增强对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,AOP中表示为“对谁做”
AOP代理(AOP Proxy):
    AOP框架使用代理模式创建的对象,从而实现在连接点处插入增强(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。
织入(Weaving):
    织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。
引入(inter-type declaration):
    也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), AOP中表示为“做什么(新增什么)”
 
AOP的Advice类型
前置增强(Before advice):
    在某连接点之前执行的增强,但这个增强不能阻止连接点前的执行(除非它抛出一个异常)。
后置返回增强(After returning advice):
    在某连接点正常完成后执行的增强:例如,一个方法没有抛出任何异常,正常返回。
后置异常增强(After throwing advice):
    在方法抛出异常退出时执行的增强。
后置最终增强(After (finally) advice):
    当某连接点退出的时候执行的增强(不论是正常返回还是异常退出)。
环绕增强Around Advice):
    包围一个连接点的增强,如方法调用。这是最强大的一种增强类型。 环绕增强可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。


 
AOP开发步骤
 
 
  类似于IoC/DI容器开发步骤,需要描述哪个连接点需要哪个通用功能(增强
 
 
 
横切关注点的表现有:  
  ·代码纠结/混乱——当一个模块或代码段同时管理多个关注点时发生这种情况。如我既要实现业务、还要实现安全和事务。即有些关注点同时被多个不同的模块实现。实现了重复的功能。
  ·代码分散——当一个关注点分布在许多模块中并且未能很好地局部化和模块化时发生这种情况 。如许多模块调用用户是否登录验证代码。调用了重复的功能。
 
 
 
AOP包括三个清晰的开发步骤:
1功能横切:找出横切关注点。
2实现分离:各自独立的实现这些横切关注点所需要完成的功能。
3功能回贴:在这一步里,方面集成器通过创建一个模块单元—— 方面来指定重组的规则。重组过程——也叫织入或结合—— 则使用这些信息来构建最终系统。
 
推荐阅读书籍:
AspectJ in Action

AOSD中文版--基于用例的面向方面软件开发

 
 
推荐阅读的帖子:

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:753291次
    • 积分:8482
    • 等级:
    • 排名:第2281名
    • 原创:28篇
    • 转载:624篇
    • 译文:0篇
    • 评论:43条
    最新评论