一、概念

Java 注解是在 JDK5 时引入的新特性,注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。注解类型定义指定了一种新的类型,一种特殊的接口类型。 在关键词 interface 前加 @ 符号也就是用 @interface 来区分注解的定义和普通的接口声明。目前大部分框架(如 Spring Boot 等)都通过使用注解简化了代码并提高的编码效率。

二、作用

  • 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息,如 @Override、@Deprecated。
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html 文档或者做其它相应处理,如 @Param、@Return、@See、@Author 用于生成 Javadoc 文档。
  • 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取,值得注意的是,注解不是代码本身的一部分。如Spring 2.5 开始注解配置,减少了配置。

三、定义

2.1 注解的本质

所有的注解本质上都是继承自 Annotation 接口。但是,手动定义一个接口继承 Annotation 接口无效的,需要通过 @interface 声明注解,Annotation 接口本身也不定义注解类型,只是一个普通的接口。

  1. public interface Annotation {
  2. boolean equals(Object obj);
  3. int hashCode();
  4. String toString();
  5. /**
  6. *获取注解类型
  7. */
  8. Class<? extends Annotation> annotationType();
  9. }

来对比下 @interface 定义注解和继承 Annotation 接口

  1. public @interface TestAnnotation1 {
  2. }
  3. public interface TestAnnotation2 extends Annotation {
  4. }

通过使用 javap 指令对比两个文件的字节码,发现通过 @interface 定义注解,本质上就是继承 Annotation 接口。

  1. // javap -c TestAnnotation1.class
  2. Compiled from "TestAnnotation1.java"
  3. public interface com.hncboy.corejava.annotation.TestAnnotation1 extends java.lang.annotation.Annotation {}
  4. // javap -c TestAnnotation2.class
  5. Compiled from "TestAnnotation2.java"
  6. public interface com.hncboy.corejava.annotation.TestAnnotation2 extends java.lang.annotation.Annotation {}

虽然本质上都是继承 Annotation 接口,但即使接口可以实现多继承,注解的定义仍然无法使用继承关键字来实现。

通过 @interface 定义注解后,该注解也不能继承其他的注解或接口,注解是不支持继承的,如下代码就会报错。

  1. public @interface TestAnnotation1 {
  2. }
  3. /** 错误的定义,注解不能继承注解 */
  4. @interface TestAnnotation2 extends TestAnnotation1 {
  5. }
  6. /** 错误的定义,注解不能继承接口 */
  7. @interface TestAnnotation3 extends Annotation {
  8. }

虽然注解不支持继承其他注解或接口,但可以使用组合注解的方式来解决这个问题。如 @SpringBootApplication 就采用了组合注解的方式。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  8. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  9. public @interface SpringBootApplication {
  10. }

2.2 注解的架构

😀   什么是注解 - 图1

注解的基本架构如图所示,先简单了解下该架构,后面会详细讲解。

该架构的左半部分为基本注解的组成,一个基本的注解包含了 @interface 以及 ElementType 和 RententionPolicy 这两个枚举类。

  • Annotation 和 ElementType 是一对多的关系
  • Annotation 和 RetentionPolicy 是一对一的关系

该架构的右半部分为 JDK 部分内置的标准注解及元注解。

  • 标准注解:@Override、@Deprecated
  • 元注解:@Documented、@Retention、@Target、@Inherited

2.3 注解的属性

注解的属性也称为成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

注解内的可使用的数据类型是有限制的,类型如下:

  • 所有的基本类型(int,float,boolean 等)
  • String
  • Class
  • enum(@Retention 中属性的类型为枚举)
  • Annotation
  • 以上类型的数组(@Target 中属性类型为枚举类型的数组)

编译器对属性的默认值也有约束。首先,属性不能有不确定的的值。也就是说,属性要么具有默认值,要么在使用注解时提供属性的值。对于非基本类型的属性,无论是在源代码中声明时,或是在注解接口中定义默认值时,都不能使用 null 为其值。因此,为了绕开这个约束,我们需要自己定义一些特殊的值,例如空字符串或负数,来表示某个属性不存在。

