AOP思想个人理解以及实战静态代理、JDK动态代理、CGlib动态代理

30 篇文章 0 订阅
15 篇文章 0 订阅

学习心路:

是什么??能干什么??怎么做???最高境界:为什么做??

AOP的介绍

AOP(Aspect-Oriented Programming,面向切面编程[面向方面编程]),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。

OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。

也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。

例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。

这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP就是来解决这个问题的。

他能够把那些重复的代码给抽取出来,并且把它们应用到任何需要它的地方。

比如我这里有一个统计代码运行效率的需求:

代码很好实现:

public class Dosomething{

    public void showDo(){
        System.out.println("记时开始!!!");

        do();

        System.out.println("记时结束!!!");
    }
}

如果一个方法要统计还好,如果有很多方法需要统计呢??

我们以前的办法就是把统计的代码copy放到其它要统计的里面去。

现在有了AOP思想(注意只是思想),我们只需要把它抽离出来。这个抽离出来的代码在AOP里面叫做 Advice(增强) ,而需要我们这些代码统计运行效率的方法叫做 Pointcut(切入点)

我们通过AOP思想的实现方式 把代码写入到 需要统计效率的方法(Pointcut),写入的过程就叫做Aspect(切面)

增强点:

  • 前置通知:在方法之前执行
  • 后置通知:在方法之后执行
  • 异常通知:方法出现异常
  • 最终通知:在后置之后执行
  • 环绕通知:在方法之前和之后执行

AOP思想比较官方的说法:

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。

业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。

横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。

Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

AOP思想的实现技术:

实现AOP的技术,主要分为两大类:

1.采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
2.采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的

主要方式用三种:

1.静态代理
下面是动态代理的两种方式:
2.JDK 动态代理
3.CGLib 动态代理

OOP回顾

OOP(Object Oriented Programming)。OOP主要是为了实现编程的重用性、灵活性和扩展性。它的几个特征分别是继承、封装、多态和抽象。OOP重点体现在编程架构,强调的是类之间的层次关系。

OOP缺陷

为了更好的说明OOP的概念,我们接下来讲一个OOP的实例,重点分析OOP存在哪些缺陷,以便更好的理解AOP的相关内容。

这里写图片描述

上面这张图有三个类:Dog,Cat和Duck,他们都有一个方法run。按照OOP的设计理念,我们很容易就会想到抽象出一个Animal父类,同时让这三个子类继承Animal父类。这样的设计可以用如下的图示表示:

这里写图片描述

需求:动物园动物的表演(这里的Animal是动物园的动物),当驯兽师说跑后,狗才跑,跑完后驯兽师给狗食物。

用OOP思想很好写:

public interface Animal {
    void run(String animalType);
}
public class DogImpl implements Animal {

    @Override
    public void run(String animalType) {
        System.out.println("驯兽师命令狗跑!!");
        System.out.println("Dog run!!!");
        System.out.println("驯兽师给奖励狗吃东西!!");
    }
}

如果现在有另外一个物种,比如猫,要表演,驯兽师让猫跑,跑后驯兽师奖励猫吃东西。是不是还要这样写呢??很明显这些代码是存在冗余的。从OOP角度是不能够完美解决这个问题的。

于是这里就得靠AOP了。

静态代理:

这是最简单方便的一个AOP实现。我们为动物园Animal写一个代理类。我们把Animal的实现类里面不必要的逻辑抽取出来,放到代理类里面。

动物就只专心跑:

public class DogImpl implements Animal {

    @Override
    public void run(String animalType) {
        System.out.println("Dog run!!!");
    }
}

代理类:

public class AnimalProxy implements Animal {

    private Animal animal;

    public AnimalProxy(Animal animal) {
        this.animal=animal;
    }

    @Override
    public void run(String animalType) {
        System.out.println("驯兽师命令狗跑!!");
        animal.run(animalType);
        System.out.println("驯兽师给奖励狗吃东西!!");
    }

}

测试代码:

public class Client {
    public static void main(String[] args) {
        Animal animalProxy=new AnimalProxy(new DogImpl());
        animalProxy.run("狗狗");
    }
}

代码间很明显体现着,抽取不必要的通用逻辑(增强Advice),把通用逻辑放到了指定的切入点(Pointcut),形成了切面(Aspect)。代码完美耦合!!不冗余!!

