註解的定義
註解是那些插入到源代碼中,使用其他工具可以對其進行處理的標籤。
註解不會改變程序的編譯方式: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"); } }
運行
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定義,繼續往下走
當執行bean工廠的後置處理器之後,纔出現了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】
// 未完待續(欠缺具體處理註解過程)