1 Java注解基础

注解是JDK1.5版本开始引入的一个特性,用于对程序代码的说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
它主要的作用有以下四方面:

  • 生成javadoc文档,通过在代码里面标识元数据生成javadoc文档。
  • 编译期的检查,通过标识的元数据让编译器在编译期间对代码进行验证。
  • 编译时动态处理,编译时通过代码中标识的元数据动态处理,比如动态生成代码。
  • 运行时动态处理,运行时通过代码中标识的元数据动态处理,比如使用反射技术注入实例。

注解的常见分类有三种:

  • Java自带的标准注解,包括 @Override、@Deprecated和@SuppressWarnings,分别代表 方法重写、某个类或方法过时、以及忽略警告,用这些注解标明后编译器就会进行检查。
  • 元注解,元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented 等6种
    • @Retention:指定其所修饰的注解的保留策略
    • @Document:该注解是一个标记注解,用于指示一个注解将被文档化
    • @Target:用来限制注解的使用范围
    • @Inherited:该注解使父类的注解能被其子类继承
    • @Repeatable:该注解是Java8新增的注解,用于开发重复注解
    • 类型注解(Type Annotation):该注解是Java8新增的注解,可以用在任何用到类型的地方
  • 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。

接下来我们通过这三种分类来逐一理解注解。

1.1 Java内置注解

我们先从Java内置注解开始说起,先看下下面的代码:

class Parent {
    public void rewriteMethod() {

    }
}

class Child extends Parent {

    /**
        * 重载父类的 rewriteMethod() 方法
        */
    @Override
    public void rewriteMethod() {
    }

    /**
        * 被弃用的过时方法
        */
    @Deprecated
    public void oldMethod() {
    }

    /**
        * 忽略告警
        *
        * @return
        */
    @SuppressWarnings("keep run")
    public List infoList() {
        List list = new ArrayList();
        return list;
    }
}

Java 1.5开始自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings:

  • @Override:表示当前类中的方法定义将覆盖父类中的方法
  • @Deprecated:表示该代码段被弃用,但是可以使用,只是编译器会发出警告而已
  • @SuppressWarnings:表示关闭编译器的警告信息
    我们再具体看下这几个内置注解,同时通过这几个内置注解中的元注解的定义来引出元注解。

1.1.1 内置注解 - @Override

我们先来看一下这个注解类型的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,
若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

1.1.2 内置注解 - @Deprecated

这个注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

1.1.3 内置注解 - @SuppressWarnings

这个注解我们也比较常用到,先来看下它的定义:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:


