Java自定义注解、Spring AOP、使用AOP实现和自定义注解实现日志记录_java 自定义注解 实现-程序员宅基地

技术标签: spring  spring boot  aop  java  自定义注解  

一:自定义注解简介

1.描述

注解的使用真的很神奇,加一个注解就能实现想要的功能,很好奇,也想自己根据需要写一些自己实现的自定义注解。问题来了,自定义注解到底是什么?其实注解一点也不神奇,注解是一种能被添加到java源代码中的元数据,单独使用注解,就相当于在类、方法、参数和包上加上一个装饰,什么功能也没有,仅仅是一个标志,然后这个标志可以加上一些自己定义的参数。就像下面这样,创建一个@interface的注解,然后就可以使用这个注解了,加在我们需要装饰的方法上,但是什么功能也没有。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ValidateToken {
    
    String value() default"";
}

2.基本介绍

  • 修饰符:访问修饰符必须为public,不写默认为pubic
  • 关键字:关键字为@interface
  • 注解名称:注解名称为自定义注解的名称,例如上面的XinLinLog 就是注解名称
  • 注解类型元素:注解类型元素是注解中内容,根据需要标志参数,例如上面的注解的value

3.元注解(@Target、@Retention、@Inherited、@Documented)

我们上面的创建的注解ValidateToken 上面还有几个注解(@Target、@Retention、@Inherited、@Documented),这四个注解就是元注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的元注解类型,它们被用来提供对其它 注解类型作标志操作(可以理解为最小的注解,基础注解)

1)@Target——用于描述注解的使用范围,该注解可以使用在什么地方

Target类型 描述
ElementType.TYPE 应用于类、接口(包括注解类型)、枚举
ElementType.FIELD 应用于属性(包括枚举中的常量)
ElementType.METHOD 应用于方法
ElementType.PARAMETER 应用于方法的形参
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.ANNOTATION_TYPE 应用于注解类型
ElementType.PACKAGE 应用于包

备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上,但是我们在这个注解标志在类上,就会报错

2)@Retention——表明该注解的生命周期

生命周期类型 描述
RetentionPolicy.SOURCE 编译时被丢弃,不包含在类文件中
RetentionPolicy.CLASS JVM加载时被丢弃,包含在类文件中,默认值
RetentionPolicy.RUNTIME 由JVM 加载,包含在类文件中,在运行时可以被获取到

3)@Inherited——是一个标记注解,其子类也可以使用该注解的功能

@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

4)@Documented——表明该注解标记的元素可以被Javadoc 或类似的工具文档化

二:Spring AOP详解

1.前言

1)什么是AOP

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面,是Spring的核心思想之一。

2)AOP 实现分类

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,按照 AOP 框架修改源代码的时机,可以将其分为两类:

  • 静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

3)AOP核心概念

在这里插入图片描述

  • 切面(Aspect):切面是通知和切点的结合。
  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 目标对象(Target):目标对象指将要被增强的对象。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。
  • 顾问(Advisor):顾问是Advice的一种包装体现,Advisor是Pointcut以及Advice的一个结合,用来管理Advice和Pointcut。

4)AOP源码解析

我们知道,spring中的aop是通过动态代理实现的,那么他具体是如何实现的呢?spring通过一个切面类,在他的类上加入@Aspect注解,定义一个Pointcut方法,最后定义一系列的增强方法。这样就完成一个对象的切面操作。
那么思考一下,按照上述的基础,要实现我们的aop,大致有以下思路:
1.找到所有的切面类
2.解析出所有的advice并保存
3.创建一个动态代理类
4.调用被代理类的方法时,找到他的所有增强器,并增强当前的方法

5)AOP在工作中的作用

  • 在调用service具体一些业务方法的时候,想在前面打一些日志。
  • 通过前后两次取时间戳来减一下,来统计所有业务方法执行的时间。
  • 在调用某一类业务方法时,判断用户有没有权限。
  • 在一系列业务方法前后加上事务的控制。
  • 比如startTransaction、commitTransaction(模拟事务控制)。

