注解的定义

注解是那些插入到源代码中,使用其他工具可以对其进行处理的标签

注解不会改变程序的编译方式:Java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。

在Java中,注解是被当做一个修饰符来使用的(修饰符:如public、private)

注解的常用用法:1. 附属文件的自动生成,例如bean信息类。 2. 测试、日志、事务等代码的自动生成。

单元测试例子:

import org.junit.Test;

public class SomeTest {

    @Test
public void test(){
// TODO
} }

以上是我们常见的代码。以前不了解的时候,都自然而然的认为是@Test让我们的代码拥有单元测试的能力,实际上:@Test注解自身并不会做任何事情,它需要工具支持才有用。例如,当测试一个类的时候,JUnit4测试工具会去调用所有标识为@Test的方法。

这也就解释了当我们要引入Test的Class时提示:

所以我们可以认为:注解=注解定义+工具支持

进入@Test,查看定义

package org.junit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Test {
Class<? extends Throwable> expected() default Test.None.class; long timeout() default 0L; public static class None extends Throwable {
private static final long serialVersionUID = 1L; private None() {
}
}
}

可以看到@Test注解被两个注解@Retention和@Target给注解了,这两个注解叫做元注解(一共四个:@Retention、@Target、@Document、@Inherited)

1. @Retention(指明这个注解可以保留多久,一般都为RUNTIME

RetentionPolicy.SOURCE            保留在源文件里
RetentionPolicy.CLASS 保留在class文件里,但是虚拟机不需要将它们载入
RetentionPolicy.RUNTIME 保留在class文件里,并由虚拟机将它们载入,通过反射可以获取到它们。

2. @Target(指明这个注解的使用范围)

ElementType.TYPE                 用于类和接口
ElementType.FIELD 用于成员域
ElementType.METHOD 用于方法
ElementType.PARAMETER 用于方法或者构造器里的参数
ElementType.CONSTRUCTOR 用于构造器
ElementType.LOCAL_VARIABLE 用于局部变量
ElementType.ANNOTATION_TYPE 用于注解类型声明
ElementType.PACKAGE 用于包
ElementType.TYPE_PARAMETER 类型参数,1.8新增
ElementType.TYPE_USE 类型用法,1.8新增

3. @Document为例如Javadoc这样的归档工具提供了一些提示。

4. @Inherited只能应用于对类的注解。指明当这个注解应用于一个类A的时候,能够自动被类A的子类继承。

注解可以在运行时处理

也可以在源码级别上处理

也可以在字节码级别上进行处理

注解的使用

新建一个实体类Book

public class Book {

    private String name;

    public Book() {
} public Book(String name) {
this.name = name;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
'}';
}
}

建立一个在类上用的注解

package com.demo.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TypeAnnotation { // 如果注解里只有一个属性,就可以以这种固定的写法。使用的时候可以省略名称如:@TypeAnnotation("")
String value() default "";
}

建立一个在方法上用的注解

package com.demo.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodAnnotation { String value() default "";
}

假装建立一个“配置类”

package com.demo.annotation;

import com.demo.tools.Book;

import java.util.LinkedHashMap;

@TypeAnnotation
public class BookConfig { private LinkedHashMap<String,Object> beans; public <T> T getBean(String name, Class<T> clazz){
Object o = beans.get(name);
return (T) o;
} @MethodAnnotation
public Book book(){
return new Book("QQQ");
} @MethodAnnotation("zzz")
public Book book2(){
return new Book("ZZZ");
} }

假装这是spring容器的初始化过程

package com.demo.annotation;

import com.demo.tools.Book;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap; public class Main {
public static void main(String[] args) throws Exception {
BookConfig config = parseAnnotation(BookConfig.class);
Book book = config.getBean("book", Book.class);
System.out.println(book);
book = config.getBean("zzz", Book.class);
System.out.println(book);
book = config.getBean("book2", Book.class);
System.out.println(book);
} public static <T> T parseAnnotation(Class<T> clazz) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
if (!clazz.isAnnotationPresent(TypeAnnotation.class)){
return null;
}
T instance = clazz.newInstance();
LinkedHashMap<String, Object> hashMap = new LinkedHashMap<>();
Field beans = clazz.getDeclaredField("beans"); Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods){
if (m.isAnnotationPresent(MethodAnnotation.class)){
Object o = m.invoke(instance);
MethodAnnotation t = m.getAnnotation(MethodAnnotation.class);
String name;
// 注解有值用注解值作为name,否则用方法名字作为name
if (t.value() != null && !t.value().equals("")){
name = t.value();
}else{
name = m.getName();
}
hashMap.put(name, o);
}
}
beans.setAccessible(true);
beans.set(instance, hashMap);
return instance;
}
}