通过一个案例来演示下注解可使用的数据类型及默认值。

  1. @interface Reference {
  2. boolean contain() default false;
  3. }
  4. enum Week {
  5. Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
  6. }
  7. public @interface TestAnnotation {
  8. /**
  9. * int 基本数据类型
  10. * @return
  11. */
  12. int type() default -1;
  13. /**
  14. * boolean 基本数据类型
  15. * @return
  16. */
  17. boolean status() default false;
  18. /**
  19. * String 类型
  20. * @return
  21. */
  22. String name() default "";
  23. /**
  24. * Class 类型
  25. * @return
  26. */
  27. Class<?> loadClass() default String.class;
  28. /**
  29. * 枚举类型
  30. * @return
  31. */
  32. Week today() default Week.Sunday;
  33. /**
  34. * 注解类型
  35. * @return
  36. */
  37. Reference reference() default @Reference(contain = true);
  38. /**
  39. * 枚举数组类型
  40. * @return
  41. */
  42. Week[] value();
  43. }

四、组成

我们已经了解了注解的架构,先来定义一个简单的注解。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TestAnnotation {
  4. }

4.1 ElementType

ElementType 枚举类型的常量为 Java 程序中可能出现注解的声明位置提供了简单的分类。这些常量用于 @Target 注解中。@Target 用于描述注解适用的范围,即注解能修饰的对象范围,通过 ElementType 的枚举常量表示。

先来看下 ElementType 该枚举类的代码。

  1. public enum ElementType {
  2. /**
  3. * 用于描述类、接口(包括注解类型)、枚举的定义
  4. */
  5. TYPE,
  6. /**
  7. * 用于描述成员变量、对象、属性(包括枚举常量)
  8. */
  9. FIELD,
  10. /**
  11. * 用户描述方法
  12. */
  13. METHOD,
  14. /**
  15. * 用于描述参数
  16. */
  17. PARAMETER,
  18. /**
  19. * 用于描述构造器
  20. */
  21. CONSTRUCTOR,
  22. /**
  23. * 用于描述局部变量
  24. */
  25. LOCAL_VARIABLE,
  26. /**
  27. * 用于描述注解的(元注解)
  28. */
  29. ANNOTATION_TYPE,
  30. /**
  31. * 用于描述包
  32. */
  33. PACKAGE,
  34. /*
  35. * 表示该注解能写在类型变量的声明语句中
  36. * @since 1.8
  37. */
  38. TYPE_PARAMETER,
  39. /**
  40. * 表示该注解能写在使用类型的任何语句中(声明语句、泛型和强制转换语句中的类型)
  41. * @since 1.8
  42. */
  43. TYPE_USE
  44. }

因为 Annotation 和 ElementType 是一对多的关系,所以 @Target 中可以存放数组,表示多个范围,默认所有范围。

JDK8 之前,注解只能用于声明的地方,JDK8 中添加了 TYPE_PARAMETER 和 TYPE_USE 类型注解,可以应用于所有地方:泛型、父类、接口,异常、局部变量等。举个例子,定义一个 @AnyWhere 注解,Boy 接口和 Test 类。

  1. @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface AnyWhere {
  4. }
  5. public interface Boy {
  6. }
  7. public class Test<@AnyWhere T> extends @AnyWhere Object implements @AnyWhere Boy {
  8. private @AnyWhere T test1(@AnyWhere T t) throws @AnyWhere Exception {
  9. return t;
  10. }
  11. private void test2() {
  12. Test<Integer> test = new @AnyWhere Test<>();
  13. @AnyWhere List<@AnyWhere Integer> list = new ArrayList<>();
  14. }
  15. }

4.2 RetentionPolicy

RetentionPolicy 枚举类型的常量用于保留注解的各种策略,即该注解的有效期。它们与 @Retention 注解类型一起使用,以指定保留注解的时间。RetentionPolicy 枚举的代码如下。

  1. public enum RetentionPolicy {
  2. /**
  3. * 表示该注解只存在于源码阶段,
  4. */
  5. SOURCE,
  6. /**
  7. * 表示该注解存在于源码阶段和编译后的字节码文件里
  8. */
  9. CLASS,
  10. /**
  11. * 表示该注解存在于源码阶段、编译后的字节码文件和运行时期,且注解的内容将会被 JVM 解释执行
  12. * 该范围的注解可通过反射获取到
  13. */
  14. RUNTIME
  15. }

