AOP的由来

114 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情

【servlet三层架构时代】

遇到了需求变更:你给别人做的系统,系统里的积分模块有几个人异常高,人家想要你把积分的变动都记录下来

需求变更与日志引入

打开代码后发现需要修改的地方太多了(基本上每一个积分操作函数都需要加日志)。假设我们的service(DemoService接口)有find、add、sub、mul、div五个方法,每次积分变动我们都需要将它的类名、方法名、参数都打印出来(其中find是个普通的查询方法,加不加日志无所谓)。

一般我们会这么做:

public int add(String userId, int points) {
        //日志==================================
        System.out.println("DemoServiceImpl add ...");
        System.out.println("user: " + userId + ", points: " + points);
        //===============其他操作=====================
        return points;
    }
 public int sub(String userId, int points) {...}
//......

这些日志的操作逻辑基本上是一样的,我们可以将它抽成一个工具类:

public class LogUtils {
    //public static void printLog(String className, String methodName,String userId, int points){}
    //这工具类做的也太离谱了,就单纯的为了这一个需求搞,不大合适,我可以做成通用的
    public static void printLog(String className, String methodName, Object... args){
        System.out.println(className + " " + methodName + "...");
        System.out.println("参数列表: " + Arrays.toString(args));
    }
}

修改完成之后,想要再哪里加日志就直接LogUtils.printLog就行了。似乎不错,但方法多了,每个方法都要加还是很麻烦的。另外当service代码多了之后,和这些日志操作混在一起会很臃肿。

引入设计模式

你觉得还是不太行,于是你去尝试使用你学过的一些设计模式看能不能解决这个问题。

装饰者设计模式【回顾】

装饰者设计模式:假设有一块蛋糕,如果只涂上奶油,就是奶油蛋糕。如果加上草莓就是草莓奶油蛋糕。如果再加上巧克力,插上蜡烛就是生日蛋糕。像这样不断地为对象添加装饰的设计模式就是Decorator模式。

该模式下的角色:

  • Component:增加功能时的核心角色,装饰前的蛋糕就是这个角色。Component角色定义了蛋糕的接口api。
  • ConcreteComponent:该角色是实现了Component角色所定义的接口api的具体蛋糕。
  • Decorator装饰物:该角色具有与Component角色相同的api接口。在它内部保存了被装饰对象--component角色。
  • ConcreteDecorator(具体的装饰物):该角色是具体的Decorator角色。
装饰者设计模式使用

我们的装饰者需要实现和我们的service相同的接口(DemoService),在构造方法中传入要修饰的对象即可。在具体方法实现前,我们先做日志记录,然后调用修饰对象的原方法就行了。

public class DemoServiceDecorator implements DemoService {
    private DemoService target;
​
    // 构造方法中需要传入被装饰的原对象
    public DemoServiceDecorator(DemoService target) {
        this.target = target;
    }
​
    @Override
    public List<String> findAll() {
        return target.findAll();
    }
​
    @Override
    public int add(String userId, int points) {
        // 在原对象执行方法之前打印日志,完成日志与业务逻辑的分离
        LogUtils.printLog("DemoService", "add", userId, points);
        return target.add(userId, points);
    }
  //...........sub、mul、div.....
}

如果要加什么操作,反复装饰就行了,但是我们也很容易就发现了它的弊端:每个业务接口都要写一个装饰器,代码量太大了。

模板设计模式

模板设计模式:父类定义抽象方法与处理流程,子类进行具体实现。

思路:我们在定义处理流程时,在业务方法执行前加入日志方法就行。

但这种只能继承,并且代码量依然很大,不够灵活。

代理模式

代理模式分为静态代理和动态代理两种。静态代理需要自己编写代理类,组合原有的对象,实现原有目标对象实现的接口,以此来做到对原有对象的方法功能的增强;动态代理只编写增强逻辑类,在运行时动态将增强逻辑类组合进原有的目标对象,即可生成代理对象,完成对目标对象的方法功能增强。

静态代理讲解juejin.cn/post/710093…

jdk动态代理讲解juejin.cn/post/710155…

jdk动态代理生成代理对象

Class<? extends DemoService> clazz = demoService.getClass();
this.demoService = (DemoService) Proxy
  .newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), (proxy, method, args) -> {
    LogUtils.printLog("DemoService", method.getName(), args);
    return method.invoke(demoService, args);
  });

AOP就是这样一步一步演化过来的,我们可以感受到:AOP的一个核心工作就是解耦,将分散在其他类的重复逻辑抽取为一个切面(面向切面变成),这里就是本篇要说的了。