这样就能够让猫,熊等动物到动物园里面好好玩耍吃东西了。代理类一般在我们写小东西的时候又不想麻烦可以用一用。

但是我们现在给动物吃的食物吃完了:

1.驯兽师发现给动物奖励的食物快吃完了
2.工作人员补充食物
3.驯兽师很高兴食物又有了,能够好好让动物表演了。

上面这个代码逻辑又该怎么实现呢??

这里引入了一个工作人员,我们用静态代理很容易实现,定义一个动物园Human接口,给动物园Human创建一个代理类。等等。但是如果又出现了其它的问题,我们又会定义无穷无尽的代理类。

那么有什么办法只定义一个代理类就能够解决上述的问题呢??

那就是用动态代理了:

JDK 动态代理

这里就简单介绍些JDK动态代理加上动态执行方法,这里的动态执行方法就写AOP里面的前置方法和后置方法。

动态代理只能代理有接口的子类。

定义一个接口:

public interface Animal {
    void run(String animalType);
}

抽象出我们动物园里面的一个狗狗:

public class DogImpl implements Animal {

    @Override
    public void run(String animalType) {
        System.out.println("Dog run!!!");
    }
}

定义一个动态任务的接口:

public interface Mession {
    public Object before(Object ...args);
    public Object after(Object ...args);
}

准备工作已经做完了,下面就通过动态代理的方式来实现AOP核心思想:

我们可以知道上面定义的接口就是我们通过AOP思想抽离出来的逻辑。叫做Advice(增强)

而我们要增强的方法叫做Pointcut(切入点)

我们要做的就是把增强放入切入点。这就是动态代理要做的!!

定义一个类实现动态代理的接口(InvocationHandler):

public class MyInvocation implements InvocationHandler {
    private Object target;//目标增强类
    private Mession mession;//增强类

    public MyInvocation(Object target,Mession mession) {
        //初始化
        this.target=target;
        this.mession=mession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        mession.before();//执行增强方法
        Object result=method.invoke(target, args);//执行目标类的核心逻辑
        mession.after();//执行增强方法
        return result;
    }

}

测试代码:

public class Client {

    public static void main(String[] args) {
        Class[] cs={Animal.class};
        Object objectProxy=Proxy.newProxyInstance(Animal.class.getClassLoader(), cs, new MyInvocation(new DogImpl(),new Mession() {
            public Object before(Object ...arg){
                System.out.println("前置方法"+arg.length);
                return null;
            }
            public Object after(Object ...arg){
                System.out.println("后置方法"+arg.length);
                return null;
            }
        }));
        ((Animal)objectProxy).run("狗狗");
    }
}

不管你是否理解,按照模板这样来就能够创建一个代理类,实现一个代理类代理所有通用逻辑和商务逻辑的分离。

现在我们就不用给每一种类型的对象写一个代理类了,实现了静态代理的简化。

下面介绍一下升级版:

都是笔者个人的想法:

把增强的接口多定义几个方法:

public interface Mession {
    public Object before(Object ...args);
    public Object after(Object ...args);
    public Object afterReturning(Object ...args);
    public Object catchError(Object ...args);
}

动态代理类这样写:

public class MyInvocation implements InvocationHandler {
    private Object target;
    private Mession mession;

    public MyInvocation(Object target,Mession mession) {
        this.target=target;
        this.mession=mession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=null;
        try {
            mession.before();
            result=method.invoke(target, args);
            mession.after();

        } catch (Exception e) {
            mession.catchError();
        }finally {
            mession.afterReturning();
        }
        return result;
    }

}

测试类:

public class Client {

    public static void main(String[] args) {
        Class[] cs={Animal.class};
        Object objectProxy=Proxy.newProxyInstance(Animal.class.getClassLoader(), cs, new MyInvocation(new DogImpl(),new Mession() {
            public Object before(Object ...arg){
                System.out.println("前置方法"+arg.length);
                return null;
            }
            public Object after(Object ...arg){
                System.out.println("后置方法"+arg.length);
                return null;
            }
            @Override
            public Object afterReturning(Object... args) {
                System.out.println("最终方法"+args.length);
                return null;
            }
            @Override
            public Object catchError(Object... args) {
                System.out.println("异常捕获方法"+args.length);
                return null;
            }
        }));
        ((Animal)objectProxy).run("狗狗");
    }
}
/*
输出:

前置方法0
Dog run!!!
后置方法0
最终方法0

*/

