转载

装饰器、代理模式与Spring AOP

引言

翻开 to-do ,注解认证中答应大家要讲解代理模式。

装饰器、代理模式与Spring AOP

正好遇到了一道这样的题:抛开 Spring 来说,如何自己实现 Spring AOP ?

就喜欢这样的题,能把那些天天写增删改查从来不思考的人给 PK 下去,今天就和大家一切学习代理模式与 Spring AOP

代理与装饰器

场景描述

代理,即替代之意,可替代所有功能,即和原类实现相同的 规范

代理模式和装饰器模式很像,之前的装饰器讲的不是很好,这里换个例子再讲一遍。

宁静的午后,来到咖啡馆,想喝一杯咖啡。

基础实现

给你一个咖啡接口:

public interface Coffee {

    /**
     * 打印当前咖啡的原材料,即咖啡里有什么
     */
    void printMaterial();
}

一个默认的苦咖啡的实现:

public class BitterCoffee implements Coffee {

    @Override
    public void printMaterial() {
        System.out.println("咖啡");
    }
}

默认的点餐逻辑:

public class Main {

    public static void main(String[] args) {
        Coffee coffee = new BitterCoffee();
        coffee.printMaterial();
    }
}

点一杯咖啡。

装饰器、代理模式与Spring AOP

装饰器模式

优雅的服务生把咖啡端了上来,抿了一口,有些苦。

想加点糖,对服务生说:“您好,请为我的咖啡加些糖”。

/**
 * 糖装饰器,用来给咖啡加糖
 */
public class SugarDecorator implements Coffee {

    /**
     * 持有的咖啡对象
     */
    private final Coffee coffee;

    public SugarDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public void printMaterial() {
        System.out.println("糖");
        this.coffee.printMaterial();
    }
}

然后服务生就拿走了我的咖啡,去使用 SugarDecorator 为咖啡加糖,最后把加好糖的咖啡给我。

public class Main {

    public static void main(String[] args) {
        Coffee coffee = new BitterCoffee();
        coffee = new SugarDecorator(coffee);
        coffee.printMaterial();
    }
}

看一看咖啡的成分,对的,确实加上了糖!

装饰器、代理模式与Spring AOP

注意看这两行:

Coffee coffee = new BitterCoffee();        // 点了一杯苦咖啡
coffee = new SugarDecorator(coffee);       // 给咖啡加了点糖

装饰器模式适合什么场景,我有一个对象,但是这个对象的功能不能令我满意,我就拿装饰器给他装饰一下。

代理模式

周末了,又抱着 iPad 来到了咖啡馆,准备享受一个宁静的下午。

“先生,请问您要喝点什么?”一旁礼貌的服务生上前问道。

上次点的咖啡太苦了,这次直接要个加糖的吧。

“我要一杯加糖咖啡。”

public class CoffeeWithSugar implements Coffee {

    private final Coffee coffee;

    public CoffeeWithSugar() {
        this.coffee = new BitterCoffee();
    }

    @Override
    public void printMaterial() {
        System.out.println("糖");
        this.coffee.printMaterial();
    }
}

这是加糖咖啡,其实内部仍然是咖啡,只是加了些配方,就产生了一种新类,一种新的可以在菜单上呈现的饮品。

点咖啡:

public class Main {

    public static void main(String[] args) {
        Coffee coffee = new CoffeeWithSugar();
        coffee.printMaterial();
    }
}

正合我意,在咖啡的陪伴下度过了一个美好的下午。

装饰器、代理模式与Spring AOP

差别

故事讲完了,两者实现的都是对原对象的包装,持有原对象的实例,差别在于对外的表现。

装饰器模式:点了咖啡,发现太苦了,不是自己想要的,然后用装饰器加了点糖。

Coffee coffee = new BitterCoffee();
coffee = new SugarDecorator(coffee);

代理模式:直接就点的加糖咖啡。

Coffee coffee = new CoffeeWithSugar();

很细微的差别,希望大家不要弄混。

批评

去看代理模式相关的资料,五花八门,怎么理解的都有。

我觉得菜鸟教程的代理模式解释的最为正宗:在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

装饰器、代理模式与Spring AOP

还有,网上许多设计模式的文章都是你抄我、我抄你,一个错了,全都错了。

我觉得我需要纠正一下。谁说代理模式一定要用接口的啊?代理模式时设计模式,设计模式不分语言,假如一门语言中没有接口,那它就不能代理模式了吗?只是 Java 中的接口可以让我们符合依赖倒置原则进行开发,降低耦合。用抽象类可以吗?可以。用类继承可以吗?也可以。

思想明白了,用什么写还不是像玩一样?

AOP

设计模式是思想,所以我上面说的代理模式不是仅适用于接口便与 Spring AOP 息息相关。