6)JDK动态代理和CGLIB动态代理

  • jdk:假如目标对象(被代理对象)实现接口,则底层可以采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口)。
  • CGLIB:假如目标对象(被代理对象)没有实现接口,则底层可以采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型)。

2.AOP 相关术语分析

1)术语分析

  • 切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
  • 通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
  • 连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
  • 切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合。
    在这里插入图片描述

2)注解作用

  • @Aspect 注解用于标识或者描述AOP中的切面类型,基于切面类型构建的对象用于为目标对象进行功能扩展或控制目标对象的执行。
  • @Pointcut注解用于描述切面中的方法,并定义切面中的切入点(基于特定表达式的方式进行描述),在本案例中切入点表达式用的是bean表达式,这个表达式以bean开头,bean括号中的内容为一个spring管理的某个bean对象的名字。
  • @Around注解用于描述切面中方法,这样的方法会被认为是一个环绕通知(核心业务方法执行之前和之后要执行的一个动作),@Aournd注解内部value属性的值为一个切入点表达式或者是切入点表达式的一个引用(这个引用为一个@PointCut注解描述的方法的方法名)。
  • ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的目标方法相关的一些信息。一般用于@Around注解描述的方法参数。

3.AOP编程增强

1)通知类型

  • 前置通知 (@Before) 。
  • 返回通知 (@AfterReturning) 。
  • 异常通知 (@AfterThrowing) 。
  • 后置通知 (@After)。
  • 环绕通知 (@Around) :重点掌握(优先级最高)

2)通知执行顺序

在这里插入图片描述

4.切入点表达式

1)bean表达式

bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:

  • bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
  • bean(“*ServiceImpl”)指定所有后缀为ServiceImpl的类中所有方法。
    说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。

2)within表达式

within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:

  • within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
  • within(“aop.service.*”) 指定当前目录下的所有类的所有方法。
  • within(“aop.service…*”) 指定当前目录以及子目录中类的所有方法。

3)execution表达式

execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。

  • execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
  • execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
  • execution(* aop.service….(…)) 万能配置。

4)@annotation表达式

@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析

  • @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
  • @annotation(anno.RequiredCache) 匹配有此注解描述的方法。
    其中:RequiredLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日扩展操作。

三:自定义注解使用——通过自定义注解记录日志

自定义注解使用范围:
上面总结的注解的定义,但是创建这样一个注解,仅仅是一个标志,装饰类、方法、属性的,并没有功能,要想实现功能,需要我们通过拦截器、AOP切面这些地方获取注解标志,然后实现我们的功能。一般我们可以通过注解来实现一些重复的逻辑,就像封装了的一个方法,可以用在一些权限校验、字段校验、字段属性注入、保存日志、缓存。

1.定义Pointcut切面

   /**
	 * @Pointcut 注解通过切入点表达式定义切入点,例如
	 * bean表达式:bean(bean的名称)
	 */
	//@Pointcut("bean(sysUserServiceImpl)")
	@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
	public void doLogPointCut() {
    
		//方法体不需要写任何内容
	}
  • @Pointcut:获取添加自定义注解的方法,获取某些特定的类
  • 方法体中不需要添加任何东西

2.定义环绕通知

    @Around("doLogPointCut()")
    public Object around(ProceedingJoinPoint jp)throws Throwable{
    
		long t1=System.currentTimeMillis();
		log.info("start:{}",t1);//{}在这里表示占位符
		try {
    
		Object result=jp.proceed();//调用本类内部切入点对应其它通知或其它切面或目标方法。
		long t2=System.currentTimeMillis();
		log.info("end: {}",t2);
		saveLog(jp,(t2-t1));//记录正常行为日志
		return result;
		}catch (Throwable e) {
    
		log.error("error end: {}",e.getMessage());
		//可以在此位置记录异常行为日志
		//return null;
		throw e;
		}
    }

1) @Around 注解描述的方法为一个通知方法,在这个方法内部可以通过连接点对象(ProceedingJoinPoint)调用目标方法,并在目标方法对象执行之前或之后添加额外功能。

2) @Around 注解描述的方法有一定要求:

  • 返回值类型为Object
  • 方法参数类型为ProceedingJoinPoint类型
  • 方法抛出throwable异常(建议)