Annotation 和 RetentionPolicy 是一对一的关系,即每个注解只能有一种保留策略。

这三个枚举值是有等级关系的,SOURCE < CLASS < RUNTIME,即 RUNTIME 的有效范围是最大的,其次的是 CLASS,最小的范围是 SOURCE,默认的保留范围为 CLASS。

  • RUNTIME 范围使用于在运行期间通过反射的方式去获取注解。
  • CLASS 适用于编译时进行一些预处理操作。
  • SOURCE 适用于一些检查性的工作,或者生成一些辅助的代码,如 @Override 检查重写的方法,Lombok 中的 @Date、@Getter、@Setter 注解。

4.3 注解与反射

通过前面我们了解到,注解本质上继承 Annotation 接口,也就是说,Annotation 接口是所有注解的父接口。@Retention 的保留策略为 RetentionPolicy.RUNTIME 的情况下,我们可以通过反射获取注解的相关信息。Java 在 java.lang.reflect 包下也提供了对注解支持的接口。

😀   什么是注解 - 图2

主要来了解下 AnnotationElement 这个接口,其他接口都为该接口的子接口。该接口的对象代表 JVM 运行期间使用注解的类型(Class,Method,Field 等)。该包下的 Constructor 类、Method 类、Package 类和 Class 类等都实现了该接口。简单了解下该接口的部分函数。

  1. public interface AnnotatedElement {
  2. /**
  3. * default 方法是 Java8 新增的
  4. * 如果指定类型的注解存在该类型上,则返回 true,否则返回 false。此方法的主要目的是方便访问一些已知的注解
  5. *
  6. * @param annotationClass 该泛型参数表示所有继承了Annotation 接口的接口,也就是注解
  7. * @return 返回该类型上是否有指定的注解
  8. */
  9. default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
  10. return getAnnotation(annotationClass) != null;
  11. }
  12. /**
  13. * 根据注解的 Class 查询注解
  14. */
  15. <T extends Annotation> T getAnnotation(Class<T> annotationClass);
  16. /**
  17. * 返回该类型上的所有注解,包含继承的
  18. */
  19. Annotation[] getAnnotations();
  20. /**
  21. * 返回该类型上的所有注解,不包含继承的
  22. */
  23. Annotation[] getDeclaredAnnotations();
  24. }

我们使用代码来测试下反射获取注解。定义两个注解,一个保留策略为 RetentionPolicy.RUNTIME,另一个为 RetentionPolicy.CLASS。创建 TestAnnotation 类测试注解,该类上使用了这两个注解。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. public @interface TestAnnotation1 {
  3. String status() default "hncboy";
  4. }
  5. @Retention(RetentionPolicy.CLASS)
  6. public @interface TestAnnotation2 {
  7. String value() default "hncboy";
  8. }
  9. @TestAnnotation1(status = "hncboy2")
  10. @TestAnnotation2("hncboy2")
  11. public class TestAnnotation {
  12. public static void main(String[] args) throws ClassNotFoundException {
  13. Class<?> clazz = Class.forName("com.hncboy.corejava.annotation.TestAnnotation");
  14. // 获取该类的所有注解
  15. Annotation[] annotations = clazz.getAnnotations();
  16. for (Annotation annotation : annotations) {
  17. System.out.println(annotation.annotationType());
  18. System.out.println(annotation.toString());
  19. }
  20. }
  21. }

输出结果如下,可见 TestAnnotation2 注解没有输出,因为 TestAnnotation2 注解类型是 RetentionPolicy.CLASS 的,所以用反射方法获取不到。这里还涉及到了注解的一个快捷方法,就是当注解里的属性名字定义为 value 时,可以在使用该注解时不指定属性名,上面的 @Target 注解和 @Retention 注解都属于这种情况,不过当注解里有多个属性时,那就必须指定属性名了。

  1. interface com.hncboy.corejava.annotation.TestAnnotation1
  2. @com.hncboy.corejava.annotation.TestAnnotation1()(status=hncboy2)