输出:

思路就是:用反射创建新类,利用【java.lang.reflect.Method#getAnnotation】方法获取注解类,然后获取注解里的值,然后进行操作即可。

Spring中的注解

引入Spring-Context的依赖

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>

1. 在resource路径下新建一个beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="book" class="com.demo.tools.Book">
<property name="name" value="AAA"></property>
</bean>
</beans>

这个是原始的写法,先测试这个也是为了对比

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Book book = applicationContext.getBean(Book.class);
System.out.println(book);
}
}

运行输出

2. 建立一个配置类(配置类的作用等同于xml配置文件)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class BookConfig {
@Bean
public Book book(){
return new Book("BBB");
} @Bean
public User user(){
return new User("KKK");
}
}

运行

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BookConfig.class);
Book book = context.getBean(Book.class);
System.out.println(book);
}
}

输出

这里不打算探究spring有多少种注解以及使用方法,只探究注解是怎样运行以取代xml配置的

先来看下容器的创建过程,这其中就包括了注解部分

进入AnnotationConfigApplicationContext构造方法

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}

A:this()

public AnnotationConfigApplicationContext() {
// 这是一个替代ClassPathBeanDefinitionScanner的注释解决方案,但只针对显式注册的类。
this.reader = new AnnotatedBeanDefinitionReader(this);
// 一个bean定义扫描器,它检测类路径上的bean候选,将相应的bean定义注册到给定的注册表(BeanFactory或ApplicationContext)
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

B:register(componentClasses);

public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
this.reader.register(componentClasses);
} public void register(Class<?>... componentClasses) {
for (Class<?> componentClass : componentClasses) {
registerBean(componentClass);
}
} public void registerBean(Class<?> beanClass) {
doRegisterBean(beanClass, null, null, null, null);
} // 根据给定的class注册bean,从类声明的注解派生其元数据
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) { // new一个AnnotatedGenericBeanDefinition,其中包含bean的class和元数据
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
} abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
// 包含名称和别名的bean定义的Holder
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 注册bean定义
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

C:refresh();

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 刷新前的准备
prepareRefresh(); // 获取bean工厂【ConfigurableListableBeanFactory】
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 对bean工厂的预设置,比如配置类加载器和后置处理器等等。(后置处理器能在bean初始化前后做一些工作)
prepareBeanFactory(beanFactory); try {
// 由子类实现的bean工厂的后置处理
postProcessBeanFactory(beanFactory); // 执行工厂的后置处理器
invokeBeanFactoryPostProcessors(beanFactory); // 注册bean的后置处理器,用来拦截bean的创建过程
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
initMessageSource(); // 初始化事件派发器
initApplicationEventMulticaster(); // 由子类实现,当容器刷新的时候,可以做一些额外的事情
onRefresh(); // 检查并注册容器中的监听器
registerListeners(); // 初始化剩下的所有单实例bean
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
} // Destroy already created singletons to avoid dangling resources.
destroyBeans(); // Reset 'active' flag.
cancelRefresh(ex); // Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

DEBUG开始

打断点

当获取Bean工厂之后,出现了bookConfig的bean定义(当前出现的6个bean定义均是在B:register阶段完成的),但是还没有book的bean定义,继续往下走

进入工厂后置处理器【org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}

进入第一行方法【org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)】

我发现过了invokeBeanDefinitionRegistryPostProcessors方法,bean工厂的beanDefinitionMap的内容发生了变化,那么进入这个方法【org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors

private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}

进入【org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId); processConfigBeanDefinitions(registry);
}

进入最后一行代码【org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions

此时可以看的已有的BeanDefinitionNames,下面进入循环

只有当循环到bookConfig时,才进入else if 并添加。继续走,通过下面的注释我们可以知道上面的逻辑是来寻找@Configuration标记的类,如果没有即返回。

然后开始解析所有配置类

进入parser.parse(candidates);方法【org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)】,有个

进入parse方法后最终进入【org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass

进入doProcessConfigurationClass方法【org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass

这里就是我们要找的读取解析注解的方法了。可以看到里面写了对各种注解的处理方式(比如:@ComponentScan、@Import等等),包括对配置类里面定义的bean的处理。

针对本案例,可以看到专门检索配置类里面被@Bean标记的Method的方法【org.springframework.context.annotation.ConfigurationClassParser#retrieveBeanMethodMetadata