3)jp 封装了正要执行的目标方法信息

4)Object result=jp.proceed();可以获取到目标方法执行的结果和时间。

3.保存用户行为信息

private void saveLog(ProceedingJoinPoint jp,long time) throws NoSuchMethodException, SecurityException, JsonProcessingException {
    
		//1.获取用户行为信息
		//1.1获取目标方法对象
		Method targetMethod=getTargetMethod(jp);
		//1.2获取目标方法的方法名信息
		String targetMethodName=
	    targetMethod.getDeclaringClass().getName()+"."+targetMethod.getName();
		//1.3获取目标方法上的操作名
		String operation = getOperation(targetMethod);
		//1.4 目标方法参数(转换为字符串)
		//String params=Arrays.toString(jp.getArgs());
		String params=
		//将参数对象转换为json格式字符串
		new ObjectMapper().writeValueAsString(jp.getArgs());
		//2.封装用户行为信息
		SysLog log=new SysLog();
		log.setIp(IPUtils.getIpAddr());
		log.setUsername(ShiroUtils.getUsername());//后续做完登陆以后,为登陆用户名
		log.setOperation(operation);
		log.setMethod(targetMethodName);
		log.setParams(params);
		log.setTime(time);
		log.setCreatedTime(new Date());
		//3.保存用户行为信息
		sysLogService.saveObject(log);
	}

4.获取目标方法

private Method getTargetMethod(ProceedingJoinPoint jp) throws NoSuchMethodException, SecurityException {
    
		Class<?> targetCls=jp.getTarget().getClass();
		MethodSignature ms=(MethodSignature)jp.getSignature();
		//获取目标方法
		Method targetMethod=//目标方法(类全名+方法名)
		targetCls.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
		return targetMethod;
	}

5.获取操作名

	private String getOperation(Method targetMethod) {
    
		String operation="operation";
		RequiredLog requiredLog=
		targetMethod.getAnnotation(RequiredLog.class);
		if(requiredLog!=null) {
    
		operation=requiredLog.value();
		}
		return operation;
	}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/suiyishiguang/article/details/126612592

智能推荐

基于XDMA 的PCIE读写DDR_xdma ddr-程序员宅基地

文章浏览阅读7.4k次,点赞16次,收藏100次。基于XDMA 的PCIE读写DDR概述:  想实现基于FPGA的PCIe通信,查阅互联网各种转载…基本都是对PCIe的描述,所以想写一下基于XDMA的PCIe通信的实现(PCIe结构仅做简单的描述(笔记),了解详细结构移至互联网)。实现功能:PC通过PCIE读写DDR,同时用户通过逻辑代码可以读取被写入DDR内的数据(我是通过VIO实现DDR任意地址,任意数据大小的读取。)。实践实践!!!说明:参考文档:PCI Express Base Specification Revision 3.0P_xdma ddr

缓存穿透与布隆过滤器_缓存穿透 布隆过滤器-程序员宅基地

文章浏览阅读1.7k次。本文主要介绍在使用缓存过程中经常会遇到的几个问题:缓存击穿、缓存雪崩、缓存穿透,以及其解决方案。之后会对缓存穿透的解决方案之一布隆过滤器,进行详细讲解。_缓存穿透 布隆过滤器

pandas写入oracle,python pandas dataframe 读取和写入Oracle-程序员宅基地

文章浏览阅读1.4k次。1、代码:主要写入时表要为小写,否则报错Could not reflect: requested table(s) not available in Enginefrom sqlalchemy import create_engineconn_string='oracle+cx_oracle://admin:[email protected]:1521/ORCL?charset=utf8'..._pandas cx_oracle 写入

react-dnd拖拽表单组件列表实例_const defaultlist =-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏2次。API 学习:https://blog.csdn.net/gaofeng6565/article/details/115696823项目准备:https://codepen.io/选择React,AntDesign 模式,添加依赖 react-dnd(@14.0.2),react-dnd-html5-backend(@14.0.0)ListDrag.jsimport React, { useState } from "react";import { Button } from "antd"._const defaultlist =

无人驾驶之MATLAB无人驾驶工具箱学习(1)_ego vehicle-程序员宅基地

