Java中注解


注解

1、Annotation 架构

image-20220926110853444

2、Annotation 组成部分

java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:

Annotation.java
package java.lang.annotation;
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}
ElementType.java
package java.lang.annotation;

public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}
RetentionPolicy.java
package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */

    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

3、自定义注解

当我们理解了内置注解, 元注解和获取注解的反射接口后,我们便可以开始自定义注解了。

创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头,我们可以为注解声明方法。

自定义注解格式:

// 元注解
public @interface 注解名称{
    // 属性列表
}

我们先来看看注解的例子:

3.1、创建自定义注解

/**
 * 自定义注解例子
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface HelloAnnotation {
    String value();
}

3.2、使用自定义注解

/**
 * 使用自定义注解
 */
public class HelloAnnotationClient {
    @HelloAnnotation(value="Simple custom Annotation example")
    public void sayHello(){
        System.out.println("Inside sayHello method..");
    }
}

3.3、测试自定义注解

/**
 * 自定义注解测试
 */
public class HelloAnnotationTest {
    public static void main(String[] args) throws Exception {
        HelloAnnotationClient helloAnnotationClient=new HelloAnnotationClient();
        Method method=helloAnnotationClient.getClass().getMethod("sayHello");
        if(method.isAnnotationPresent(HelloAnnotation.class)){
            HelloAnnotation helloAnnotation=method.getAnnotation(HelloAnnotation.class);
            //Get value of custom annotation
            System.out.println("Value : "+helloAnnotation.value());
            //Invoke sayHello method
            method.invoke(helloAnnotationClient); }
    }
}

4、注解是如何实现的

4.1、疑问的起点

以前在写代码的时候就看过一些注解的源码,每次点进去看的时候发现都只有简简单单几行代码,但是为什么就可以实现一些非常复杂的功能呢?哪个时候还不太在意,今天在写代码的时候,看到了@Slf4j的注解,突然想看看它的源码是什么,然后点进去一看

//  
// Source code recreated from a .class file by IntelliJ IDEA  
// (powered by FernFlower decompiler)  
//  
  
package lombok.extern.slf4j;  
  
import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
  
@Retention(RetentionPolicy.SOURCE)  
@Target({ElementType.TYPE})  
public @interface Slf4j {  
    String topic() default "";  
}

这就是所有的代码,还是一如既往的简单,再去看了其他的注解依然如此,那么究竟靠什么来实现对应的功能呢?

4.2、注解如何起作用

4.2.1、注解与接口

注解实际上就是一个标签,我们在一个某一类事物打标签的时候也是尽量越简单越好,所以注解就是这么回事,它仅仅是一个标签,其代码结构如下:

public @interface 注解名称{
    属性列表
;
}

仅仅是注解名字加上注解的一些属性,然后我们又注意到,它的定义方式是@interface
interface我们都知道,是一个接口,那么猜测注解的本质上类似接口,所以注解才会如此简单,因为接口也是如此仅仅定义方法而不去实现,想到这一层之后脑子里突然明朗起来。

4.2.2、注解与反射

为什么都说注解实际上就是使用反射实现的,我们已经看过了注解里面的东西,就这么一点,与反射有什么关系呢?但是大家都说注解就是用反射实现的自然有其的道理。
如果自己使用过一些反射代码,就会留意到有这么一个方法:

xxx.getAnnotation(annotation.class)

xxx代表着一个对象,annotation.class代表一个注解的class,我们可以通过这个方法来获取一个类上相应的注解。
还可以直接判断一个对象上是否使用了某某注解:

xxx.isAnnotationPresent(annotation.class)

至此我们应该已经想到了一个注解对应的功能是如何实现的了。

当我们注明一个注解的时候,只是制造出了一个标签
当我们给一个类加上注解的时候,就是给这个对象贴上了某个标签
然后实现一个方法:通过反射得到一个对象,然后判断对象上是否有此注解,如果有再进行相应的操作,通过反射我们可以得到这个对象的所有信息,所以想要进行什么操作也是可以的。
这就是注解从定义到起作用的一个过程,举个第三步的例子:
当我们给一个方法加上了一个注解