private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
AnnotationMetadata original = sourceClass.getMetadata();
Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
// 标记@Bean的方法超过1个,有下面说的顺序问题,所以要用asm;否则直接返回
if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
// 尝试通过ASM读取.class文件以确定声明顺序
// 不幸的是,JVM的标准反射以任意顺序返回方法,甚至在相同JVM上运行相同应用程序的不同实例之间也是如此。
try {
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
break;
}
}
}
if (selectedMethods.size() == beanMethods.size()) {
// All reflection-detected methods found in ASM method set -> proceed
beanMethods = selectedMethods;
}
}
}
catch (IOException ex) {
logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);
// No worries, let's continue with the reflection metadata we started with...
}
}
return beanMethods;
}

获取到之后即添加到ConfigurationClass的beanMethods集合

看到这里,可以得知注解的作用:标记作用

以本例来看:无非就是标记了一个@Bean注解,之后扫描工具能够解析并得到MethodMetadata对象,再到后面由Spring进行统一实例化。

Java注解及其原理以及分析spring注解解析源码的更多相关文章

  1. 分析spring aop的源码实现

    AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较. spring AOP是Sp ...

  2. 最简 Spring IOC 容器源码分析

    前言 BeanDefinition BeanFactory 简介 Web 容器启动过程 bean 的加载 FactoryBean 循环依赖 bean 生命周期 公众号 前言 许多文章都是分析的 xml ...

  3. 【Spring注解驱动开发】聊聊Spring注解驱动开发那些事儿!

    写在前面 今天,面了一个工作5年的小伙伴,面试结果不理想啊!也不是我说,工作5年了,问多线程的知识:就只知道继承Thread类和实现Runnable接口!问Java集合,竟然说HashMap是线程安全 ...

  4. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

  5. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  6. Spring IOC 容器源码分析

    声明!非原创,本文出处 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 S ...

  7. Spring IOC 容器源码分析(转)

    原文地址 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢 ...

  8. Spring IOC容器源码分析

    注:本文转自https://javadoop.com/post/spring-ioc Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容 ...

  9. Spring Developer Tools 源码分析:二、类路径监控

    在 Spring Developer Tools 源码分析一中介绍了 devtools 提供的文件监控实现,在第二部分中,我们将会使用第一部分提供的目录监控功能,实现对开发环境中 classpath ...

随机推荐

  1. HTML 从入门到精通 [目录]

    目录 一.服务器的认识 二.浏览器的认识 三.Web 标准 四.HTML 的认识 五.HTML 文本标签 六.HTML 图像标签 七.HTML 路径 八.HTML 链接标签 九.HTML 列表 十.H ...

  2. Vue递归组件实现层层嵌套显示数据

    问题来自朋友...记录一下 需求是表格头部后端返回的数据中是不确定的 n维数据,表头存在于 listVo 字段中,如何实现层层显示呢? 温馨提示,以下内容为5张大图,请打开 WIFI 享用... 以下 ...

  3. InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's

    InvalidOperationException: Operations that change non-concurrent collections must have exclusive acc ...

  4. 【Java_基础】HashMap的工作原理

    转载至博客:HashMap的工作原理

  5. 【目录】Jmeter系列

    为天地立心,为生民立命.为往圣继绝学,为万世开太平. ---<横渠四句>·张载 Jmeter(一)简介以及环境搭建 Jmeter(二)Jmeter目录介绍 Jmeter(三)Test-Pl ...

  6. The 2019 Asia Nanchang First Round Online Programming Contest B. Fire-Fighting Hero

    题目链接:https://nanti.jisuanke.com/t/41349 题意:有一个灭火英雄,和一个灭火团队,一个人与一个团队比较. 灭火英雄到其他灭火点的最短路最大值,与一个团队到其他灭火点 ...

  7. Nginx下HTML页面POST请求静态JSON数据返回405状态

    在浏览器访问HTML页面,发现一些静态JSON数据没有显示,F12查看,如下图所示: 可以看到请求方式为POST 将请求链接复制在浏览器地址栏访问,可以正常请求到数据 F12查看,可以看到请求方式为G ...

  8. 数据结构 - 二叉搜索树封装 C++

    二叉搜索树封装代码 #pragma once #include <iostream> using namespace std; template<class T>class T ...

  9. 第07节-开源蓝牙协议BTStack框架代码阅读(上)

    首先来看一下,对于硬件操作,它是如何来进行处理的.在上篇文章中曾说过,在main函数里面它会调用硬件相关的代码,调用操作系统相关的代码.在BTStack中,可以搜索一下main.c,将会发现有很多ma ...

  10. 19-C#笔记-多态性

    # 静态多态性 --- ## 1 函数重载 和C++一样. --- ## 2 运算符重载 public static operator public static Box operator+ (Box ...