这样就感觉非常Nice!!但是还是没有Spring的实现的完美,但是如果这样实现,在我们做些小东西的情况下也是非常轻量级和绿色的。

缺点:JDK动态代理只能增强那些拥有接口的实现类。

这里必须要介绍的是在动态代理类实现过程中涉及的参数

Proxy类的newInstance()方法有三个参数:

1.ClassLoader loader:它是类加载器类型,
2.Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子只我们只指定了一个接口:Class[] cs={Animal.class};
3.InvocationHandler:它是最重要的一个参数!它是一个接口!它的名字叫调用处理器!你想一想,上面例子中objectProxy对象是Animal接口的实现类对象,那么它一定是可以调用run方法了。难道你不想调用一下run方法么,它会执行些什么东东呢?其实无论你调用代理对象的什么方法,它都是在调用InvocationHandler的invoke()方法!,除了调用getClass()方法不会调用invoke()方法。

InvocationHandler的invoke()方法的参数有三个:

1.Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它;
2.Method method:表示当前被调用方法的反射对象,例如((Animal)target).run(),那么method就是run()方法的反射对象;
3.Object[] args:表示当前被调用方法的参数,当然((Animal)target).run()这个调用是没有参数的,所以args是一个零长数组。

我们常常会出现的异常

一般我们要注意注意注意一点:

proxy这个代理对象参数我们不能这样用:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=null;
        try {
            ((Animal)proxy).run("Proxy..........");
            mession.before();
            result=method.invoke(target, args);
            mession.after();

        } catch (Exception e) {
            mession.catchError();
        }finally {
            mession.afterReturning();
        }
        return result;
    }

这样用会报内存溢出。因为这是一个无限的递归过程,因为我们前面说过,当我们通过代理对象调用一次Animal接口中的方法的时候就会来调用这里的invoke(…)方法,调用了又调用,这就形成了无限的递归过程。

所以我们一般不会用proxy这个代理对象!!!

CGlib动态代理

实现CGlib动态代理的第一步是导包:

下载地址

CGlib能够用在Java普通项目中等其它地方。

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

在上面我们对动物园的动物进行了JDK的动态代理,下面我们用CGlib进行替代:

比如这里有一条小狗。

public class DogImpl implements Animal {

    @Override
    public void run(String animalType) {
        System.out.println("Dog run!!!");
    }
}

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。
intercept()方法拦截所有目标类方法的调用,target表示目标类的实例(不是单纯的父类实例),method为目标类方法的反射对象,args为方法的动态入参,methodProxy为代理类实例。
proxy.invokeSuper(target, args)通过代理类调用父类中的方法。

代理类对象这样写:

public class MyCglibProxy implements MethodInterceptor{
    private Enhancer enhancer=new Enhancer();
    private Mession mession;

    public Object getProxy(Class clazz,Mession mession){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        this.mession=mession;
        return enhancer.create();
    }

    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        try {
            mession.before();
            methodProxy.invokeSuper(target, args);
            mession.after();
        } catch (Exception e) {
            mession.catchError();
        }finally {
            mession.afterReturning();
        }
        return null;
    }
}

测试代码:

public class Client {
    public static void main(String[] args) {
        MyCglibProxy myCglibProxy=new MyCglibProxy();
        DogImpl dogImplProxy=(DogImpl)myCglibProxy.getProxy(DogImpl.class,new Mession() {

            public Object before(Object ...arg){
                System.out.println("前置方法"+arg.length);
                return null;
            }
            public Object after(Object ...arg){
                System.out.println("后置方法"+arg.length);
                return null;
            }
            @Override
            public Object afterReturning(Object... args) {
                System.out.println("最终方法"+args.length);
                return null;
            }
            @Override
            public Object catchError(Object... args) {
                System.out.println("异常捕获方法"+args.length);
                return null;
            }
        });
        dogImplProxy.run("小狗");
    }
}
/*


输出:

前置方法0
Dog run!!!
后置方法0
最终方法0
*/

CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值