public class TestAnnotation {
    public static void main(String[] args) {

        XXX类 对象= new XXX();

        Class clazz = 对象.getClass();

        Method[] method = clazz.getDeclaredMethods();

        for ( Method m: method ) {
            if ( m.isAnnotationPresent( annotation.class )) {
               如果方法有此注解,我们就可以进行想要进行的操作
               ...........
        }
    }
}

当然要完成真正注解的作用还是很复杂的,主要用到反射,包注解扫描,aop相关的知识共同完成的

4.3、注解三板斧

定义、使用、读取

定义:包括名字,能用到哪些地方,有效期,是否可以被继承

使用:定义好之后在允许的地方使用标注即可

光有前两步,没什么用,如最熟悉的@Override注解,为什么能验证重写是否有效,怎么不是验证重载?spring的@Autowired为什么是注入作用,而不是输出一句话?显然,他们在程序中做了实现,使得其注解具有各自的作用,也具有了意义,而赋予灵魂的一步就是读取

读取:让注解发挥作用,给注解注入灵魂

4.3.1、举个例子

自己自定义个注解,并赋予它作用,模拟aop功能,在方法前后加入自定义逻辑

定义注解
//方法前执行
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface MyBefore {}
 //主要方法
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface MyCore {}
 //方法后执行
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface MyAfter {}
使用注解
public class Test {
     @MyBefore
     public void init() {
         System.out.println("初始化。。。");
     }
     @MyAfter
     public void destroy() {
         System.out.println("销毁。。。");
     }
     @MyCore
     public void core() {
         System.out.println("核心方法");
     }
 }
赋予注解作用

前两步简单,重要的是如何赋予注解作用

public class MyWork {
    public static void main(String[] args) throws Exception{
        //获得测试类
        Class clazz = Test.class;
        //获得一个实例
        Object obj = clazz.newInstance();
        //获得Test的所有public方法,将他们存入对应的集合中
        List<Method> myBeforeList = new ArrayList<>();
        List<Method> myAfterList = new ArrayList<>();
        List<Method> myTestList = new ArrayList<>();
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyBefore.class)) {
                myBeforeList.add(method);
                continue;
            }
            if (method.isAnnotationPresent(MyCore.class)) {
                myTestList.add(method);
                continue;
            }
            if (method.isAnnotationPresent(MyAfter.class)) {
                myAfterList.add(method);
                continue;
            }
        }
        //调用标有MyTest注解的方法
        for (Method test : myTestList) {
            //在测试方法前先执行标有MyBefore注解的方法
            for (Method method : myBeforeList) {
                method.invoke(obj);
            }
            //调用test方法
            test.invoke(obj);
            //在测试方法后执行标有MyAfter注解的方法
            for (Method method : myAfterList) {
                method.invoke(obj);
            }
        }
    }
}

输出

初始化。。。
核心方法
销毁。。。

这只是个举个例子,真实的应用场景绝对不会这么简单

我们尝试按此三板斧套路分析下@Autowired是如何工作的

4.3.2、源码分析

定义注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
   /**
    * Declares whether the annotated dependency is required.
    * <p>Defaults to {@code true}.
    */
   boolean required() default true;

}

源码看起来不难,规定了有效期在运行期间,并且可以标注在构造器、方法、参数、常量、注解上面,并且定义了一个属性required默认值为true

使用注解
@RestController
public class Test {
    @Autowired
    private User user;
    @RequestMapping("/say")
    String sayHello() {
        user.setName("法外狂徒张三");
        return user.getName();
    }
}

这里主要是为了体现spring给我们创建了一个对象,所有不会抛空指针,页面有值

image-20220926110406180

那么spring是如何做到的呢?正所谓注解两板斧不难,重要的是第三板斧

读取注解

@Autowired注解由AutowiredAnnotationBeanPostProcessor这个后置器类进行处理

先获取标注了@Autowired注解的属性或方法并存入一个集合中,通过反射的方式注入进去

注入的方法是该后置器的postProcessProperties方法的inject方法

image-20220926110435182

这两种方法同样在后置器类中对InjectionMetadata进行了重写

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Collection<InjectedElement> checkedElements = this.checkedElements;
   Collection<InjectedElement> elementsToIterate =
         (checkedElements != null ? checkedElements : this.injectedElements);
   if (!elementsToIterate.isEmpty()) {
      for (InjectedElement element : elementsToIterate) {
         //核心由于AutowiredAnnotationBeanPostProcessor重写了inject,所以会根据传入的元素类型来调用不同的方法,注入属性或者调用方法
         element.inject(target, beanName, pvs);
      }
   }
}

如果标注的是属性那么调用AutowiredFieldElement方法

image-20220926110455184

这里表明会给我的user属性附上一个user对象

如果标注的是方法那么调用AutowiredMethodElement方法

if (arguments != null) {
   try {
      //执行标注了@Autowired的方法
      ReflectionUtils.makeAccessible(method);
      method.invoke(bean, arguments);
   }
}

这些就是@Autowired注解的读取

源码拓展

如果深入了解下@Autowired可以看下去

整个过程的调用情况如下,其中postProcessPropertyValues已弃用,取而代之的是postProcessProperties

image-20220926110518431

整个过程的源码调用非常的多,建议自己动手debug,不然会蒙圈,源码光看是看不会的

疑问一:如何具备匹配Autowired的能力?

在创建出AutowiredAnnotationBeanPostProcessor后置类的时候进行了初始化,将Autowired这个注解类型放入了集合中,方便后面匹配使用,那么如何进行该后置器类的初始化?

public AutowiredAnnotationBeanPostProcessor() {
   this.autowiredAnnotationTypes.add(Autowired.class);
   this.autowiredAnnotationTypes.add(Value.class);
	//省略
}