五、元注解

元注解即注解的注解且只能作用于注解上的注解,也就是说元注解负责其他注解的注解,而且只能用在注解上面。

JDK8 以前内置的元注解有 @Documented、@Retention、@Target、@Inherited 这四个,JDK 8 引入了 @Repeatable, 前面已经了解过了 @Target 和 @Retention,下面做一些简单的补充。

元注解的 @Target 都为 ElementType.ANNOTATION_TYPE,因为元注解只能应用于注解的注解。元注解在定义该注解的同时也可以直接使用该注解。

5.1 @Target

该注解用于定义注解能使用的范围,取值为 ElementType 枚举。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Target {
  5. /**
  6. * 返回可以应用注解类型的各种范围的枚举数组
  7. * 名字为 value 时可以省略属性名
  8. * @return
  9. */
  10. ElementType[] value();
  11. }

使用方式:

  1. @Target(ElementType.METHOD)
  2. @Target(value = ElementType.METHOD)
  3. @Target({ElementType.METHOD, ElementType.TYPE})
  4. @Target(value = {ElementType.METHOD, ElementType.TYPE})

5.2 @Retention

该注解定义注解的保留策略或者说定义注解的有效期,取值范围为 RetationPolicy 枚举。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Retention {
  5. /**
  6. * 返回保留策略
  7. * @return
  8. */
  9. RetentionPolicy value();
  10. }

使用方式:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Retention(value = RetentionPolicy.RUNTIME)

5.3 @Documented

该注解的使用表示是否包含在生成的 javadoc 文档中。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Documented {
  5. }

举个例子,定义一个 @TestAnnotation 注解和 Test 类。

  1. @Retention(value = RetentionPolicy.RUNTIME)
  2. @Documented
  3. public @interface TestAnnotation {
  4. }
  5. @TestAnnotation
  6. public class Test {
  7. }

通过 javadoc -d doc *.java 命令将该目录下的这两个类生成文档并放在 doc 目录下。生成的文件如下,点击 index.html。

😀   什么是注解 - 图3
看到如图所示的样子,Test 类中包含 @TestAnnotation。

😀   什么是注解 - 图4

我们再把 @TestAnnotation 注解上的 @Documenet 注解注释掉再来生成下文档。此时发现 Test 类中没有 @TestAnnotation 注解了。

😀   什么是注解 - 图5

5.4 @Inherited

该注解表示注解是否具有继承的特性。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Inherited {
  5. }

举个例子来测试下。新建 TestAnnotation 注解,Father 类,Son 类,Father 类使用了该注解,Son 类继承 Father 类。

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Inherited
  3. public @interface TestAnnotation {
  4. }
  5. @TestAnnotation
  6. public class Father {
  7. }
  8. public class Son extends Father {
  9. }

新建一个测试类,测试 Father 和 Son 这两个类是否包这两个注解。

  1. public class Test {
  2. public static void main(String[] args) {
  3. System.out.println(Father.class.isAnnotationPresent(TestAnnotation.class));
  4. System.out.println(Son.class.isAnnotationPresent(TestAnnotation.class));
  5. }
  6. }

输出为 true true,当把 @TestAnnotation 注解上的 @Inherited 注解注释掉时,输出 true false,如此可见该注解的作用。

5.5 @Repeatable

JDK8 以前是不支持重复注解的,同一个地方只能使用同一个注解一次。 该注解从 JDK8 引入,该注解类型用于表示其声明注解的注解类型为可重复时。 value() 的值表示可重复注解的类型,包含注解类型。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Repeatable {
  5. /**
  6. * 指可重复注解的类型,包含注解类型
  7. * @return
  8. */
  9. Class<? extends Annotation> value();
  10. }

