装饰者模式、代理模式与AOP

·  阅读 621
装饰者模式、代理模式与AOP

前言

Spring全家桶是当下Java开发中最锋利的一柄剑,IOC思想的产生大大降低了代码内的耦合。IOC被称为控制反转,最具有代表性的实现方式是DI(依赖注入),但我们今天不讲IOC(︿( ̄︶ ̄)︿我海王阿浪就是要闪断在座各位的腰),想了解IOC原理的可以翻翻我其它文章。

AOP(面向切面编程)是Spring核心思想之一,常用于日志管理、事务控制(取代Filter)、权限验证等。我们知道传统OOP面向对象编程,方法的调用方式都是垂直调用,会产生大量的冗余。而AOP主要为了将通用功能分离,使多个类共享。由此可看出来,两个编程范式是互补的关系。

1、代理 Proxy

在聊AOP之前阿浪想先梳理梳理代理的相关体系知识,因为AOP的底层实现就是依据动态代理来实现的。

代理是一种设计模式,通过代理对象访问目标对象,可以在目标对象功能实现的基础上,增加额外的功能,从而达到扩展目标对象功能的效果,其本质为了控制和管理访问。但日常开发过程中,经常容易把它和装饰者模式搞混。

1.1、静态代理

网上有很多介绍代理模式的文章,这张图表达的结构应该都很熟悉了,但有没有想过为何代理类与被代理类一定要实现一个相同接口?

下面是我个人理解:

  1. 为了实现多态,符合面向接口编程设计思想,依赖接口,而不依赖具体实现。
  2. 可以对它的用户隐藏目标对象的具体信息,使客户不知道代理委托了另一个对象,实现控制和管理访问。
  3. 用户只需关心接口功能,而不在乎谁提供了具体功能。

Talk is cheap,show me the code

public interface Noodle {
    void Material();
}
复制代码

首先创建了公共接口,然后根据接口创建一个实际处理功能方法的目标类:

public class ChongqingNoodle implements Noodle {
    @Override
    public void Material() {
        System.out.println("重庆小面");
    }
}
复制代码

代理类同时实现接口,并在编译阶段明确指定代理对象,静态代理的特点就是简单粗暴。

public class NoodleProxy implements Noodle {

    private Noodle noodle;

    public NoodleProxy() {
        //关系在编译时确定
        this.noodle = new ChongqingNoodle();
    }

    @Override
    public void Material() {
        noodle.Material();
        System.out.println("加辣!");
        System.out.println("加醋!");
    }
}
复制代码

模拟用户调用试试看效果:

public static void main(String[] args) {
        Noodle noodle = new NoodleProxy();
        noodle.Material();
    }
    
打印结果:
    重庆小面
    加辣!
    加醋!
复制代码

虽然静态代理起到了控制访问的功能,但缺点也显而易见。不易维护,一旦接口增加方法,目标对象与代理对象都要进行修改。那有没有更好的实现方法呢?

1.2、动态代理

静态代理需要手动编写代码让代理类Proxy实现接口。而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现接口的代理,而不需要去定义Proxy这个类。

动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件。

动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。

Java动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类)。每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中。

当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

public class DynamicProxy implements InvocationHandler {

    private Object target;

    private Integer num;        //辣度

    public DynamicProxy(Object target) {
        this.target = target;
        this.num = 0;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        num++;
        System.out.println("添加" + num + "勺辣椒!");
        return result;
    }
}

复制代码

写个测试看看效果:

public static void main(String[] args) {
        //动态代理
        Noodle noodle = new ChongqingNoodle();
        noodle = (Noodle) Proxy.newProxyInstance(ChongqingNoodle.class.getClassLoader(),
                                        ChongqingNoodle.class.getInterfaces(),
                                        new DynamicProxy(noodle));

        noodle.Material();
        noodle.Material();

    }
    
打印结果:
        重庆小面
        添加1勺辣椒!
        重庆小面
        添加2勺辣椒!
  
