开启掘金成长之旅!这是我参与「掘金日新计划 · 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的一个核心工作就是解耦,将分散在其他类的重复逻辑抽取为一个切面(面向切面变成),这里就是本篇要说的了。