AOPAspect Oriented Programming ,面向切面编程,是面向对象编程的补充。如果你不明白这句话,好好去学学面向对象就知道为什么了。

我们会声明切面,即切在某方法之前、之后或前后都执行。而 Spring AOP 的实现就是代理模式。

场景

正好最近写过短信验证码,就拿这个来当例子吧。

public interface SMSService {

    void sendMessage();
}
public class SMSServiceImpl implements SMSService {

    @Override
    public void sendMessage() {
        System.out.println("【梦云智】您正在执行重置密码操作,您的验证码为:1234,5分钟内有效,请不要将验证码转发给他人。");
    }
}

主函数:

public class Main {

    public static void main(String[] args) {
        SMSService smsService = new SMSServiceImpl();
        smsService.sendMessage();
        smsService.sendMessage();
    }
}

费用统计

老板改需求了,发验证码要花钱,老板想看看一共在短信上花了多少钱。

正常按 Spring 的思路,肯定是声明一个切面,来切发短信的方法,然后在切面内统计短信费用。

只是现在没有框架,也就是这道题:抛开 Spring 来说,如何自己实现 Spring AOP ?

写框架考虑的自然多些,我上文讲的代理是静态代理,编译期间就决定好的。而框架实现却是动态代理,需要在运行时生成代理对象,因为需要进行类扫描,看看哪些个类有切面需要生成代理对象。

JDK 动态代理

编写一个统计短信费用的类实现 InvocationHandler 接口。

写到这,终于明白为什么每次后台 Debug 的时候都会跳转到 invoke 方法。

public class MoneyCountInvocationHandler implements InvocationHandler {

    /**
     * 被代理的目标
     */
    private final Object target;

    /**
     * 内部存储钱的总数
     */
    private Double moneyCount;

    public MoneyCountInvocationHandler(Object target) {
        this.target = target;
        this.moneyCount = 0.0;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target, args);
        moneyCount += 0.07;
        System.out.println("发送短信成功,共花了:" + moneyCount + "元");
        return result;
    }
}

将主函数里的 smsService 替换为使用 MoneyCountInvocationHandler 处理的代理对象。

public class Main {

    public static void main(String[] args) {
        SMSService smsService = new SMSServiceImpl();
        smsService = (SMSService) Proxy.newProxyInstance(Main.class.getClassLoader(),
                                            new Class[]{SMSService.class},
                                            new MoneyCountInvocationHandler(smsService));
        smsService.sendMessage();
        smsService.sendMessage();
    }
}

装饰器、代理模式与Spring AOP

根据 InvocationHandler 中的 invoke 方法动态生成一个类,该类实现 SMSService 接口,代理对象,就是用这个类实例化的。

装饰器、代理模式与Spring AOP

AOP 实现

上面的都实现了?写一个 AOP 是不是也不是难事?

主函数的代码,应该放在 IOC 容器初始化中,扫描包,去看看哪些个类需要生成代理对象,然后构造代理对象到容器中。

然后在 invoke 方法里,把统计费用的逻辑改成切面的逻辑不就好了吗?

不足分析

结束了吗?当然没有,上面的方法实现仅对接口有效。

因为 JDK 的动态代理,是生成一个实现相应 接口 的代理类。但是 Spring 又不是只能通过接口注入。

@Autowired
private Type xxxxx;

Spring@Autowired 是通过声明的类型去容器里找符合的对象然后注进来的,接口是类型,类不也是类型吗?

@Autowired
private SMSService smsService;

这样能注进来。

@Autowired
private SMSServiceImpl smsService;

这样呢?也能注进来。

所以, JDK 动态代理针对直接注入类类型的,就代理不了。

cglib 动态代理

自古以来,从来都是时势造英雄,而不是英雄创造了时代。

出现了问题,自然会有英雄出来解决。拯救世界的就是 cglib

JDK 动态代理解决不了的,统统交给 cglib

就这个来说:

@Autowired
private SMSServiceImpl smsService;

不是使用接口注入的, JDK 动态代理解决不了。 cglib 怎么解决的呢?它会根据当前的类,动态生成一个子类,在子类中织入切面逻辑。

然后使用子类对象代理父类对象。这就是为什么我上面说:代理模式,不要拘泥于接口。

所以织入成功的,都是子类能把父类覆盖的方法。

所以 cglib 也不是万能的,方法是 final 的,子类重写不了,它当然也无计可施了。

总结

读书读的是什么?是真正理解作者的思想,明白作者想歌颂什么、批判什么。

框架学的是什么?不只是为了提高开发效率,而是在使用的时候,就像与设计者交流一样,能真正明白框架设计者的思想,才算用明白一款框架。

如果我们都能做到这般,又何愁设计不出一款真正属于自己的框架呢?

原文  https://segmentfault.com/a/1190000019148468
正文到此结束
Loading...