注解是干什么的

注解本身不提供作用,注解只能是被看作元数据,它不包含任何业务逻辑。注解更像是一个标签,一个声明,表面被注释的这个地方,将具有某种特定的逻辑。

注解常见的种类

常见的注解有三大类:JDK的,自定义的,第三方的(比如框架)

注解三板斧

定义、使用、读取

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

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

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

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

注解前置知识

首先是元注解,Java中提供了四个

@Documented | @Retention | @Target | @Inherited

分别解释下

@Documented

代表着此注解会被javadoc工具提取成文档

@Retention:

代表该注解的有效期

SOURCE 表示编译期,如@Override,只做编译时的提示,不会写入字节码中。

CLASS表示类加载期,会保存在class文件中,但在运行class文件被丢弃,也是默认值

RUNTIME 表示运行期,也是最常用的,可以在代码运行时进行反射执行相关的操作

@Target:

表示这个注解可以放在哪

TYPE:接口、类、枚举、注解

FIELD:字段、枚举的常量

METHOD:方法

PARAMETER:参数

CONSTRUCTOR:构造函数

LOCAL_VARIABLE:局部变量

ANNOTATION_TYPE:注解

PACKAGE:包

@Inherited:

表示子类可以继承该类的注解

举个例子

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

定义注解
1
2
3
4
5
6
7
8
9
10
11
12
//方法前执行
@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 {}
使用注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
@MyBefore
public void init() {
System.out.println("初始化。。。");
}
@MyAfter
public void destroy() {
System.out.println("销毁。。。");
}
@MyCore
public void core() {
System.out.println("核心方法");
}
}

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

读取注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
}
}
}
}
输出
1
2
3
初始化。。。
核心方法
销毁。。。

这只是个举个例子,真实的应用场景绝对不会这么简单,比如我们尝试按此套路分析下@Autowired是如何工作的

源码分析

定义注解
1
2
3
4
5
6
7
8
9
10
11
@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

使用注解
1
2
3
4
5
6
7
8
9
10
@RestController
public class Test {
@Autowired
private User user;
@RequestMapping("/say")
String sayHello() {
user.setName("法外狂徒张三");
return user.getName();
}
}

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

image-20210810205814551

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

读取注解

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

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

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

image-20210811224127127

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

1
2
3
4
5
6
7
8
9
10
11
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-20210812135029193

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

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

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

源码拓展

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

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

img

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

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

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

1
2
3
4
5
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
//省略
}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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注解的属性和方法

1
2
3
4
5
6
7
8
9
10
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-20210812152011029

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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-20210812152622107

1
2
3
4
5
6
7
8
9
10
11
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-20210812153128145

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

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

image-20210812153304732

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

1
2
3
4
5
6
7
@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的一些注解,它开发利用注解替代了很多的配置文件,可以看看是怎么做到的,看源码实现可以学到很多东西,不过需要一定的内功,比如良好的基础,尤其是对反射这块需要很清楚,还有设计模式也是非常重要的,源码中也有很多地方得到了体现