千家信息网

SpringBoot开发怎么使用AOP记录日志

发表于:2024-11-24 作者:千家信息网编辑
千家信息网最后更新 2024年11月24日,这篇文章主要讲解了"SpringBoot开发怎么使用AOP记录日志",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"SpringBoot开发怎么使用AOP
千家信息网最后更新 2024年11月24日SpringBoot开发怎么使用AOP记录日志

这篇文章主要讲解了"SpringBoot开发怎么使用AOP记录日志",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"SpringBoot开发怎么使用AOP记录日志"吧!

为什么要用AOP?

答案是解耦!

Aspect Oriented Programming 面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。

具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。

AOP 主要是利用代理模式的技术来实现的。

通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

常用的工作场景

事务控制

日志记录

必须知道的概念

AOP 的相关术语

通知(Advice)

通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。

  • 前置通知(Before):在目标方法调用前调用通知功能;

  • 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;

  • 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;

  • 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;

  • 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。

连接点(JoinPoint)

通知功能被应用的时机。比如接口方法被调用的时候就是日志切面的连接点。

切点(Pointcut)

切点定义了通知功能被应用的范围。比如日志切面的应用范围就是所有接口,即所有 controller 层的接口方法。

切面(Aspect)

切面是通知和切点的结合,定义了何时、何地应用通知功能。

引入(Introduction)

在无需修改现有类的情况下,向现有的类添加新方法或属性。

织入(Weaving)

把切面应用到目标对象并创建新的代理对象的过程。

Spring 中使用注解创建切面

相关注解

@Aspect:用于定义切面

@Before:通知方法会在目标方法调用之前执行

@After:通知方法会在目标方法返回或抛出异常后执行

@AfterReturning:通知方法会在目标方法返回后执行

@AfterThrowing:通知方法会在目标方法抛出异常后执行

@Around:通知方法会将目标方法封装起来

@Pointcut:定义切点表达式

切点表达式

指定了通知被应用的范围,表达式格式:

execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数)//com.ninesky.study.tiny.controller包中所有类的public方法都应用切面里的通知execution(public * com.ninesky.study.tiny.controller.*.*(..))//com.ninesky.study.tiny.service包及其子包下所有类中的所有方法都应用切面里的通知execution(* com.ninesky.study.tiny.service..*.*(..))//com.ninesky.study.tiny.service.PmsBrandService类中的所有方法都应用切面里的通知execution(* com.macro.ninesky.study.service.PmsBrandService.*(..))

实战应用-利用AOP记录日志

从传统行业转行,以前都没想过打日志埋点,第一份工作,真的应该选择一个好的平台比较重要。

定义日志信息封装

用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息

public class WebLog {    /**     * 操作描述     */    private String description;    /**     * 操作用户     */    private String username;    /**     * 操作时间     */    private Long startTime;    /**     * 消耗时间     */    private Integer spendTime;    /**     * 根路径     */    private String basePath;    /**     * URI     */    private String uri;    /**     * URL     */    private String url;    /**     * 请求类型     */    private String method;    /**     * IP地址     */    private String ip;    /**     * 请求参数     */    private Object parameter;    /**     * 请求返回的结果     */    private Object result;    //省略了getter,setter方法}

定义注解,通过注解减少代码量

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface OperationLog {    String name();//调用接口的名称     boolean intoDb() default false;//该条操作日志是否需要持久化存储}

统一日志处理切面

@Aspect@Component@Order(1)@Slf4jpublic class WebLogAspect {    private static final Logger controlLog = LoggerFactory.getLogger("tmall_control");    @Pointcut("execution(public * com.yee.walnut.*.*.*(..))")    public void webLog() {    }     @Before(value = "webLog()&& @annotation(OperationLog)")    public void doBefore(ControllerWebLog controllerWebLog) throws Throwable {    }     @AfterReturning(value = "webLog()&& @annotation(OperationLog)", returning = "ret")    public void doAfterReturning(Object ret, ControllerWebLog controllerWebLog) throws Throwable {    }     @Around(value = "webLog()&& @annotation(OperationLog)")    public Object doAround(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {        long startTime = System.currentTimeMillis();        //获取当前请求对象        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();        HttpServletRequest request = attributes.getRequest();        //记录请求信息        Object[] objs = joinPoint.getArgs();        WebLog webLog = new WebLog();        Object result = joinPoint.proceed();//返回的结果,这是一个进入方法和退出方法的一个分界        Signature signature = joinPoint.getSignature();        MethodSignature methodSignature = (MethodSignature) signature;        Method method = methodSignature.getMethod();        long endTime = System.currentTimeMillis();        String urlStr = request.getRequestURL().toString();        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));        webLog.setIp(request.getRemoteUser());        webLog.setMethod(request.getMethod());        webLog.setParameter(getParameter(method, joinPoint.getArgs()));        webLog.setResult(JSONUtil.parse(result));        webLog.setSpendTime((int) (endTime - startTime));        webLog.setStartTime(startTime);        webLog.setUri(request.getRequestURI());        webLog.setUrl(request.getRequestURL().toString());        controlLog.info("RequestAndResponse {}", JSONObject.toJSONString(webLog));        //必须有这个返回值。可以这样理解,Around方法之后,不再是被织入的函数返回值,而是Around函数返回值        return result;    }     /**     * 根据方法和传入的参数获取请求参数     */    private Object getParameter(Method method, Object[] args) {        List argList = new ArrayList<>();        Parameter[] parameters = method.getParameters();        for (int i = 0; i < parameters.length; i++) {            //将RequestBody注解修饰的参数作为请求参数            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);            if (requestBody != null) {                argList.add(args[i]);            }            //将RequestParam注解修饰的参数作为请求参数            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);            if (requestParam != null) {                Map map = new HashMap<>();                String key = parameters[i].getName();                if (!StringUtils.isEmpty(requestParam.value())) {                    key = requestParam.value();                }                map.put(key, args[i]);                argList.add(map);            } else {                argList.add(args[i]);            }        }        if (argList.size() == 0) {            return null;        } else if (argList.size() == 1) {            return argList.get(0);        } else {            return argList;        }    }}

在方法上加上自定义注解即可

@OperationLog(name = "TurnOnOffStrategy")public String doOperation(GlobalDto globalDto, DeviceOperator deviceOperator) {}

感谢各位的阅读,以上就是"SpringBoot开发怎么使用AOP记录日志"的内容了,经过本文的学习后,相信大家对SpringBoot开发怎么使用AOP记录日志这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

方法 日志 切面 目标 应用 参数 功能 注解 开发 接口 切点 时间 信息 结果 业务 对象 就是 程序 范围 表达式 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 狂雨cms搭建教程怎么进数据库 阿里云数据库帮助文档 软件开发公司市场部职责 dota服务器列表不显示房间 用微信怎样找到授权服务器 河北人工智能软件开发哪家好 易联天下互联网科技有限公司 我的世界无红石农田服务器 石家庄市网络安全和信息办 南京企业网络安全准入控制系统 数据库如何保证并发 乡镇网络安全方案 推荐 服务器 网众服务器设置密码 软件开发精神风貌标语 网络安全自治区等保办 区块链网络安全阿里 河南移动软件开发公司 软件工程如何改变软件开发 秒杀抢购神器软件开发 齐鲁师范学院教务系统服务器地址 数据库链接超时卡死 电视家 自定义服务器 思科网络安全产品 青岛方向软件开发公司 无悔华夏官服务器有哪些 不同数据库的应用程序的区别 网络安全毕业答辩问题及答案 绍兴无线网络技术创新服务 湖北统一软件开发服务郑重承诺
0