疑问二:那么如何进行AutowiredAnnotationBeanPostProcessor后置器类的初始化?

在spring容器启动的时候会调用关键的refresh方法,其中有一步就是去注册所有的后置器类,自然也包括AutowiredAnnotationBeanPostProcessor这个类

image-20220926110539976

疑问三:AutowiredAnnotationBeanPostProcessor激活之后如何去获取到注解标注的属性或方法

如图第11步,该后置器类有一个findAutowiringMetadata方法,找到的话最后返回一个InjectionMetadata集合,后面利用这个集合中的元素来进行注入

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
	//省略
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                //核心
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

那么如何获取这个元素的?核心方法为buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    //省略之前
    //找到带有@Autowired注解的属性并封装为AutowiredFieldElement
      ReflectionUtils.doWithLocalFields(targetClass, field -> {
          //核心
         MergedAnnotation<?> ann = findAutowiredAnnotation(field);
         if (ann != null) {
            if (Modifier.isStatic(field.getModifiers())) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation is not supported on static fields: " + field);
               }
               return;
            }
            boolean required = determineRequiredStatus(ann);
            currElements.add(new AutowiredFieldElement(field, required));
         }
      });
    //找到带有@Autowired注解的方法并封装为AutowiredMethodElement
      ReflectionUtils.doWithLocalMethods(targetClass, method -> {
         Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
         if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
            return;
         }
          //核心
         MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
         if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
            if (Modifier.isStatic(method.getModifiers())) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation is not supported on static methods: " + method);
               }
               return;
            }
            if (method.getParameterCount() == 0) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation should only be used on methods with parameters: " +
                        method);
               }
            }
            boolean required = determineRequiredStatus(ann);
            PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
            currElements.add(new AutowiredMethodElement(method, required, pd));
         }
      });

      elements.addAll(0, currElements);
      targetClass = targetClass.getSuperclass();
   }
   while (targetClass != null && targetClass != Object.class);

   return InjectionMetadata.forElements(elements, clazz);
}

而这两个方法中都有一个findAutowiredAnnotation方法,作用是找到带有@Autowired和@Value注解的属性和方法

private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
   MergedAnnotations annotations = MergedAnnotations.from(ao);
   for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
      MergedAnnotation<?> annotation = annotations.get(type);
      if (annotation.isPresent()) {
         return annotation;
      }
   }
   return null;
}

最后将AutowiredFieldElementAutowiredMethodElement对象的封装成InjectionMetadata,完成对注解的收集,如果找不到就返回null

疑问四:在拥有收集好注解的集合后,在哪里去使用?

这个问题也可以改为如何进行依赖注入的?

这时候refresh的registerBeanPostProcessors方法也执行完了,然后来执行finishBeanFactoryInitialization

image-20220926110612203

中间一大串调用就不说了,可以自己debug去看,主要说下调用到后置器类的postProcessProperties方法

可以粗略理解为finishBeanFactoryInitialization — > xxxxxxx —-> postProcessProperties

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
   InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
   try {
      //核心
      metadata.inject(bean, beanName, pvs);
   }
   catch (BeanCreationException ex) {
      throw ex;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
   }
   return pvs;
}

在点进去,里面有个InjectedElement抽象类,而之前的这两个封装对象也就用到了

image-20220926110628390

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Collection<InjectedElement> checkedElements = this.checkedElements;
   Collection<InjectedElement> elementsToIterate =
         (checkedElements != null ? checkedElements : this.injectedElements);
   if (!elementsToIterate.isEmpty()) {
      for (InjectedElement element : elementsToIterate) {
          //根据不同的元素类型去调用不同的注入方法
         element.inject(target, beanName, pvs);
      }
   }
}

接下来就对应上开头的那段代码了

疑问五:开头的那段代码中的bean时怎么获取的?

因为开头只展示了属性注入了对象,没说这个对象是怎么获取的,获取的方法为resolveFieldValue,这个方法是在属性注入前调用的

image-20220926110653832

别看方法一大堆,真正有用的就是红框中的那个,其他是缓存的处理

点进去之后一般执行else方法

image-20220926110712713

这个方法也是一大堆,主要讲了matchingBeans集合的获取与调用,这个集合存的是依赖对象的所有实现类

@Nullable
    public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
        //前面省略
    Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
        //后面省略
}

这里利用获取的map集合进行bean的实例化返回,具体判断规则感兴趣的可以看源码

总结

任何的注解都是三板斧,难点在于如何读取注解并赋予它作用,推荐看springboot的一些注解,它开发利用注解替代了很多的配置文件,可以看看是怎么做到的,看源码实现可以学到很多东西,不过需要一定的内功,比如良好的基础,尤其是对反射这块需要很清楚,还有设计模式也是非常重要的,源码中也有很多地方得到了体现


文章作者: Liu Yuan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Liu Yuan !
—— 评论区 ——
  目录