业务背景

很久以前开源了一款 auto-log 自动日志打印框架。

其中对于 spring 项目,默认实现了基于 aop 切面的日志输出。

但是发现一个问题,如果切面定义为全切范围过大,于是 v0.2 版本就是基于注解 @AutoLog 实现的。

只有指定注解的类或者方法才会生效,但是这样使用起来很不方便。

如何才能动态指定 pointcut,让用户使用时可以自定义切面范围呢?

自定义注解切面原理

常规 aop 方式

@Aspect
@Component
@EnableAspectJAutoProxy
@Deprecated
public class AutoLogAop { @Pointcut("@within(com.github.houbb.auto.log.annotation.AutoLog)" +
"|| @annotation(com.github.houbb.auto.log.annotation.AutoLog)")
public void autoLogPointcut() {
} /**
* 执行核心方法
*
* 相当于 MethodInterceptor
*
* @param point 切点
* @return 结果
* @throws Throwable 异常信息
* @since 0.0.3
*/
@Around("autoLogPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 日志增强逻辑
} }

发现这里的 @Pointcut 注解属性是一个常量,无法方便地动态修改。

于是去查资料,找到了另一种更加灵活的方式。

可以指定 pointcut 的方式

我们通过 @Value 获取属性配置的切面值,给定默认值。这样用户就可以很方便的自定义。

/**
* 动态配置的切面
* 自动日志输出 aop
* @author binbin.hou
* @since 0.3.0
*/
@Configuration
@Aspect
//@EnableAspectJAutoProxy
public class AutoLogDynamicPointcut { /**
* 切面设置,直接和 spring 的配置对应 ${},可以从 properties 或者配置中心读取。更加灵活
*/
@Value("${auto.log.pointcut:@within(com.github.houbb.auto.log.annotation.AutoLog)||@annotation(com.github.houbb.auto.log.annotation.AutoLog)}")
private String pointcut; @Bean("autoLogPointcutAdvisor")
public AspectJExpressionPointcutAdvisor autoLogPointcutAdvisor() {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(pointcut);
advisor.setAdvice(new AutoLogAdvice());
return advisor;
} }

当然,这里的 Advice 和以前的 aop 不同,需要重新进行实现。

AutoLogAdvice

只需要实现 MethodInterceptor 接口即可。

/**
* 切面拦截器
*
* @author binbin.hou
* @since 0.3.0
*/
public class AutoLogAdvice implements MethodInterceptor { @Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// 增强逻辑
} }

介绍完了原理,我们一起来看下改进后的日志打印组件的效果。

spring 整合使用

完整示例参考 SpringServiceTest

maven 引入

<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>auto-log-spring</artifactId>
<version>0.3.0</version>
</dependency>

注解声明

使用 @EnableAutoLog 启用自动日志输出

@Configurable
@ComponentScan(basePackages = "com.github.houbb.auto.log.test.service")
@EnableAutoLog
public class SpringConfig {
}

测试代码

@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringServiceTest { @Autowired
private UserService userService; @Test
public void queryLogTest() {
userService.queryLog("1");
} }
  • 输出结果
信息: public java.lang.String com.github.houbb.auto.log.test.service.impl.UserServiceImpl.queryLog(java.lang.String) param is [1]
五月 30, 2020 12:17:51 下午 com.github.houbb.auto.log.core.support.interceptor.AutoLogMethodInterceptor info
信息: public java.lang.String com.github.houbb.auto.log.test.service.impl.UserServiceImpl.queryLog(java.lang.String) result is result-1
五月 30, 2020 12:17:51 下午 org.springframework.context.support.GenericApplicationContext doClose

切面自定义

原理解释

spring aop 的切面读取自 @Value("${auto.log.pointcut}"),默认为值 @within(com.github.houbb.auto.log.annotation.AutoLog)||@annotation(com.github.houbb.auto.log.annotation.AutoLog)

也就是默认是读取被 @AutoLog 指定的方法或者类。

当然,这并不够方便,我们希望可以想平时写 aop 注解一样,指定 spring aop 的扫描范围,直接在 spring 中指定一下 auto.log.pointcut 的属性值即可。

测试例子

完整测试代码

我们在配置文件 autoLogConfig.properties 中自定义下包扫描的范围:

auto.log.pointcut=execution(* com.github.houbb.auto.log.test.dynamic.service.MyAddressService.*(..))

自定义测试 service

package com.github.houbb.auto.log.test.dynamic.service;

import org.springframework.stereotype.Service;

@Service
public class MyAddressService { public String queryAddress(String id) {
return "address-" + id;
} }

自定义 spring 配置,指定我们定义的配置文件。springboot 啥的,可以直接放在 application.properties 中指定,此处仅作为演示。

@Configurable
@ComponentScan(basePackages = "com.github.houbb.auto.log.test.dynamic.service")
@EnableAutoLog
@PropertySource("classpath:autoLogConfig.properties")
public class SpringDynamicConfig {
}

测试

