开发过程中面对很多的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在 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方法中可以一窥端倪,
在InvokeBeanFactoryPostPrcoessors中,有一个特殊的BeanFactoryPostProcessor实现,调用堆栈里面可以看到
这个特殊的类叫ConfigurationClassPostProcessor类,继承了BeanDefinitionRegistryPostProcessor,又继承了BeanFactoryPostProcessor,
在具体的实现中,ConfigurationClassPostProcessor会委托ConfigurationClassParser解析当前的容器中的BeanDefinition
获取BeanDefinition的class文件
通过反射查看是否被@ComponentScan注解,如果被注解
会进入当前方法,进行处理。
代码同样委托给ComponentScanAnnotaionParser解析器,解析注解
代码继续跟踪
根据反射原理获取@ComponentScan内的basePackages属性的值,解析成路径
然后委托给ClassPathBeanDefinitionScanner#doScan进行路径扫描
继续跟进:
看到要扫描的路径就是我们注解的“com.david.study.spring.annotation”包
底层的话依托之前讲过的PathMatcherResourcePatternerResovler,把路径解析成资源Resource[]
Resource其实就是Class文件资源。然后可以转换成BeanDefinition加载到上下文,然后再次遍历加载进入的BeanDefinition是否有@ComponentScan等注解标注的类,直到没有新的BeanDefinition被加载。
上面简单的讲了一下@ComponentScan注解的作用,下节讲一下组合注解的使用。