Java注解及其原理以及分析spring注解解析源码
注解的定义
注解是那些插入到源代码中,使用其他工具可以对其进行处理的标签。
注解不会改变程序的编译方式: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注解解析源码的更多相关文章
- 分析spring aop的源码实现
AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较. spring AOP是Sp ...
- 最简 Spring IOC 容器源码分析
前言 BeanDefinition BeanFactory 简介 Web 容器启动过程 bean 的加载 FactoryBean 循环依赖 bean 生命周期 公众号 前言 许多文章都是分析的 xml ...
- 【Spring注解驱动开发】聊聊Spring注解驱动开发那些事儿!
写在前面 今天,面了一个工作5年的小伙伴,面试结果不理想啊!也不是我说,工作5年了,问多线程的知识:就只知道继承Thread类和实现Runnable接口!问Java集合,竟然说HashMap是线程安全 ...
- Spring IOC 容器源码分析 - 创建原始 bean 对象
1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...
- Spring IOC 容器源码分析 - 获取单例 bean
1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...
- Spring IOC 容器源码分析
声明!非原创,本文出处 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 S ...
- Spring IOC 容器源码分析(转)
原文地址 Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢 ...
- Spring IOC容器源码分析
注:本文转自https://javadoop.com/post/spring-ioc Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容 ...
- Spring Developer Tools 源码分析:二、类路径监控
在 Spring Developer Tools 源码分析一中介绍了 devtools 提供的文件监控实现,在第二部分中,我们将会使用第一部分提供的目录监控功能,实现对开发环境中 classpath ...
随机推荐
- python--进程初识详解
进程:通俗理解一个运行的程序或者软件,进程是操作系统资源分配的基本单位 1.1.导入进程模块 import multiprocessing 1.2.Process进程类的语法结构如下: Process ...
- mysql--日志文件
1 选择常规查询日志和慢查询日志输出目标 1.1 log_output查看.定义 所谓的输出目标就是日志写入到哪里,mysql中用系统变量 log_output来指定输出目标,log_output的 ...
- 『计算机视觉』imgaug图像增强库中部分API简介
https://github.com/aleju/imgaug 介绍一下官方demo中用到的几个变换,工程README.md已经给出了API简介,个人觉得不好理解,特此单独记录一下: import n ...
- 记一次wsl上的pip3安装失败问题 The following packages were automatically installed and are no longer required:
转载请注明来源.https://www.cnblogs.com/sogeisetsu/.然后我的CSDNhttps://blog.csdn.net/suyues/article/details/103 ...
- python测试开发django-71.自定义标签tag
前言 django的模板里面有很多标签可以快速实现一些功能,比如{% url url_name%} 可以快捷的导入一个本地url地址. 上一篇我们可以自定义一些过滤器https://www.cnblo ...
- django 权限设置 左侧菜单点击显示,面包屑
1.左侧菜单点击显示 就是在点击的时候保留点击的功能 方法. 1.加入新的字段,pid来判断 class Permission(models.Model): """ 权限 ...
- matlab-fsolve函数求解多元非线性方程
记录一下代码,方便下次套用模板 options=optimset('MaxFunEvals',1e4,'MaxIter',1e4); [x,fval,exitflag] = fsolve(@(x) m ...
- PHP 多个字段自增或者自减
//自增$res=Db::name('accessories') ->where('id',$req['id']) ->inc('number',$req['number']) -> ...
- 【MyEclipse】安装svn插件
svn插件包下载:http://subclipse.tigris.org/servlets/ProjectDocumentList?folderID=2240 重启myeclipse 看import就 ...
- freeradius编译安装+mysql配置
参考文档: freeradius 官网的wikihttp://wiki.freeradius.org/Home ubuntu freeradius搭建教程 http://yustanto.com/fr ...