文章浏览阅读2.4w次,点赞21次,收藏162次。更新完显卡驱动后,视频可以自动导入了,继续码。2018.08.111 坐标系转换ADST(Automated Driving System Toolbox)中的坐标系ADST中的坐标系:世界坐标系(world),所有车辆及其传感器都建立其上的固定坐标系。 车辆(Vechicle):固定在车身上。有代表性地,车辆坐标系建立在车辆后轴中点处的地面上。 传感器(Sensor):明确具..._ego vehicle

SAP S/4 HANA新变化-FI数据模型-程序员宅基地

文章浏览阅读370次。SAP S/4 HANA新变化-FI数据模型 http://mp.weixin.qq.com/s?__biz=MzAwMjgyMTA4MQ==&mid=2652153162&idx=1&sn=aee6fc43e0577479854e4842df919c90&ch..._hana anlc

随便推点

0-1字典树总结和经典例题(ing)_字典树例题 poj-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏5次。Table of Contents0-1字典树例题1. CSU 1216:异或最大值:给定一些数,任意两个数的最大异或值例题2.HDU 4825Xor Sum:每次询问给出一个数,找出一个与它异或结果最大的数例题3.HDU 5536Chip Factory: 计算(s[i] + s[j]) ^ s[k] 的最大值例题4.POJ 3764The xor-longe..._字典树例题 poj

HEVD之栈溢出_通过fs寄存器解决栈溢出-程序员宅基地

文章浏览阅读881次。自学习CVE-2017-11882漏洞后,便开始希望学习更多的漏洞,提高一下对漏洞的理解。在github上找到一个很好的项目,基本涵盖了所有漏洞(https://github.com/hacksysteam/HackSysExtremeVulnerableDriver)。下载下来后,执行过后得到驱动文件HEVD.sys,以及漏洞利用程序HackSysEVDExploit.exe,然后开启双..._通过fs寄存器解决栈溢出

【漏洞复现-通达OA】通达OA share身份认证绕过漏洞_tongda oa 身份绕过-程序员宅基地

文章浏览阅读364次。通达OA(Office Anywhere网络智能办公系统)是中国通达公司的一套协同办公自动化软件。通达OA /share/handle.php存在一个认证绕过漏洞,利用该漏洞可以实现任意用户登录。攻击者可以通过构造恶意攻击代码,成功登录系统管理员账户,继而在系统后台上传恶意文件控制网站服务器。_tongda oa 身份绕过

Java使用LocalDate获取某个月的第一天和最后一天日期_localdate获取当月最后一天-程序员宅基地

文章浏览阅读3.8w次,点赞33次,收藏80次。Java使用LocalDate或LocalDateTime获取某个月的第一天和最后一天日期_localdate获取当月最后一天

银河麒麟ARM64 飞腾FT2000 linuxdeployqt linux打包qt_linuxdeployqt aarch64-程序员宅基地

文章浏览阅读6.2k次,点赞3次,收藏40次。银河麒麟ARM64 飞腾FT2000 linuxdeployqt linux打包qt下载linuxdeployqt-aarch64.AppImageqt版本说明linuxdeployqt打包准备编译好的程序插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入下载linuxdeployqt-aa_linuxdeployqt aarch64

在Springboot集成Activiti工作流引擎-引入、调用,测试【基础讲解】-程序员宅基地

文章浏览阅读1.6k次,点赞2次,收藏6次。工作流 通过计算机对业务流程自动化执行管理他主要解决的是使在多个参与者之间按照某种“预定义规则”自动进行传递稳定 信息或任务的过程通俗来讲 业务上一个玩着的审批流程 比如请假,出差 外出采购等工作流引擎就是来解决流程问题的 提高我们的工作效率如果没有工作流引擎 我们就需要自己去写逻辑 就特别的复杂 扩展性还不强使用工作流引擎 业务改变,不需要修改代码如果是我们自己写的逻辑 有可能 业务改变,代码也需要改变那么为什么工作流引擎不用修改代码因为我们的工作流引擎都实现了一个规范这个规范要_集成activiti

推荐文章

热门文章

相关标签