当前位置:网站首页>6.统一记录日志
6.统一记录日志
2022-08-02 14:10:00 【鱼子酱:P】
能否使用控制器通知来进行日志的统一记录,答案是不行的。因为控制器通知是在控制器发生异常的时候才统一的处理,而这里的统一记录日志并不是只在发生异常实现,平时不发生异常也要记录日志,所以这个时候控制器通知就不太管用了。
那能否使用拦截器呢,拦截器也是针对控制器的一个处理,那我们记录日志未必是对控制器进行记录,也可能是针对业务组件以及数据访问层进行日志记录。
目前有帖子模块,评论模块,将来越来越多service,想要对所有帖子所有方法记日志,那传统的方法我们是把记录日志的代码封装到一个组件,然后在不同的service里的方法去调用它,这样肯定是可以解决问题的,
不过它存在弊端:这个业务组件里的方法主要是用来处理业务的,而在处理业务的同时,又需要去加入记录日志的需求,而记录日志又不是业务需求,它属于系统需求(很多功能都具备的一种需求),所以我们在业务方法里耦合了系统需求,这样是有很大坏处:比如有一天系统需求发生变化,不想在前面记日志想在后面记日志,或者抛异常时候记日志,一旦发生变化要很大改动。(所以我们需要将像记录日志这样的系统需求拆分出去,单独去实现而不是硬编码,而AOP就可以实现这种技术)。
AOP(面向切面编程)
很抽象的一个概念,AOP是一种编程思想,是对OOP的补充,它和OOP是一种互补的状态并非竞争状态,可以进一步提高编程的效率。
要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
项目中有很多业务模块,而每一个业务模块都有相同的系统需求,比如对它们做统一的记录日志、权限检查以及事务管理。而使用AOP的时候我们只需要单独定义一个组件,这个组件不会和业务组件发生任何直接的关系,我们不用去业务组件里面调用它,所以我们额外定义了系统组件,就好像这个组件横跨了多个业务组件,横向扩展业务组件的需求,所以为面向方面或者切面编程,变成的角度是面向这个横切组件。
AOP的术语
这里还是先给出一个比较专业的概念定义:
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
然后举一个容易理解的例子:
看完了上面的理论部分知识, 我相信还是会有不少朋友感觉到 AOP 的概念还是很模糊, 对 AOP 中的各种概念理解的还不是很透彻. 其实这很正常, 因为 AOP 中的概念是在是太多了, 我当时也是花了老大劲才梳理清楚的.
下面我以一个简单的例子来比喻一下 AOP 中 Aspect, Joint point, Pointcut 与 Advice之间的关系:
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?
- Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.
- Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.
- Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.
- Aspect::Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.
AOP的实现
AspectJ:
- AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法。
- AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件。
Spring AOP(不是一个全面的解决方案而是一个高性价比的解决方案):
- Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器。
- Spring AOP在运行时通过代理的方式织入代码,只支持方法类型的连接点。
- Spring支持对AspectJ的集成。
Spring AOP运行时zhi入程序采用代理对象
- JDK动态代理:
Java提供的动态代理技术,可以在运行时创建接口的代理实例。
Spring AOP默认采用此种方式,在接口的代理实例中织入代码。
2.CGLib动态代理:
采用底层的字节码技术,在运行时创建子类代理实例。
当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码。
AOP代码演示
@Component;交给Spring容器管理,@Aspect表示这是一个方面组件,而不是一个简单的组件。
定义切点@Pointcut, 快速筛选出一切想要的连接点。
定义通知:5类通知:@Before @After @AfterReturning @AfterThrowing @around
@Component
@Aspect
public class AlphaAspect {
//定义连接点
//所有的业务组件的所有的方法所有的参数以及所有的返回值
@Pointcut("execution(* com.newcoder.community.service.*.*(..))")
public void pointcut(){
}
//定义通知:在连接点的开始织入代码
@Before("pointcut()")
public void before(){
System.out.println("before");
}
@After("pointcut()")
public void after(){
System.out.println("after");
}
//在有了返回值之后织入代码,处理一些逻辑
@AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("afterReturning");
}
//在抛出异常时候织入代码
@AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("afterThrowing");
}
// 同时在方法的前后都织入逻辑
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
//joinPoint表示程序织入的部位
//目标方法调用之前的处理逻辑
System.out.println("around before");
//调用目标组件的方法(可能有返回值)
Object obj = joinPoint.proceed();
//目标方法调用之后的处理逻辑
System.out.println("around after");
return obj;
}
}
针对每一个业务组件,相应的方法都会被触发,且业务组件中的代码未做任何修改,降低耦合度。
利用AOP完成项目记录日志的功能
想在程序一开始记录日志,格式:“用户XXX(ip地址,因为可能有用户没登录)在某时刻访问了某功能”
在每一个业务组件方法调用之前(前置通知),
统一日志管理采用AOP的思想在内一个方法内都织入记录日志的行为
很简单Spring提供了@Aspect注解只要在一个类上加上这个注解就可以实现该功能
@Component
@Aspect
public class ServiceLogAspect {
//实例化logger
private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);
//1.定义切点
// 第一个*:方法的返回值
// com.nowcoder.community.service:包名
@Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
public void pointcut(){
}
// 前置通知
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
// 格式:用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].
// 用户的IP可以通过request获取,可以利用RequestContextHolder这个工具类
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
// IP地址和时间的获取
String ip = request.getRemoteHost();
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
// 获取类名和方法名
String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
logger.info(String.format("用户[%s],在[%s],访问了[%s].",ip,now,target));
}
}
边栏推荐
猜你喜欢
Win7怎么干净启动?如何只加载基本服务启动Win7系统
深入理解Golang之Map
pygame绘制弧线
How to solve Win11 without local users and groups
What should I do if I install a solid-state drive in Win10 and still have obvious lags?
Flink + sklearn - use JPMML implement flink deployment on machine learning model
win10怎么设置不睡眠熄屏?win10设置永不睡眠的方法
FP7195降压恒流PWM转模拟调光零压差大功率驱动方案原理图
Lightweight AlphaPose
Win11声卡驱动如何更新?Win11声卡驱动更新方法
随机推荐
Configure clangd for vscode
MATLAB绘图命令fimplicit绘制隐函数图形入门详解
What should I do if the Win10 system sets the application identity to automatically prompt for access denied?
FP7122降压恒流内置MOS耐压100V共正极阳极PWM调光方案原理图
Bash shell位置参数
关于c语言的调试技巧
2022TI杯D题混沌信号产生实验装置
vscode镜像
系统线性、时不变、因果判断
FP6296锂电池升压 5V9V12V内置 MOS 大功率方案原理图
Open the door to electricity "Circuit" (3): Talk about different resistance and conductance
FP5139电池与适配器供电DC-DC隔离升降压电路反激电路电荷泵电路原理图
pytorch模型转libtorch和onnx格式的通用代码
Win10上帝模式干嘛的?Win10怎么开启上帝模式?
FP7195芯片PWM转模拟调光至0.1%低亮度时恒流一致性的控制原理
日常-笔记
模板系列-二分
镜像法求解接地导体空腔电势分布问题
Mysql lock
Do Windows 10 computers need antivirus software installed?