一文读懂Annotation(1)

/** 属性的声明 */

FIELD,

/** 方法的声明 */

METHOD,

/** 方法形式参数声明 */

PARAMETER,

/** 构造方法的声明 */

CONSTRUCTOR,

/** 局部变量声明 */

LOCAL_VARIABLE,

/** 注解类型声明 */

ANNOTATION_TYPE,

/** 包的声明 */

PACKAGE,

/** 作用于类型参数(泛型参数)声明 */

TYPE_PARAMETER,

/** 作用于使用类型的任意语句(不包括class) */

TYPE_USE

}

根据此处可以知道 @Autowired 注解的作用范围:

// 可以作用在 构造方法、方法、方法形参、属性、注解类型 上

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})

2.2 @Retention 元注解

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Retention {

RetentionPolicy value();

}

@Retention 注解,翻译为持久力、保持力。即用来修饰自定义注解的生命周期。

注解的生命周期有三个阶段:

  • Java 源文件阶段

  • 编译到 class 文件阶段

  • 运行期阶段

同样使用了 RetentionPolicy 枚举类型对这三个阶段进行了定义:

public enum RetentionPolicy {

/**

  • 注解将被编译器忽略掉

*/

SOURCE,

/**

  • 注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为

*/

CLASS,

/**

  • 注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到

*/

RUNTIME

}

再详细描述下这三个阶段:

  • 如果被定义为 RetentionPolicy.SOURCE,则它将被限定在 Java 源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读 Java 文件的人看到;

  • 如果被定义为 RetentionPolicy.CLASS,则它将被编译到 Class 文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时 JVM(Java虚拟机)会忽略它,并且在运行期也不能读取到;

  • 如果被定义为 RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到 Class 对象中。那么在程序运行阶段,可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。

注意:实际开发中的自定义注解几乎都是使用的 RetentionPolicy.RUNTIME 。

2.3 @Documented 元注解

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Documented {

}

@Documented 注解,是被用来指定自定义注解是否能随着被定义的 java 文件生成到 JavaDoc 文档当中。

2.4 @Inherited 元注解

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Inherited {

}

@Inherited 注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。

@Inherited 注解只对那些 @Target 被定义为 ElementType.TYPE 的自定义注解起作用。

三、如何自定义注解


上面把注解与元注解说完了,那得实战一下吧。其实很多人在工作中已经用到过了或者自己没用到过但项目中有用到过。但你有没有想过自定义注解是怎么关联到目标方法的,是的没错,就是我们开头讲的定义,通过反射。

这里我就拿一个我们项目中自定义注解的例子来说:

1、标记日志打印的自定义注解

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface PrintLog {

}

2、定义一个切面,在切面中对使用了 @PrintLog 自定义注解的方法进行环绕增强通知

@Component

@Aspect

@Slf4j

public class PrintLogAspect {

@Around(value = “@annotation(com.riemann.core.annotation.PrintLog)”)

public Object handlerPrintLog(ProceedingJoinPoint joinPoint) throws Throwable {

String clazzName = joinPoint.getSignature().getDeclaringTypeName();

String methodName = joinPoint.getSignature().getName();

Object[] args = joinPoint.getArgs();

Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, methodName, args);

log.info(“Enter class[{}] method[{}] params[{}]”, clazzName, methodName, nameAndArgs);

Object object = null;

try {

object = joinPoint.proceed();

} catch (Throwable throwable) {

log.error(“Process class[{}] method[{}] error”, clazzName, methodName, throwable);

}

log.info(“End class[{}] method[{}]”, clazzName, methodName);

return object;

}

private Map<String, Object> getFieldsName(Class clazz, String clazzName, String methodName, Object[] args) throws NotFoundException {

Map<String, Object > map = new HashMap<>();

ClassPool pool = ClassPool.getDefault();

ClassClassPath classPath = new ClassClassPath(clazz);

pool.insertClassPath(classPath);

CtClass cc = pool.get(clazzName);

CtMethod cm = cc.getDeclaredMethod(methodName);

MethodInfo methodInfo = cm.getMethodInfo();

CodeAttribute codeAttribute = methodInfo.getCodeAttribute();

LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);

if (attr == null) {

// exception

}

int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;

for (int i = 0; i < cm.getParameterTypes().length; i++) {

map.put( attr.variableName(i + pos), args[i]);

}

return map;

}

}

3、最后,在 Controller 中的方法上使用 @PrintLog 自定义注解即可;当某个方法上使用了自定义注解,那么这个方法就相当于一个切点,那么就会对这个方法做环绕(方法执行前和方法执行后)增强处理。

@RestController

public class Controller {

@PrintLog

@GetMapping(value = “/user/findUserNameById/{id}”, produces = “application/json;charset=utf-8”)

public String findUserNameById(@PathVariable(“id”) int id) {

// 模拟根据id查询用户名

String userName = “公众号【老周聊架构】”;

return userName;

}

}

4、在浏览器中输入网址: http://127.0.0.1:8080/api/user/findUserNameById/666 回车后触发方法执行,发现控制台打印了日志

Enter class[Controller] method[findUserNameById] params[{id=666}]

End class[Controller] method[findUserNameById]

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-fXCCHMw9-1715186695525)]

[外链图片转存中…(img-1FOBLhjC-1715186695526)]

[外链图片转存中…(img-BegLbtj0-1715186695526)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值