java注解(实现原理及自定义注解)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

    • 一、注解的实现原理
      • [1.1 怎样定义注解(打标签)](#1.1 怎样定义注解(打标签))
      • [1.2 注解处理器 (决定了这个标签到底能干嘛)](#1.2 注解处理器 (决定了这个标签到底能干嘛))
      • [1.3 常用标签的处理过程](#1.3 常用标签的处理过程)
    • 二、怎样创建自定义注解
    • 总结

本文不是讲某个注解什么含义,而是讲注解的实现原理,以及自定义注解的创建。

我们最开始会接触一些框架的注解,例如spring等,但是这些注解是怎么起作用的呢?我们怎样实现自己的注解呢?

一、注解的实现原理

一言以蔽之,原理就是打个标签 (类或方法等上使用 @注解),然后在特定时机处理标签(检测到标签后,加相应的功能)。

我们使用注解时,只接触打标签,也就是编码加上各种 @注解,而标签的处理则隐藏在框架或工具类中,嫌少接触。

1.1 怎样定义注解(打标签)

如下,跟定义接口一样,不过类型是@interface。

java 复制代码
@Target(ElementType.METHOD)  //  注解用于方法上
@Retention(RetentionPolicy.RUNTIME)  // 保留到运行时,可通过注解获取
public @interface MyAnnotation {
    String description();
    int length() default 0;
}

这个注解上面的注解,是 java定义的元注解,用于描述注解,常用的有:

  • Target:描述了注解修饰的对象范围。(TYPE-描述类、接口或enum,METHOD-描述方法等)
  • Retention: 表示注解保留时间长短。(SOURE-在源文件中有效,编译过程中忽略,RUNTIME-在运行时有效)

其中,这个注解可以有属性,也可以没有属性,属性可以有默认值,也可以没有默认值。这些不重要,只是标签的附属功能。

当在类等上面使用 @ 加上注解后,只是给它打了个标签,并没有任何功能,这个标签到底对应什么功能,取决于它的注解处理器

1.2 注解处理器 (决定了这个标签到底能干嘛)

注解会被编译进class文件中,所以获取到class对象后,就能获取到其定义在类、方法、属性等上的标签了。

所以怎样获取注解?

复制代码
class对象--反射获取到方法、属性等--获取方法或属性上的注解。
java 复制代码
Class c = XX.class;
	// 获取所有字段
    for(Method m : c.getMethods()){
        // 判断这个字段是否有MyAnnotion注解
        if(f.isAnnotationPresent(MyAnnotation.class)){
            MyAnnotion annotation = f.getAnnotation(MyAnnotation.class);
        }
    }

1.3 常用标签的处理过程

例如经典的@Bean标签(一般不再单独使用,仅用于理解),表示这是一个spring的Bean,会初始化到spring容器中。

那这个标签是怎样起作用的呢?

首先,spring定义好了Bean注解。

java 复制代码
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    // 其他属性忽略
}

你在某个类型上也使用了该注解,在spring容器初始化时(refresh入口的那一堆难以理解的源码),会检测这个类有没有Bean注解,有的话,就注册。

只看其中判断有没有注解的代码。

java 复制代码
// 判断该类是否有某注解
protected Boolean hasAnnotation(String typeName) {
    try {
    	// 获取class对象
        Class<?> clazz = ClassUtils.forName(typeName, this.getClass().getClassLoader());
        // 关键是clazz.getAnnotation,获取类上的注解
        return (this.considerMetaAnnotations ? AnnotationUtils.getAnnotation(clazz, this.annotationType) : clazz.getAnnotation(this.annotationType)) != null;
    } catch (Throwable var3) {
    	// ...
    }
}

二、怎样创建自定义注解

某些场景使用自定义注解,能大大简化代码。例如调用某controller方法时,添加认证信息;两个对象之间属性拷贝,处理不同名属性等。

下面以对象拷贝为例,例如对象A,对象B之间的拷贝,期望自动处理不同名属性时。

java 复制代码
// 原实体
@Data
public class Source {

    // 按属性名称转
    @PropertyTransform(name = "targetName",transformStrategy = TransformStrategy.DEFAULT)
    String sourceName;
}

// 目标实体
@Data
public class Target {
    String targetName;
}

// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Component
public @interface PropertyTransform {

    /**
     * 目标属性
     * @return
     */
    @AliasFor("name")
    String value() default "";

    /**
     * 目标属性
     * @return
     */
    @AliasFor("value")
    String name() default "";
}



// 注解处理器
public class PropertiesTransformUtils {

    @SneakyThrows
    public static Object copy(Object source,Class<?> target){

        Class<?> sourceClass = source.getClass();

        //获取原实体下的所有属性
        Field[] fields = sourceClass.getDeclaredFields();

		// 初始化目标实体
		Object targetObj = target.newInstance();
		
		//记录转换名称后的属性名以及属性值
        Map<String,Object> objMap = new HashMap<>(fields.length);

        for (Field field : fields) {
            field.setAccessible(true);
            //获取属性上的注解
            PropertyTransform propertyTransform = field.getAnnotation(PropertyTransform.class);
			if (propertyTransform != null){
				objMap.put(propertyTransform.name(),field.get(source));
			}

	      //将值拷贝到目标实体
	       Object targetObj = target.newInstance();
	       for (Field declaredField : target.getDeclaredFields()) {
	           Object value = objMap.getOrDefault(declaredField.getName(),null);
	           declaredField.setAccessible(true);
	           declaredField.set(targetObj,value);
	       }

        return targetObj;
    }

总结

注解的实现分为三步:定义注解;在代码中使用注解;注解处理器赋予相应的功能。不管是框架内的注解,还是自定义注解,都是按照这三步来的。不太好理解的可能就是注解处理器赋予的各种功能。

相关推荐
栗子~~1 分钟前
bat脚本- 将jar 包批量安装到 Maven 本地仓库
java·maven·jar
罗光记12 分钟前
腾讯混元游戏视觉生成平台正式发布2.0版本
数据库·经验分享·百度·facebook·开闭原则
我科绝伦(Huanhuan Zhou)21 分钟前
达梦数据守护集群监视器详解与应用指南
数据库
Mr.Entropy29 分钟前
ecplise配置maven插件
java·maven
zhangfeng113335 分钟前
BiocManager下载失败 R语言 解决办法
开发语言·r语言
叙白冲冲1 小时前
tomcat 为啥能一直运行?不像方法那样结束?
java·tomcat
CoderYanger1 小时前
MySQL数据库——3.2.1 表的增删查改-查询部分(全列+指定列+去重)
java·开发语言·数据库·mysql·面试·职场和发展
迷知悟道1 小时前
java面向对象四大核心特征之抽象---超详细(保姆级)
java·后端
炮院李教员1 小时前
使用Qt Core模块(无GUI依赖),确保程序作为后台服务/daemon运行,与任何GUI完全无交互。
开发语言·qt
歪歪1002 小时前
Qt Creator 打包应用程序时经常会遇到各种问题
开发语言·c++·qt·架构·编辑器