AOP实现原理,手写aop

2021-11-30 12:07:26
代理与装饰器

代理即使替代的意思,可替代所有的功能,即和原来的类实现相同的规范。
代理模式和装饰器模式很像。
基础实现
给你一个咖啡接口
public interface Coffee{
  void printMaterial();
}

一个默认的咖啡实现

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

默认的点餐逻辑

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

 

装饰器模式
优雅的服务生把咖啡端上来了,抿了一口,有些苦。
想加点糖,对服务生说:您好,请为我加糖。
/**装饰器模式用来给咖啡加糖*/
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();
    }
}

注意这两行

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();
    }
}

差别

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

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

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

 

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

Coffee coffee = new CoffeeWithSugar();

很细微的差别。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

在装饰器模式中,持有的对象不是创建的,而是外部传入的。

AOP

设计模式是思想,代理模式不仅适用于接口便是和aop相关。

aop:Aspect Oriented Programming ,面向切面编程,是对面向对象编程的补充。、

我们会生成切面,即切在某方法之前、之后、前后,而springAop的实现就是代理模式。

场景

以短信验证码的例子。

public interface SMSService {

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

    @Override
    public void sendMessage() {
        System.out.println("您正在执行重置密码操作,您的验证码为:2333,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();
    }
}

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

AOP实现

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

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

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

jkd动态代理智能支持接口代理,通过实现接口,持有接口实现类的引用,动态扩展实现类的功能。

cglib动态代理

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

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

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

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

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

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

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

cglib是通过集成类,实现类的字类,重新父类的方法来实现静态代理

cglib继承被代理的类,重写方法,织入通知,动态生成字节码并运行,因为是继承所以final类是没有办法动态代理的。



 

posted @ 2021-11-30 13:55  啦啦拉扎尔  阅读(197)  评论(0编辑  收藏  举报