Java注解及其原理以及分析spring注解解析源码
Posted 小LUA
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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注解解析源码的主要内容,如果未能解决你的问题,请参考以下文章