@ContextConfiguration(classes = SpringDynamicConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringDynamicServiceTest { @Autowired
private MyAddressService myAddressService; @Autowired
private MyUserService myUserService; @Test
public void queryUserTest() {
// 不会被日志拦截
myUserService.queryUser("1");
} @Test
public void queryAddressTest() {
// 会被日志拦截
myAddressService.queryAddress("1");
} }

开源地址

为了便于大家学习,项目已开源。

Github: https://github.com/houbb/auto-log

Gitee: https://gitee.com/houbinbin/auto-log

小结

这个项目很长一段时间拘泥于注解的方式,我个人用起来也不是很方便。

最近才想到了改进的方法,人还是要不断学习进步。

关于日志最近还学到了 aspect 的编译时增强,和基于 agent 的运行时增强,这 2 种方式都很有趣,有机会会做学习记录。

如何动态修改 spring aop 切面信息?让自动日志输出框架更好用的更多相关文章

  1. spring aop切面编程实现操作日志步骤

    1.在spring-mvc.xml配置文件中打开切面开关: <aop:aspectj-autoproxy proxy-target-class="true"/> 注意: ...

  2. 利用spring AOP实现每个请求的日志输出

    前提条件: 除了spring相关jar包外,还需要引入aspectj包. <dependency> <groupId>org.aspectj</groupId> & ...

  3. Spring AOP 切面编程记录日志和接口执行时间

    最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...

  4. Java动态代理-->Spring AOP

    引述要学习Spring框架的技术内幕,必须事先掌握一些基本的Java知识,正所谓“登高必自卑,涉远必自迩”.以下几项Java知识和Spring框架息息相关,不可不学(我将通过一个系列分别介绍这些Jav ...

  5. 使用Spring AOP切面解决数据库读写分离

    http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ...

  6. Spring AOP切面的时候参数的传递

    Spring AOP切面的时候参数的传递 Xml: <?xml version="1.0" encoding="UTF-8"?> <beans ...

  7. Spring Boot 2.0 教程 | AOP 切面统一打印请求日志

    欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...

  8. spring AOP(切面) 表达式介绍

    在 spring AOP(切面) 例子基础上对表达式进行介绍 1.添加接口删除方法 2.接口实现类 UserDaoServer 添加实现接口删除方法 3.测试类调用delUser方法 4. 输出结果截 ...

  9. 利用Spring AOP切面对用户访问进行监控

    开发系统时往往需要考虑记录用户访问系统查询了那些数据.进行了什么操作,尤其是访问重要的数据和执行重要的操作的时候将数记录下来尤显的有意义.有了这些用户行为数据,事后可以以用户为条件对用户在系统的访问和 ...

  10. 运用Spring Aop,一个注解实现日志记录

    运用Spring Aop,一个注解实现日志记录 1. 介绍 我们都知道Spring框架的两大特性分别是 IOC (控制反转)和 AOP (面向切面),这个是每一个Spring学习视频里面一开始都会提到 ...

随机推荐

  1. Windows安装系统

    0x01下载PE 微PE 0x02安装PE 0x021方式一:安装到系统 此方法开机有选择系统的选项,强迫症使用方法二 0x022方式二:安装到U盘 此方法需要一个U盘 确认无误后点击 立即安装到U盘 ...

  2. RTP分包模式(H264/H265)

    在rfc6184-h264文档5.4章节有详细说明.以及rfc7798-h265文档4.2章节中也有部分介绍. 一.NALU Header ● H264 NALU Header(1 byte)结构图如 ...

  3. Vue中关于keep-alive的使用

    keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在v页面渲染完毕后不会被渲染成一个DOM元素 当组件在keep-alive内被切换时组件的ac ...

  4. 【经验分享】使用Windows自带Xbox显示游戏帧率

    环境: 工具:Xbox Game Bar 系统版本:Windows 10 快捷键:win + G 需求描述: 描述:需要实时显示当前游戏的帧率和硬件的占用率情况.如下图: 实现方法: 1.按下组合键w ...

  5. IPS 和 IDS

    IPS/IDS 什么是IPS和IDS IDS/IPS是检测和防止对网络服务器进行未授权的访问的系统.有许多产品同时有IDS和IPS的作用,作为加强企业信息安全所必须的系统 什么是IDS(Intrusi ...

  6. 修改mysql的密码时遇到问题ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corre

    先输入:flush privileges; 再输入:ALTER USER 'root'@'localhost' IDENTIFIED BY 'mysql'; 再输入:flush privileges刷 ...

  7. 2022-11-11:设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。 实现 MaxStack 类: MaxStack() 初始化栈对象 void push(int x) 将元素 x 压

    2022-11-11:设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素. 实现 MaxStack 类: MaxStack() 初始化栈对象 void push(int x) 将元素 x 压 ...

  8. 2021-11-18:给定一个长度len,表示一共有几位。所有字符都是小写(a~z),可以生成长度为1,长度为2,长度为3...长度为len的所有字符串。如果把所有字符串根据字典序排序,每个字符串都有

    2021-11-18:给定一个长度len,表示一共有几位.所有字符都是小写(a~z),可以生成长度为1,长度为2,长度为3-长度为len的所有字符串.如果把所有字符串根据字典序排序,每个字符串都有所在 ...

  9. 2021-08-04:给定一个字符串str,当然可以生成很多子序列。返回有多少个子序列是回文子序列,空序列不算回文。比如,str = “aba”,回文子序列:{a}、{a}、 {a,a}、 {b}、{

    2021-08-04:给定一个字符串str,当然可以生成很多子序列.返回有多少个子序列是回文子序列,空序列不算回文.比如,str = "aba",回文子序列:{a}.{a}. {a ...

  10. 2021-09-26:搜索旋转排序数组。整数数组 nums 按升序排列,数组中的值 互不相同 。在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了

    2021-09-26:搜索旋转排序数组.整数数组 nums 按升序排列,数组中的值 互不相同 .在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.lengt ...