《spring设计思想》30-Spring的注解

开发过程中面对很多的spring-framework的注解(@Repository,@Service等)SpringMVC的注解(@Controller等),还有spring-boot的注解(@SpringbootConfiguration等),Spring-cloud的注解。

其实这些注解都是基于Java语言的特性做的扩展,java语言自身在1.5版本之后开始支持注解,那么注解到底是个什么东西,有什么作用呢。

其实注解相当于java的字节码注释,java自身的javaDoc注释是不会被存储在class文件中的,所以当我们在运行时想要获取开发时,或者类定义时的一些注释信息的时候,是没有办法的。所以java引入Annotation,让我们的注释能够自由的存储在字节码中,保留到运行时,通过反射自动获取。

比如我们自定义一个注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface AnnotationDemo {

    String des() default "hello world";

}

当前AnnoatationDemo注解的内容保存到运行时,有个属性des

@AnnotationDemo(des = "你好")
@Component
public class AnnotationPojo {

    public static void main(String[] args) {
        System.out.println("哈哈哈");
    }
}

AnnotationPojo被@AnnotationDemo注释,属性des为“你好”,我们可以看下编译之后的class文件:

  Last modified 2020-8-8; size 773 bytes
  MD5 checksum fbf95327323cab1749ba50af395f1ee9
  Compiled from "AnnotationPojo.java"
public class com.david.study.spring.annotation.AnnotationPojo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#25         // java/lang/Object."":()V
   #2 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #28            // 哈哈哈
   #4 = Methodref          #29.#30        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #31            // com/david/study/spring/annotation/AnnotationPojo
   #6 = Class              #32            // java/lang/Object
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/david/study/spring/annotation/AnnotationPojo;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               AnnotationPojo.java
  #20 = Utf8               RuntimeVisibleAnnotations
  #21 = Utf8               Lcom/david/study/spring/annotation/AnnotationDemo;
  #22 = Utf8               des
  #23 = Utf8               你好
  #24 = Utf8               Lorg/springframework/stereotype/Component;
  #25 = NameAndType        #7:#8          // "":()V
  #26 = Class              #33            // java/lang/System
  #27 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #28 = Utf8               哈哈哈
  #29 = Class              #36            // java/io/PrintStream
  #30 = NameAndType        #37:#38        // println:(Ljava/lang/String;)V
  #31 = Utf8               com/david/study/spring/annotation/AnnotationPojo
  #32 = Utf8               java/lang/Object
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (Ljava/lang/String;)V
{
  public com.david.study.spring.annotation.AnnotationPojo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/david/study/spring/annotation/AnnotationPojo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String 哈哈哈
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "AnnotationPojo.java"
RuntimeVisibleAnnotations:
  0: #21(#22=s#23)
  1: #24()

可以看到最后的“RuntimeVisibleAnnotations:”部分,表示#21 到#24的常量池可以被虚拟机观察到:

正好标注的是我们的注解@AnnotaionDemo和属性des。这就说明我们的注解的主要作用就是字节码注释,或者另一类说法:注释性代码。

通过反射可以获取注解信息,所以我们可以在编译时,运行时对特定注解标注的类/方法做特殊的处理。比如一个普通的pojo,如果被标注了@Transactional,我们可以通过字节码提升做动态代理,其他的不必做字节码提升,节省资源,提高性能

那spring-framework基于java的注解能力,做了一些什么事情呢。

spring基于java的注解,自己拓展了spring专属的一些注解,包括spring的范式注解,组合注解。

范式注解:

@Component/@Repository/@Service/@Controller等,这四个注解在统一的stererotype下,表示这四个注解是范式注解,

范式注解可以被扩展,类似java中类的继承一样,范式注解可以注释在注解上

比较有意思的地方

 * 

As of Spring 2.5, this annotation also serves as a specialization of * {@link Component @Component}, allowing for implementation classes to be autodetected * through classpath scanning. * * @author Rod Johnson * @author Juergen Hoeller * @since 2.0 * @see Component * @see Service * @see org.springframework.dao.DataAccessException * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { /** * The value may indicate a suggestion for a logical component name, * to be turned into a Spring bean in case of an autodetected component. * @return the suggested component name, if any (or empty String otherwise) */ @AliasFor(annotation = Component.class) String value() default ""; }

@Repository注解在2.0版本就有了,但是@Component在2.5版本才存在,所以@Repository后来被统一成了一种特殊的Component。类似的还有@Service。

spring会识别被@Component注释的类,当作Spring的bean加载到上下文中。

可以搭配xml的一起使用,也可以搭配@ComponentScan注解一起使用。

那么,@ComponentScan在 spring中是怎么使用的呢。

看简单的例子:

@ComponentScan(basePackages = "com.david.study.spring.annotation")
public class ComponentScanDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(ComponentScanDemo.class);
        configApplicationContext.getBean(AnnotationPojo.class);
        configApplicationContext.close();
    }
}

当前启动类,除了@ComponentScan注解没有注入其他类,但是我们能够获取AnnotationPojo的bean,因为这个bean被标注了@Component注解,且在com.david.study.spring.annotation目录下。

但是spring中到底怎么实现扫描的呢?

这个其实要借助spring自身的编码实现。

其实在AbstractApplicationContext中的refresh方法中可以一窥端倪,

《spring设计思想》30-Spring的注解_第1张图片

在InvokeBeanFactoryPostPrcoessors中,有一个特殊的BeanFactoryPostProcessor实现,调用堆栈里面可以看到

《spring设计思想》30-Spring的注解_第2张图片

这个特殊的类叫ConfigurationClassPostProcessor类,继承了BeanDefinitionRegistryPostProcessor,又继承了BeanFactoryPostProcessor,

在具体的实现中,ConfigurationClassPostProcessor会委托ConfigurationClassParser解析当前的容器中的BeanDefinition

获取BeanDefinition的class文件

通过反射查看是否被@ComponentScan注解,如果被注解

《spring设计思想》30-Spring的注解_第3张图片

会进入当前方法,进行处理。

代码同样委托给ComponentScanAnnotaionParser解析器,解析注解

代码继续跟踪

《spring设计思想》30-Spring的注解_第4张图片根据反射原理获取@ComponentScan内的basePackages属性的值,解析成路径

然后委托给ClassPathBeanDefinitionScanner#doScan进行路径扫描

继续跟进:

《spring设计思想》30-Spring的注解_第5张图片

看到要扫描的路径就是我们注解的“com.david.study.spring.annotation”包

底层的话依托之前讲过的PathMatcherResourcePatternerResovler,把路径解析成资源Resource[]

《spring设计思想》30-Spring的注解_第6张图片

Resource其实就是Class文件资源。然后可以转换成BeanDefinition加载到上下文,然后再次遍历加载进入的BeanDefinition是否有@ComponentScan等注解标注的类,直到没有新的BeanDefinition被加载。

上面简单的讲了一下@ComponentScan注解的作用,下节讲一下组合注解的使用。

 

你可能感兴趣的:(spring)