举个例子,定义 @Activity 和 @Activities 注解,定义 Hncboy 类测试重复注解。@Activity 注解被 @Repeatable(Activities.class) 注解,@Activities 相当于一个容器注解,属性为 Activity 类型的数组,通过这样的方式,使得 @Activity 注解可以被重复使用。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Activities {
  4. Activity[] value();
  5. }
  6. @Target(ElementType.TYPE)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Repeatable(Activities.class)
  9. public @interface Activity {
  10. String value();
  11. }
  12. @Activity("打代码")
  13. @Activity("吃饭")
  14. @Activity("睡觉")
  15. public class Hncboy {
  16. }
  17. @Activities({@Activity("打代码"), @Activity("吃饭"), @Activity("睡觉")})
  18. public class Hncboy {
  19. }

六、标准注解

JDK 内置的注解有 @Deprecated、@Override、@SuppressWarnnings、@SafeVarargs(JDK 7 引入)、@FunctionalInterface(JDK 引入)等。接下来介绍下 3 种常用的内置注解。

6.1 @Deprecated

注解为 @Deprecated 的类型是不鼓励程序员使用的元素,通常是因为这样做很危险,或者是因为存在更好的替代方法。当在不推荐使用的代码中使用或覆盖不推荐使用的程序元素时,编译器会发出警告。该注解可以用来修饰构造器、字段、局部变量、方法等类型。

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

举个例子,使用 @Deprecated 修饰的元素是不推荐使用的,编译器会帮我们将这些类和方法用删除线标记。直接声明在包上会报 Package annotations should be in file package-info.java 错误。

  1. @Deprecated
  2. public class TestDeprecated {
  3. @Deprecated
  4. String s = "hncboy";
  5. @Deprecated
  6. public void test() {
  7. }
  8. }

😀   什么是注解 - 图6

6.2 @Override

@Override 注解我们经常用到,提示子类需要重写父类的方法。方法重写或实现了在父类中声明的方法时需要加上该注解,该注解用于编译器检查重写的操作是否正确,保留策略为 RetentionPolicy.SOURCE。

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface Override {
  4. }

6.3 @SuppressWarnings

用来关闭编译器生成警告信息,可以用来修饰类、方法、成员变量等,在使用该注解时,应采用就近原则,如方法产生警告是,应该针对方法声明该注解,而不是对类声明,有利于发现该类的其他警告信息。

  1. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface SuppressWarnings {
  4. /**
  5. * 带有注解的元素中的编译器将禁止的警告集。
  6. * 使用 unchecked 忽略无法识别的警告
  7. */
  8. String[] value();
  9. }

举个例子,rawtypes 用于使用泛型时忽略没有指定相应的类型,unused 用于没有使用过的代码。

  1. public class Test {
  2. @SuppressWarnings({"rawtypes", "unused"})
  3. private List test() {
  4. return new ArrayList();
  5. }
  6. }

七、自定义注解

自定义注解实现 Spring IOC Bean 实例创建,自定义简单的注解: @Component、@Bean 和 @ComponentScan。

通过什么是反射?这篇文章我们已经学习到通过反射实现 Spring IOC Bean 实例的三种创建方式,不清楚的可以去看下那篇文章。

7.1 新建 @MyComponent、@MyBean、 @MyComponentScan

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.METHOD})
  3. public @interface MyBean {
  4. String value() default "";
  5. }
  6. @Target({ElementType.TYPE})
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface MyComponent {
  9. String value() default "";
  10. }
  11. @Target({ElementType.TYPE})
  12. @Retention(RetentionPolicy.RUNTIME)
  13. public @interface MyComponentScan {
  14. String value() default "";
  15. }

7.2 新建 A、B、C 三个类

  1. @MyComponent("a")
  2. public class A {
  3. public A() {
  4. System.out.println("调用 A 的无参构造器");
  5. }
  6. @MyBean("b")
  7. public static B createBInstance() {
  8. System.out.println("调用 A 的静态方法 createBInstance");
  9. return new B();
  10. }
  11. @MyBean("c")
  12. public C createCInstance() {
  13. System.out.println("调用 A 的实例方法 createCInstance");
  14. return new C();
  15. }
  16. }
  17. class B {}
  18. class C {}

7.3 新建 IOCContainer 类

  1. /**
  2. * 定义 map 存放 bean
  3. */
  4. public class IOCContainer {
  5. private static HashMap<String, Object> container = new HashMap<>();
  6. public static void putBean(String id, Object object) {
  7. container.put(id, object);
  8. }
  9. public static Object getBean(String id) {
  10. return container.get(id);
  11. }
  12. }

