AOP入门(一)

来源:互联网 发布:雅思作文批改 知乎 编辑:程序博客网 时间:2024/05/20 19:32

    网上由很多关于AOP的解释,但是,它最大的好处是什么,它的作用是什么?

    众说纷纭,有的解释甚至很扯淡。例如,百度百科上有这样一段:

     

     

    这给我的感觉就是——人类的主要功能:吃、喝、玩、乐。   -_-!

     

    要解释这个问题,得先回答,为什么要有AOP

     

    AOP的历史

     

    有人说AOP(Aspect Oriented Program)是基于OOP (Object Oriented Program)来的,这个,我感觉貌似无法考究。而且即便是传统的Structured Program也未可不能进化出AOP的用法。因为它只是一个思想。我们来看,这个思想具体是什么。

     

 

    有如下代码:

     

    package com.edi.poc;

     

    public interface Hall {

    void open();

    void close();

    void visit(Entrant e);

    }

     

    package com.edi.poc.ifc;

     

    public enum HALL_NAME {

     

    DINOSAUR, CAT;

    }

     

    package com.edi.poc.ifc;

     

    import com.edi.poc.Zoo;

     

    public interface Entrant {

     

    String getName();

     

    void visit(Zoo zoo, HALL_NAME hallName);

    }

     

    package com.edi.poc;

     

    import com.edi.poc.ifc.Entrant;

    import com.edi.poc.ifc.HALL_NAME;

     

    public class Tourer implements Entrant {

     

    private String name;

     

    public Tourer(String name)

    {

    this.name = name;

    }

     

    public boolean isCharged() {

    return false;

    }

     

    public String getName() {

    return name;

    }

     

    public void visit(Zoo zoo, HALL_NAME hallName) {

    zoo.enter(this, hallName).visit(this);

    }

     

    }

     

    package com.edi.poc;

     

    import java.util.HashMap;

    import java.util.Map;

     

    import com.edi.poc.ifc.HALL_NAME;

    import com.edi.poc.ifc.Hall;

    import com.edi.poc.ifc.Entrant;

     

    public class Zoo {

     

    private String name;

    private Map<HALL_NAME, Hall> halls;

     

    public Zoo(String name)

    {

    this.name = name;

    this.halls = new HashMap<HALL_NAME, Hall>();

    halls.put(HALL_NAME.DINOSAUR, new DinoHall());

    }

     

    public void open()

    {

    for(Hall h:halls.values())

    {

    h.open();

    }

    System.out.println("The "+ name + " Zoo " + "is opened.");

    }

     

    public void close()

    {

    for(Hall h:halls.values())

    {

    h.close();

    }

    System.out.println("The "+ name + " Zoo " + "is closed.");

    }

     

    public Hall enter(Entrant e, HALL_NAME hallName)

    {

    return this.halls.get(hallName);

    }

     

    public String getName() {

    return name;

    }

     

    public void setName(String name) {

    this.name = name;

    }

     

    public static void main(String[] args) {

    Entrant jack = new Tourer("jack");

    Zoo zoo = new Zoo("People");

    zoo.open();

    jack.visit(zoo, HALL_NAME.DINOSAUR);

    zoo.close();

    }

    }

     

    典型的OOP。实现了一个动物园开放和关门,以及一个游客Jack到恐龙馆晃了一圈。

    输出

    Dinosaur hall is opened.

    The People Zoo is opened.

    jack visited diano hall.

    Dinosaur hall is closed.

    The People Zoo is closed.

     

    现在,新的需求来了,动物园得赚钱才能维持,得收费。怎么办?有人说,那就在开放时,加一个收费站收门票费。

    游客不干了,我来是看恐龙的,不是看猫的。我家就有猫,就算没你这品种多,也不能跟恐龙一个价啊。

    场馆也不干了,恐龙馆说,我养只恐龙喂的东西,跟猫馆不一个价啊。恩,所以,只能让每个馆自己定价,进门收门票费,进馆收馆费(为了简单起见,我就不再封装一个类去抽象票了)

    public interface Hall {

     

    ….

    void charge(Entrant e);

    }

     

    public interface Entrant {

     

    ….

    boolean isCharged();

     

    public void setCharged(boolean isCharged);

    }

     

    public class DinoHall implements Hall {

    public void visit(Entrant e) {

    if(!e.isCharged())

    {

    System.out.println(e.getName() + " needs to be charged first.");

    charge(e);

    }

    System.out.println(e.getName() + " visited diano hall.");

    }

    }

     

    public void charge(Entrant e) {

    e.setCharged(true);

    System.out.println("Dianosaur hall charges " + e.getName() + " $2.00");

    }

    }

     

    为了加入这个功能,我们修改了Hall Entrant的接口,并且所有Hall的实现类都需要实现charge()方法。显然,这里出现了代码冗余。

    假如一个zoo20个馆,20个馆的Hall实现类就有20charge()方法,里面除了金额不同外,完全一样。

    如果解决呢?一种方式是剥离charge,在zoo.enter()的时候去收费,然后封装一个ticket类,里面用Collection来表示哪些馆已经买了票。这样在进馆时就只检查该Collection中是否包含该馆就行了。

    这是什么?封装!  OOP做法。显然这样可以解决这个需求了,但是,wait,这样就没问题了吗?

     

    mmm…动物园引入了IBM的咨询(可参看IBM关于企业咨询的广告),想借此提高利润。IBM的咨询师们需要搜集数据,想获得整个动物园当天、实时的人流量,每个场馆的人流量,每个场馆的实时income等。怎么办?

    有人说,简单,把这些东西封装成一个属性加到几个BEAN中去呗。跟收费一样。

    但是,客流量、实时收入,这些东西是一个动物园或者场馆的有效属性吗?我们说抽象一个对象,需要考虑它的有效属性。否则,人这个抽象对象有可能就包含身份证编号。但其实这个东西是属于一个外部系统的。

     

    好,那么,我们就可以把这些数据封装成另外一个对象,例如ConsultingData,在ZooHall里面调用下这个封装类的更新方法。

    OK了吗?永远不要低估一个咨询师对于信息的需求,当你辛苦改完代码后,他们会又要求你搜集更多的数据!

    怎么办?继续封装?那么这个ZooHall的实现类就会变得无法maintain了,被强制加入了很多它自己完全不是非常有关系的方法调用。

     

    这个时候,有经验的工程师会自然想到——设计模式。可以用代理模式来把它做的很好。

    典型代码:

    package com.edi.poc;

     

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    import java.lang.reflect.Proxy;

     

    import com.edi.poc.ifc.Hall;

     

    public class JdkProxy implements InvocationHandler{

     

    private Object target;

     

    public Object getProxy(Object target)

    {

    this.target = target;

     

    return Proxy.newProxyInstance(

    target.getClass().getClassLoader(),

    target.getClass().getInterfaces(),

    this);

    }

     

    public Object invoke(Object proxy, Method method, Object[] args)

    throws Throwable {

    System.out.println("Before ...");

    // Add those operations whatever you want before it really goes

    Object result = method.invoke(target, args);

    // Add those operations whatever you want before it really leave

    System.out.println("After ...");

    return result;

    }

     

    public static void main(String[] args) {

    Hall dinoHall = new DinoHall();

    JdkProxy proxy = new JdkProxy();

    Hall dinoHallProxy = (Hall)proxy.getProxy(dinoHall);

    dinoHallProxy.open();

    }

    }

     

    输出:

    Before ...

    Dinosaur hall is opened.

    After ...

     

    这样,我们就可以把统计的那些需求跟原始代码剥离开来。该例子使用的是JDK提供的动态代理。但它必须要求被代理类要有接口!

     

    这种该法,其实就是AOP的本质——将流程操作截开,以拦截器的方式,实现切片组装,以灵活加入更多逻辑。

    说白了——代理调用。

     

    正常调用:

     


     

    拦截调用:

     


     

    回头来再看看,有了AOP,或者根本上说,代理模式调用,为我们带来了什么好处:

  1. 原始代码简洁,没有因为各种集成造成的复杂冗余。(想想这时候的zoohall是多么的纯粹)
  2. 根据需要灵活插入各种业务逻辑IBM咨询师一走,就不用搜集数据了,果断把这些插件类去掉?)
  3. 被插入的逻辑处理类也显得很纯粹而易于维护

     

    凡事有利必有弊,我们再看看(可能)坏处:

  4. 原始流程被打断,在阅读时,增加理解难度
  5. 调用代理类开销可能比直接调用原始类要大 (取决于实现)

     

    第一个显然只能通过各种文档和友好的注释来尽量解决。第二个的话,已经开发出相应工具解决掉了。

    目前代理的方法有三类:

  6. 运行时动、静态代理 (jdk代理类,cglib)
  7. 加载类时代理
  8. 编译时代理

     

    后两者,b会造成加载缓慢,c会造成编译缓慢。通常,我们会选择c,但实际上两者差距并不是很大。可参见一篇专业performance比对文章(需要翻墙),或者直接看下面图。


     

    但其本质是一样的,都是帮我们把这些拦截插入的代码和原始代码拼接到一起,然后加载入JVM

     

    本文源码:

    https://github.com/EdisonXu/POC/tree/master/intro-aop