我们在初学spring的时候会觉着注解这个东西很神奇,原本需要我们写很长一段的代码现在只需要一个注解就能够解决问题,所以自己也希望能够实现自定义注解来实现一个功能,最近在工作中小组长就给了这样一个任务:能不能在调用方法之前使用自定义注解动态的获取用户信息;
自定义注解功能其实很简单,也很好实现,可以把他理解为一个装饰物,简单理解为在一个方法上再添加一个方法对这个方法增强,就像下面这样就可以称为一个简单的自定义注解:
/**
* 上下文注解(
* 1.将当前登录用户存放在上下文
* 2.打印当前用户,rpc请求应用名,http请求referer名)
*
* @author: cbc
* @Date: 2023/5/31
* @Description: BasicContext
* @Version 1.0.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessControl {
/**
* 是否将网关传递的登录用户信息保存在ttl中
*
* @return
*/
boolean userinfo() default false;
/**
* 是否打印当前登录用户信息,rpc调用方应用名,http调用方referer信息
*
* @return
*/
boolean log() default true;
}
这个时候就可以在对应的方法上加入这个@AccessControl的注解,看着很简单吧!
但是现在这个注解什么功能也没有实现,所以我们要结合AOP对这个注解进行后置操作,再说AOP之前我们先来讲讲自定义注解所使用的元注解所代表的意思
- @Target:用于描述注解的使用范围,该注解可以使用在什么地方(常用)
- 这里需要注意的是如果你的这个target注解与你加入的类型不一样的话就会报错
ElementType.METHOD | 应用于方法 |
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
- @Retention:表明该注解的生命周期(常用)
RetentionPolicy.RUNTIME 由JVM 加载,包含在类文件中,在运行时可以被获取到 -
@Documented:表明该注解标记的元素可以被Javadoc 或类似的工具文档化
一、加入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 的过程
今天我们所使用的就只有三种,Aspect(切面)、Pointcut(切点)、Advice(增强)
废话不多说,直接上代码(相关解释都在代码注释上哦)
/**
* 上下文注解aspect
*
* @Auther: cbc
* @Date: 2023/5/31
* @Description: AccessControlAspect
* @Version 1.0.0
*/
@Slf4j
@Aspect
@Component
public class AccessControlAspect {
//切点,不需要编写实际的代码
//限制连接点的匹配,其中连接点的主题(在 Spring AOP 中执行的方法)具有给定的 annotation
//@annotation只匹配实现类中的有注解的方法,不会匹配接口中的注解方法。
@Pointcut("@annotation(utils.user.AccessControl)")
public void cutMethod() {
}
/**
* 前置操作(这里的方法就是执行我们实际的业务逻辑)
*
* @param joinPoint
*/
@Before("cutMethod()")
public void before(JoinPoint joinPoint) {
//从JoinPoint(连接点,可以理解成所有方法)获取我们的自定义注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
AccessControl annotation = method.getAnnotation(AccessControl.class);
//获取的是我们自定义注解中所传递的值
//对应的业务逻辑:是否将网关传递的登录用户信息保存在ttl中
if (annotation.userinfo()) {
UserInfoDTO userInfoDTO = SofaRpcContext.userInfo();
//log
log.info("@@@@@@ userInfo : {} @@@@@@", userInfoDTO);
ThreadLocalUtils.set(ImmutableMap.of(USER_INFO_IN_THREAD_LOCAL, userInfoDTO));
}
//获取的是我们自定义注解中所传递的值
//对应的业务逻辑:是否打印当前信息
if (annotation.log()) {
//服务调用方
String consumer = SofaRpcContext.appName();
//refer
String referer = SofaRpcContext.referer();
//log
log.info("@@@@@@ consumer : {} , referer : {} , methodName : {} @@@@@@", consumer, referer, Joiner.on(Constant.SEPARATOR_PERIOD).join(methodSignature.getDeclaringTypeName(), methodSignature.getName()));
}
}
}
二、实际接口中使用获取用户信息
我们来看下对应的日志看是否可以获取到对应的用户信息
这里可以看到我们可以获取到对应的用户信息,以上就是我在实际开发中所使用自定义注解的全过程,希望可以帮助到各位猴宝宝们~