7.4 新建 Test 类

  • 先获取 @MyComponentScan 注解中的包名
  • 然后扫描该包下所有类的全限定名
  • 遍历类名,判断改类是否实现 @MyComponent 注解
  • 遍历方法,判断该方法是否实现 @MyBean 注解

大致过程是这样,具体的可以见代码的注释。

  1. @MyComponentScan("com.hncboy.corejava.annotation.spring")
  2. public class Test {
  3. public static void main(String[] args) throws Exception {
  4. Test test = new Test();
  5. // 获取 MyComponentScan 注解中的包名
  6. String scanPackage = test.getScanPackage();
  7. HashSet<String> classPathSet = new HashSet<>();
  8. // 扫描包下的所有类并将类的全限定名放进 classPathSet
  9. test.doScanPackage(classPathSet, scanPackage);
  10. // 遍历扫描包下的所有类
  11. for (String className : classPathSet) {
  12. // 通过类的全限定名获取 Class
  13. Class<?> clazz = Class.forName(className);
  14. // 判断该类是否实现了 MyComponent 注解
  15. if (clazz.isAnnotationPresent(MyComponent.class)) {
  16. // 方式1:通过构造器实例化
  17. IOCContainer.putBean(className, clazz.newInstance());
  18. }
  19. Method[] methods = clazz.getDeclaredMethods();
  20. for (Method method : methods) {
  21. // 判断方法是否有 MyBean 注解
  22. if (method.isAnnotationPresent(MyBean.class)) {
  23. // 获取 bean 值
  24. String beanName = method.getAnnotation(MyBean.class).value();
  25. // 判断该方法是否是静态方法或实例方法
  26. if (Modifier.isStatic(method.getModifiers())) {
  27. // 方式2:通过静态工厂实例化
  28. IOCContainer.putBean(beanName, method.invoke(null));
  29. } else {
  30. // 方式3:通过实例工厂实例化
  31. // 首先获取该类的实例对象,再调用实例方法进行实例化
  32. IOCContainer.putBean(beanName, method.invoke(IOCContainer.getBean(className)));
  33. }
  34. }
  35. }
  36. }
  37. }
  38. /**
  39. * 获取 MyComponentScan 注解中的包名
  40. *
  41. * @return
  42. */
  43. private String getScanPackage() {
  44. Class<?> clazz = this.getClass();
  45. if (!clazz.isAnnotationPresent(MyComponentScan.class)) {
  46. return "";
  47. }
  48. MyComponentScan scanPackage = clazz.getDeclaredAnnotation(MyComponentScan.class);
  49. return scanPackage.value();
  50. }
  51. /**
  52. * 扫描该包下的类
  53. *
  54. * @param classPathSet
  55. * @param scanPackage
  56. */
  57. private void doScanPackage(HashSet<String> classPathSet, String scanPackage) {
  58. // 通过正则表达式将包名中的 . 替代为 /,并获取到该路径的 class url
  59. URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));
  60. // 获取该 url 下的所有 File(目录/文件)
  61. File classDir = new File(url.getFile());
  62. // 遍历所有 File
  63. for (File file : classDir.listFiles()) {
  64. // 判断该 file 如果是目录的话
  65. if (file.isDirectory()) {
  66. // 拼接该目录的名字并递归遍历该目录
  67. doScanPackage(classPathSet, scanPackage + "." + file.getName());
  68. } else {
  69. // 如果文件不是以 .class 结尾
  70. if (!file.getName().endsWith(".class")) {
  71. continue;
  72. }
  73. // 通过 包名+目录名+除去.class的类名 拼接该类的全限定名
  74. String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
  75. // 将该类的全限定名放入 classPathSet
  76. classPathSet.add(clazzName);
  77. }
  78. }
  79. }
  80. }

输出如下:

  1. 调用 A 的无参构造器
  2. 调用 A 的静态方法 createBInstance
  3. 调用 A 的实例方法 createCInstance

注:APT——这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。

Java 编程思想 Java Annotation认知