返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  • Annotation[] getAnnotations()
    返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。

  • 返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。getAnnotationsByType方法与 getAnnotation的区别在于,getAnnotationsByType会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。

  • 返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null

  • 返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释
  • Annotation[] getDeclaredAnnotations()
    返回直接存在于此元素上的所有注解及注解对应的重复注解容器。与此接口中的其他方法不同,该方法将忽略继承的注解。如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。
  • 1.4 自定义注解

    • 定义自己的注解
    package com.helenlyn.common.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * <p>Description: 水果供应者注解        </p>
     * <p>Copyright: Copyright (c) 2021 </p>
     * <p>Company: helenlyn Co., Ltd.             </p>
     *
     * @author brand
     * @date 2021/5/16 16:35
     * <p>Update Time:                      </p>
     * <p>Updater:                          </p>
     * <p>Update Comments:                  </p>
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface FruitProvider {
        /**
         * 供应商编号
         * @return
         */
        public int id() default -1;
    
        /**
         * 供应商名称
         * @return
         */
        public String name() default "";
    
        /**
         * 供应商地址
         * @return
         */
        public String address() default "";
    }
    
    • 使用注解
    package com.helenlyn.common.dto;
    
    import com.helenlyn.common.annotation.FruitColor;
    import com.helenlyn.common.annotation.FruitName;
    import com.helenlyn.common.annotation.FruitProvider;
    
    /**
     * <p>Description:               </p>
     * <p>Copyright: Copyright (c) 2021 </p>
     * <p>Company: helenlyn Co., Ltd.             </p>
     *
     * @author brand
     * @date 2021/5/16 16:28
     * <p>Update Time:                      </p>
     * <p>Updater:                          </p>
     * <p>Update Comments:                  </p>
     */
    public class AppleDto {
       @FruitName("Apple")
        private String appleName;
    
        @FruitColor(fruitColor=FruitColor.Color.RED)
        private String appleColor;
    
        @FruitProvider(id=1,name="helenlyn 贸易公司",address="福州xx路xxx大楼")
        private String appleProvider;
    }
    
    
    • 用反射接口获取注解信息
      在 FruitInfoUtil 中进行测试:
    /**
     * <p>Description: FruitInfoUtil注解实现 </p>
     * <p>Copyright: Copyright (c) 2021 </p>
     * <p>Company: helenlyn Co., Ltd.             </p>
     *
     * @author brand
     * @date 2021/5/16 16:37
     * <p>Update Time:                      </p>
     * <p>Updater:                          </p>
     * <p>Update Comments:                  </p>
     */
    public class FruitInfoUtil {
        public static String getFruitInfo(Class<?> clazz) {
            String strFruitName = " 水果名称:";
            String strFruitColor = " 水果颜色:";
            String strFruitProvicer = "供应商信息:";
    
            Field[] fields = clazz.getDeclaredFields();
    
            for (Field field : fields) {
                if (field.isAnnotationPresent(FruitName.class)) {
                    FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
                    strFruitName += fruitName.value();
                    System.out.println(strFruitName);
                } else if (field.isAnnotationPresent(FruitColor.class)) {
                    FruitColor fruitColor = (FruitColor) field.getAnnotation(FruitColor.class);
                    strFruitColor += fruitColor.fruitColor().toString();
                    System.out.println(strFruitColor);
                } else if (field.isAnnotationPresent(FruitProvider.class)) {
                    FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
                    strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" + fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
                    System.out.println(strFruitProvicer);
                }
            }
            return String.format("%s;%s;%s;", strFruitName, strFruitColor, strFruitProvicer);
        }
    }
    
    
    • 测试的输出
    2022-07-09 11:33:41.688  INFO 5895 --- [TaskExecutor-35] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: cl-debug-rabbitmq-erp-service-7w0cpa.docker.sdp:9146
    Hibernate: update UserBasicInfo set personName=? where personCode=?
     水果名称:Apple
     水果颜色:RED
     供应商编号:1 供应商名称:helenlyn 贸易公司 供应商地址:福州xx路xxx大楼
    

    2 理解注解的原理

    2.1 Java 8 提供了哪些新的注解

    • @Repeatable
    • ElementType.TYPE_USE
    • ElementType.TYPE_PARAMETER

    ElementType.TYPE_USE(此类型包括类型声明和类型参数声明,是为了方便设计者进行类型检查)包含了ElementType.TYPE(类、接口(包括注解类型)和枚举的声明)和ElementType.TYPE_PARAMETER(类型参数声明), 可以看下面这个例子:

    // 自定义ElementType.TYPE_PARAMETER注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_PARAMETER)
    public @interface MyNotEmpty {
    }
    
    // 自定义ElementType.TYPE_USE注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_USE)
    public @interface MyNotNull {
    }
    
    // 测试类
    public class TypeParameterAndTypeUseAnnotation<@MyNotEmpty T>{
    
      //使用TYPE_PARAMETER类型,会编译不通过
    //		public @MyNotEmpty T test(@MyNotEmpty T a){
    //			new ArrayList<@MyNotEmpty String>();
    //				return a;
    //		}
    
      //使用TYPE_USE类型,编译通过
      public @MyNotNull T test2(@MyNotNull T a){
        new ArrayList<@MyNotNull String>();
        return a;
      }
    }
    
    

    2.2 注解支持继承吗?

    3 注解的应用场景

    自定义注解多喝AOP - 通过切面实现解耦

    • 自定义Annotation,映射的目标范围为 类型和方法。
    /**
     * @author brand
     * @Description: 数据源切换注解
     * @Copyright: Copyright (c) 2021
     * @Company: Helenlyn, Inc. All Rights Reserved.
     * @date 2021/12/15 7:36 下午
     */
    @Target({ ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        String name() default "";
    }
    
    • 编写AOP实现,切面代码,以实现对注解的PointCut,切点拦截
    /**
     * @author brand
     * @Description:
     * @Copyright: Copyright (c) 2021
     * @Company: Helenlyn, Inc. All Rights Reserved.
     * @date 2021/12/15 7:49 下午
     */
    @Aspect
    @Component
    public class DataSourceAspect implements Ordered  {
        /**
         * 定义一个切入点,匹配到上面的注解DataSource
         */
        @Pointcut("@annotation(com.helenlyn.dataassist.annotation.DataSource)")
        public void dataSourcePointCut() {
        }
    
        /**
         * Around 环绕方式做切面注入
         * @param point
         * @return
         * @throws Throwable
         */
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            DataSource ds = method.getAnnotation(DataSource.class);
            String routeKey = ds.name();  // 从头部中取出注解的name(basic 或 cloudoffice 或 attend),用这个name进行数据源查找。
            String dataSourceRouteKey = DynamicDataSourceRouteHolder.getDataSourceRouteKey();
            if (StringUtils.isNotEmpty(dataSourceRouteKey)) {
                // StringBuilder currentRouteKey = new StringBuilder(dataSourceRouteKey);
                routeKey = ds.name();
            }
            DynamicDataSourceRouteHolder.setDataSourceRouteKey(routeKey);
            try {
                return point.proceed();
            } finally { // 最后做清理,这个步骤很重要,因为我们的配置中有一个默认的数据源,执行完要回到默认的数据源。
                DynamicDataSource.clearDataSource();
                DynamicDataSourceRouteHolder.clearDataSourceRouteKey();
            }
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }
    
    
    • 测试,在Control中写三个测试方法
    /**
         * 无注解默认情况:数据源指向basic
         * @return
         */
        @RequestMapping(value = "/default/{user_code}", method = RequestMethod.GET)
        public UserInfoDto getUserInfo(@PathVariable("user_code") String userCode) {
            return userInfoService.getUserInfo(userCode);
        }
    
        /**
         * 数据源指向attend
         * @return
         */
        @DataSource(name= Constant.DATA_SOURCE_ATTEND_NAME)
        @RequestMapping(value = "/attend/{user_code}", method = RequestMethod.GET)
        public UserInfoDto getUserInfoAttend(@PathVariable("user_code") String userCode) {
            return userInfoService.getUserInfo(userCode);
        }
    
        /**
         * 数据源指向cloud
         * @return
         */
        @DataSource(name= Constant.DATA_SOURCE_CLOUD_NAME)
        @RequestMapping(value = "/cloud/{user_code}", method = RequestMethod.GET)
        public UserInfoDto getUserInfoCloud(@PathVariable("user_code") String userCode) {
            return userInfoService.getUserInfo(userCode);
        }
    
    
    • 执行效果
      Java核心知识体系2:注解机制详解-LMLPHP
      Java核心知识体系2:注解机制详解-LMLPHP
      Java核心知识体系2:注解机制详解-LMLPHP
    07-20 21:13