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;
    }

总结

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

相关推荐
丁总学Java1 分钟前
如何使用 maxwell 同步到 redis?
数据库·redis·缓存
爱吃南瓜的北瓜9 分钟前
Redis的Key的过期策略是怎样实现的?
数据库·redis·bootstrap
万物得其道者成10 分钟前
React Zustand状态管理库的使用
开发语言·javascript·ecmascript
学步_技术15 分钟前
Python编码系列—Python抽象工厂模式:构建复杂对象家族的蓝图
开发语言·python·抽象工厂模式
一心只为学23 分钟前
Oracle密码过期问题,设置永不过期
数据库·oracle
【D'accumulation】29 分钟前
典型的MVC设计模式:使用JSP和JavaBean相结合的方式来动态生成网页内容典型的MVC设计模式
java·设计模式·mvc
小光学长32 分钟前
基于vue框架的宠物销售管理系统3m9h3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库
wn53139 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
试行43 分钟前
Android实现自定义下拉列表绑定数据
android·java
茜茜西西CeCe1 小时前
移动技术开发:简单计算器界面
java·gitee·安卓·android-studio·移动技术开发·原生安卓开发