复制代码

1.3、装饰者模式

代理模式关注于控制对对象的访问,然而装饰器模式关注于在一个对象上动态的添加方法。

从结构图上看,装饰者模式和构造器模式非常相像,两者都实现了一个接口。但不同于代理的是装饰者与被装饰者通过构造器进行交互。

直接通过代码对比:

//公用接口
public interface Noodle {
    void Material();
}

//被装饰者
public class ChongqingNoodle implements Noodle {
    @Override
    public void Material() {
        System.out.println("重庆小面");
    }
}
复制代码

装饰者模式最大的特点就是装饰器的构造,而且允许多装饰器迭代装饰。

//装饰器一
public class VinegarDecorator implements Noodle {

    private Noodle noodle;

    public VinegarDecorator(Noodle noodle) {
        this.noodle = noodle;
    }

    @Override
    public void Material() {
        this.noodle.Material();
        System.out.println("加醋!");
    }
}

//装饰器二
public class SpiceDecorator implements Noodle {

    private Noodle noodle;

    public SpiceDecorator(Noodle noodle) {
        this.noodle = noodle;
    }

    @Override
    public void Material() {
        this.noodle.Material();
        System.out.println("加辣!");
    }
}
复制代码

客户必须指定装饰者需要装饰的是哪一个类,并且装饰者能够在运行时递归地被构造。

public static void main(String[] args) {
        Noodle noodle = new ChongqingNoodle();
        noodle = new VinegarDecorator(noodle);
        noodle = new SpiceDecorator(noodle);
        noodle.Material();
    }

打印结果:
    重庆小面
    加醋!
    加辣!
复制代码

通过上面的比较我们可以得出结论:

  • 静态代理只能编译时扩展目标类,装饰者可以运行时扩展目标类,动态代理为了

2、AOP与代理

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP会使用JDK Proxy去创建代理对象。

如果要代理的对象没有实现接口,就无法使用 JDK Proxy ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。

2.1、JDK Proxy

JDK Proxy就是我们上文提到的动态代理。

重要的方法

  //InvocationHandler接口类的invoke方法执行真正逻辑处理
  public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable
;

  //Proxy类就是用来创建一个代理对象的类,重要方法newProxyInstance
  public static Object newProxyInstance(ClassLoader loader, 
                                        Class<?>[] interfaces, 
                                        InvocationHandler h)
  
         throws IllegalArgumentException;
复制代码
  • loader:classloader对象,定义了由哪个classloader对象对生成的代理类进行加载

  • interfaces:interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。

  • h:InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

2.2、CGLib Proxy

通过结构图,看以看出CGLib Proxy与JDK Proxy有很明显的差异。

代理类继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类该方法的逻辑。

首先创建一个没有实现任何接口的实体类:

public class Wife {

    public String amorous(String express) {
        return "MyWife :" +express;
    }
}
复制代码

首先实现MethodInterceptor接口,方法调用会被转发到该类的intercept()方法。

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("I say:" + Arrays.toString(objects));
        return methodProxy.invokeSuper(o, objects);
    }
}
复制代码

JDK Proxy提供一个Proxy类来创建代理类,而CGLib Proxy也提供了一个类似的类Enhancer;

public class CGlibProxy {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Wife.class);
        enhancer.setCallback(new MyMethodInterceptor());

        Wife wife = (Wife)enhancer.create();
        System.out.println(wife.amorous("I love you!"));
    }
}

打印输出:
      I say:[I love you!]
      MyWife :I love you!
复制代码

上述代码中,我们通过CGLib的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象。

对代理对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;

通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象。CGLib中MethodInterceptor的作用跟JDK Proxy中的InvocationHandler很类似,都是方法调用的中转站。


阿浪技术还很菜,文章内可能存在错误,欢迎各位大佬指正。


分类:
后端
标签:
分类:
后端
标签: