程式碼地址:https://github.com/showkawa/spring-annotation/tree/master/src/main/java/com/brian
1.原始碼分析二主要分析的內容
1.使用@Condition多條件註冊bean物件
2.@Import註解快速注入第三方bean物件
3.@EnableXXXX 開啟原理
4.基於ImportBeanDefinitionRegistrar註冊bean
5.基於FactoryBean註冊bean物件
1.使用@Conditional多條件註冊bean物件
conditional字面意思條件句,亦即滿足某些條件將該類註冊到IOC容器的意思
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition Conditions} that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); }
可以看到 Conditional註解的value是Condition的子類或實現類
我這裡寫自定義的BriancCondition類來實現Condition介面,可以看到Condition就一個返回boolean值的mathes()方法
public class BrianCondition implements Condition { /* * context:判斷條件能使用的上下文(環境) * metadata: 註釋資訊 * */ public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { System.out.println("---male:" + context.getRegistry().containsBeanDefinition("person"));
//我這裡判斷ioc容器中是否有person例項,有返回true,否則返回false if(context.getRegistry().containsBeanDefinition("person")) return true; return false; } }
現在在配置類中加上,註冊Person類到容器中,然後用@Conditional控制是否將person01和person02註冊到容器中
@Configuration //告訴spring這是一個配置類 /* * @ComponentScan * value:只當於掃描的的包 * excludeFilters = 指定掃描的時候按照什麼規則排除哪些元件 * includeFilters = 指定掃描的時候只需要包含哪些元件 * Filter.ANNOTATION:按照註解 * Filter.ASSIGNABLE_TYPE: 按照給定的型別 * */ @ComponentScans(value = { @ComponentScan(value = "com.brian",includeFilters = { // @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}), // @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}), @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {BrianTypeFilter.class}) },useDefaultFilters = false) }) //@Import({Brian.class,Alan.class,BrianSelector.class}) public class MainConfig { @Bean("person") //給容器中註冊一個Bean;型別為返回值的型別;id預設是方法名作為id public Person person(){ return new Person("Alan",18); } /* * @Conditional() 按照條件註冊 * * */ @Conditional({BrianCondition.class}) @Bean("person01") public Person person01() { return new Person("Brian",17); } @Conditional({BrianCondition.class}) @Bean("person02") public Person person02() { return new Person("wenTao",19); } /* * *給容器中註冊元件 * 1,包掃描+ 元件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的方法] * 2, @Bean [匯入的第三方包裡面的元件] * 3,@Import [快速的給容器匯入一個元件] * 1.@Import(要匯入的元件class) * 2.ImportSelector:返回需要匯入的元件的全類名陣列 * 3.ImportBeanDefinitionRegistrar: 手動註冊bean到容器 * 4. 使用Spring提供的FactoryBean * */ @Bean public BrianBeanFactory brianBeanFactory() { return new BrianBeanFactory(); } }
加上測試類
public class MainTest { public static void main(String[] args) { ApplicationContext acac = new AnnotationConfigApplicationContext(MainConfig.class); /* ApplicationContext acac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);*/ System.out.println("ioc容器建立成功"); // Alan alan1 = acac.getBean(Alan.class); // Alan alan2 = acac.getBean(Alan.class); //System.out.println("比較兩個Alan例項: " + (alan1 == alan2)); Person person1 = (Person) acac.getBean("person01"); System.out.println("---main---test---person1---: " + person1.toString()); Person person2 = (Person) acac.getBean("person02"); System.out.println("---main---test---person2---: " + person2.toString()); //關閉ioc容器 ((AnnotationConfigApplicationContext) acac).close(); } }
你會發現控制檯可以獲取到物件person01和person02的資訊
我這邊再做一個matches發返回false的測試,亦即修改BrianCondition類的matches返回值為false,可以下面的測試結果:NoSuchBeanDefinitionException: No bean named 'person01' available。所i以根據上面我們測試的介面可以知道@Conditional註解的使用也是簡單的
2.@Import註解快速注入第三方bean物件
通過@Import可以快速的匯入依賴的bean物件,比如我們在配置類上匯入其他類@Import({Brian.class,Alan.class})
configure配置類
@Configuration //告訴spring這是一個配置類 /* * @ComponentScan * value:只當於掃描的的包 * excludeFilters = 指定掃描的時候按照什麼規則排除哪些元件 * includeFilters = 指定掃描的時候只需要包含哪些元件 * Filter.ANNOTATION:按照註解 * Filter.ASSIGNABLE_TYPE: 按照給定的型別 * */ @ComponentScans(value = { @ComponentScan(value = "com.brian",includeFilters = { // @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}), // @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}), @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {BrianTypeFilter.class}) },useDefaultFilters = false) }) @Import({Brian.class,Alan.class}) //@Import({BrianSelector.class}) public class MainConfig { @Bean("person") //給容器中註冊一個Bean;型別為返回值的型別;id預設是方法名作為id public Person person(){ return new Person("Alan",18); } /* * @Conditional() 按照條件註冊 * * */ @Conditional({BrianCondition.class}) @Bean("person01") public Person person01() { return new Person("Brian",17); } @Conditional({BrianCondition.class}) @Bean("person02") public Person person02() { return new Person("wenTao",19); } /* * *給容器中註冊元件 * 1,包掃描+ 元件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的方法] * 2, @Bean [匯入的第三方包裡面的元件] * 3,@Import [快速的給容器匯入一個元件] * 1.@Import(要匯入的元件class) * 2.ImportSelector:返回需要匯入的元件的全類名陣列 * 3.ImportBeanDefinitionRegistrar: 手動註冊bean到容器 * 4. 使用Spring提供的FactoryBean * */ @Bean public BrianBeanFactory brianBeanFactory() { return new BrianBeanFactory(); } }
測試類
public class MainTest {
public static void main(String[] args) {
ApplicationContext acac =
new AnnotationConfigApplicationContext(MainConfig.class);
/* ApplicationContext acac =
new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);*/
System.out.println("ioc容器建立成功");
Alan alan1 = acac.getBean(Alan.class);
System.out.println("--ALAN--:" + alan1);
// Alan alan2 = acac.getBean(Alan.class);
//System.out.println("比較兩個Alan例項: " + (alan1 == alan2));
// Person person1 = (Person) acac.getBean("person01");
// System.out.println("---main---test---person1---: " + person1.toString());
// Person person2 = (Person) acac.getBean("person02");
// System.out.println("---main---test---person2---: " + person2.toString());
// MathCalculator mathCalculator = (MathCalculator) acac.getBean("mathCalculator");
// System.out.println("----get--mathCalculator---: " + mathCalculator);
//關閉ioc容器
((AnnotationConfigApplicationContext) acac).close();
}
}
@Import上面的使用方式屬於靜態的匯入依賴,當然Import註解還有一種動態匯入第三元件的方式是和ImportSelector結合使用
比如我在這裡MainConfig配置類上通過Import註解匯入BrianSelector類.
@Configuration //告訴spring這是一個配置類 /* * @ComponentScan * value:只當於掃描的的包 * excludeFilters = 指定掃描的時候按照什麼規則排除哪些元件 * includeFilters = 指定掃描的時候只需要包含哪些元件 * Filter.ANNOTATION:按照註解 * Filter.ASSIGNABLE_TYPE: 按照給定的型別 * */ @ComponentScans(value = { @ComponentScan(value = "com.brian",includeFilters = { // @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}), // @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}), @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {BrianTypeFilter.class}) },useDefaultFilters = false) }) //@Import({Brian.class,Alan.class}) @Import({BrianSelector.class}) public class MainConfig { @Bean("person") //給容器中註冊一個Bean;型別為返回值的型別;id預設是方法名作為id public Person person(){ return new Person("Alan",18); } /* * @Conditional() 按照條件註冊 * * */ @Conditional({BrianCondition.class}) @Bean("person01") public Person person01() { return new Person("Brian",17); } @Conditional({BrianCondition.class}) @Bean("person02") public Person person02() { return new Person("wenTao",19); } /* * *給容器中註冊元件 * 1,包掃描+ 元件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的方法] * 2, @Bean [匯入的第三方包裡面的元件] * 3,@Import [快速的給容器匯入一個元件] * 1.@Import(要匯入的元件class) * 2.ImportSelector:返回需要匯入的元件的全類名陣列 * 3.ImportBeanDefinitionRegistrar: 手動註冊bean到容器 * 4. 使用Spring提供的FactoryBean * */ @Bean public BrianBeanFactory brianBeanFactory() { return new BrianBeanFactory(); } }
BrianSelector類,該類實現了ImportSelector介面,通過實現selectImports方法,返回需要動態匯入到IOC容器的其他的配置類的全量類名
/* * 自定義返回需要匯入的元件 * */ public class BrianSelector implements ImportSelector { /** * * @param importingClassMetadata 當前被標記有@Import註解的所有註解資訊 * @return */ public String[] selectImports(AnnotationMetadata importingClassMetadata) { System.out.println("----ImportSelector----:"+importingClassMetadata.getClassName()); //return new String[]{}; return new String[]{MainConfigOfAOP.class.getName()}; } }
MainConfigOfAOP配置類有注入MathCalculator物件
@Configuration ublic class MainConfigOfAOP { @Bean public MathCalculator mathCalculator() { return new MathCalculator(); } }
測試類,主要測試在IOC容器中獲取MathCalculator類的資訊
public class MainTest { public static void main(String[] args) { ApplicationContext acac = new AnnotationConfigApplicationContext(MainConfig.class); /* ApplicationContext acac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);*/ System.out.println("ioc容器建立成功"); // Alan alan1 = acac.getBean(Alan.class); // System.out.println("--ALAN--:" + alan1); // Alan alan2 = acac.getBean(Alan.class); //System.out.println("比較兩個Alan例項: " + (alan1 == alan2)); // Person person1 = (Person) acac.getBean("person01"); // System.out.println("---main---test---person1---: " + person1.toString()); // Person person2 = (Person) acac.getBean("person02"); // System.out.println("---main---test---person2---: " + person2.toString()); MathCalculator mathCalculator = (MathCalculator) acac.getBean("mathCalculator"); System.out.println("----get--mathCalculator---: " + mathCalculator); //關閉ioc容器 ((AnnotationConfigApplicationContext) acac).close(); } }
這裡簡單的擴充套件下@Import註解和@Bean註解異同點
1.都是匯入的外部的Jar包
2.@Import的bean id是當前完整路徑地址註冊到IOC容器,@Bean的bean id是以方法名註冊到IOC容器,相比來說@Import注入類更加簡單
3.@EnableXXXX 開啟原理
enable字面意思啟動,亦即開關的概念,有@EnableXXXX註解的地方,基本會看到@Import這個註解,一般他們都是結合起來使用的
比如看到我程式碼裡面的MainConfigOfAutowired這個配置類,上有加上@EnableTransactionManagement這個註解,亦即開啟事務管理
/** * 自動裝配 * Spring利用依賴注入(DI),完成對IOC容器中各個元件的依賴關係賦值 *1).@Autowired,自動注入: * 1.預設優先按照型別去容器中找對應的元件:applicationContext.getBean(BookDao.class); * 2.如果找到多個相同型別的元件,再將屬性方法的名稱作為元件的id去容器中查詢 * applicationContext.getBean("bookDao"); * 3.@Qualifier("bookDao"):使用@Qualifier指定需要裝配的元件id,而不是使用屬性名 * 4.自動裝配預設一定要將屬性賦值好,沒有就會報錯 * 使用@Autoeired(required=false),沒有預設值也不會報錯 * 5.@Primary, 讓Spring進行自動裝配的時候,預設使用首先的Bean * * 2).Spring還支援使用@Resource(JSR250)和@Inject(JSR330) [java規範的註解] * 3).@Autowired :構造器,引數,方法,屬性, * */ @EnableAspectJAutoProxy //開啟AOP代理自動配置 @EnableTransactionManagement //基於註解的事務管理 //@ComponentScan(value = {"com.brian.bean","com.write.annotation"}) @ComponentScan(value = {"com.write.annotation.transaction"}) @Configuration public class MainConfigOfAutowired { @Bean public DataSource dataSource() throws PropertyVetoException { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setJdbcUrl("jdbc:mysql://remotemysql.com:3306/khgvUiO4eh");
我們再看看@EnableTransactionManagement的程式碼,通過Import快速匯入TransactionManagementConfigurationSelector
@Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { /** * Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as * opposed to standard Java interface-based proxies ({@code false}). The default is * {@code false}. <strong>Applicable only if {@link #mode()} is set to * {@link AdviceMode#PROXY}</strong>. * <p>Note that setting this attribute to {@code true} will affect <em>all</em> * Spring-managed beans requiring proxying, not just those marked with * {@code @Transactional}. For example, other beans marked with Spring's * {@code @Async} annotation will be upgraded to subclass proxying at the same * time. This approach has no negative impact in practice unless one is explicitly * expecting one type of proxy vs another, e.g. in tests. */ boolean proxyTargetClass() default false; /** * Indicate how transactional advice should be applied. * <p><b>The default is {@link AdviceMode#PROXY}.</b> * Please note that proxy mode allows for interception of calls through the proxy * only. Local calls within the same class cannot get intercepted that way; an * {@link Transactional} annotation on such a method within a local call will be * ignored since Spring's interceptor does not even kick in for such a runtime * scenario. For a more advanced mode of interception, consider switching this to * {@link AdviceMode#ASPECTJ}. */ AdviceMode mode() default AdviceMode.PROXY; /** * Indicate the ordering of the execution of the transaction advisor * when multiple advices are applied at a specific joinpoint. * <p>The default is {@link Ordered#LOWEST_PRECEDENCE}. */ int order() default Ordered.LOWEST_PRECEDENCE; }
我們再點進去看看TransactionManagementConfigurationSelector這個類,會發現selectImports會根據條件,選擇不同的配置類,所以這就是為什麼說ImportSelector可以動態載入其他配置類了
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { /** * Returns {@link ProxyTransactionManagementConfiguration} or * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY} * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, * respectively. */ @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } } private String determineTransactionAspectClass() { return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); } }
4.基於ImportBeanDefinitionRegistrar註冊bean
再回到MainConfigOfAOP這個配置類
/** * AOP: [動態代理] * 指在程式執行時期間將某段程式碼切入到指定方法指定位置執行的程式設計方式 * * 1.將業務邏輯類和切面類注入到容器中(加上@Aspect註解表示切面類 ) * 2.在切面類上的每個通知方法註解上註解,定義好切點 * 3.開啟基於註解的AOP模式: @EnableAspectAutoProxy * * * AOP 原理: * @EnableAspectJAutoProxy * @Import(AspectJAutoProxyRegistrar.class) 給容器中匯入AspectJAutoProxyRegistrar類 * 利用AspectJAutoProxyRegistrar自定義向容器中註冊bean * AnnotationAwareAspectJAutoProxyCreator * ->AspectJAwareAdvisorAutoProxyCreator * ->AbstractAdvisorAutoProxyCreator * ->AbstractAutoProxyCreator * implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware * 後置處理器(在bean初始化完成前後執行) ,自動裝配BeanFactory * * * */ @Configuration @EnableAspectJAutoProxy public class MainConfigOfAOP { @Bean public MathCalculator mathCalculator() { return new MathCalculator(); } @Bean public LogAspects logAspects() { return new LogAspects(); } }
上面通過@EnableAspectJAutoProxy開啟基於註解的AOP模式,我們點進去看看,又是熟悉的@Import註解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy { /** * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed * to standard Java interface-based proxies. The default is {@code false}. */ boolean proxyTargetClass() default false; /** * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal} * for retrieval via the {@link org.springframework.aop.framework.AopContext} class. * Off by default, i.e. no guarantees that {@code AopContext} access will work. * @since 4.3.1 */ boolean exposeProxy() default false; }
我們再點進去看看AspectJAutoProxyRegistrar這個類
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { /** * Register, escalate, and configure the AspectJ auto proxy creator based on the value * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing * {@code @Configuration} class. */ @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } }
你會發現該類通過實現ImportBeanDefinitionRegistrar介面的registerBeanDefinitions的方法,最終通過AopConfigUtil工具類來註冊到容器中
5.基於FactoryBean註冊bean物件
再次回到我的MainConfig這個配置類
@Configuration //告訴spring這是一個配置類 /* * @ComponentScan * value:只當於掃描的的包 * excludeFilters = 指定掃描的時候按照什麼規則排除哪些元件 * includeFilters = 指定掃描的時候只需要包含哪些元件 * Filter.ANNOTATION:按照註解 * Filter.ASSIGNABLE_TYPE: 按照給定的型別 * */ @ComponentScans(value = { @ComponentScan(value = "com.brian",includeFilters = { // @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}), // @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes = {BookService.class}), @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {BrianTypeFilter.class}) },useDefaultFilters = false) }) //@Import({Brian.class,Alan.class}) @Import({BrianSelector.class}) public class MainConfig { @Bean("person") //給容器中註冊一個Bean;型別為返回值的型別;id預設是方法名作為id public Person person(){ return new Person("Alan",18); } /* * @Conditional() 按照條件註冊 * * */ @Conditional({BrianCondition.class}) @Bean("person01") public Person person01() { return new Person("Brian",17); } @Conditional({BrianCondition.class}) @Bean("person02") public Person person02() { return new Person("wenTao",19); } /* * *給容器中註冊元件 * 1,包掃描+ 元件標註註解(@Controller/@Service/@Repository/@Component)[自己寫的方法] * 2, @Bean [匯入的第三方包裡面的元件] * 3,@Import [快速的給容器匯入一個元件] * 1.@Import(要匯入的元件class) * 2.ImportSelector:返回需要匯入的元件的全類名陣列 * 3.ImportBeanDefinitionRegistrar: 手動註冊bean到容器 * 4. 使用Spring提供的FactoryBean * */ @Bean public BrianBeanFactory brianBeanFactory() { return new BrianBeanFactory(); } }
通過@Bean註解注入BrianBeanFactory,我們點進去看看
public class BrianBeanFactory implements FactoryBean<WenTao> { //獲取物件 public WenTao getObject() throws Exception { return new WenTao(); } //獲取物件的型別 public Class<?> getObjectType() { return WenTao.class; } //獲取物件是單例模式還是原型模式 public boolean isSingleton() { return true; } }
BrianBeanFactoryt通過實現了BeanFactory介面的getObject()獲取到bean物件
上測試類
public class MainTest { public static void main(String[] args) { ApplicationContext acac = new AnnotationConfigApplicationContext(MainConfig.class); /* ApplicationContext acac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);*/ System.out.println("ioc容器建立成功"); // Alan alan1 = acac.getBean(Alan.class); // System.out.println("--ALAN--:" + alan1); // Alan alan2 = acac.getBean(Alan.class); //System.out.println("比較兩個Alan例項: " + (alan1 == alan2)); // Person person1 = (Person) acac.getBean("person01"); // System.out.println("---main---test---person1---: " + person1.toString()); // Person person2 = (Person) acac.getBean("person02"); // System.out.println("---main---test---person2---: " + person2.toString()); // MathCalculator mathCalculator = (MathCalculator) acac.getBean("mathCalculator"); // System.out.println("----get--mathCalculator---: " + mathCalculator); BrianBeanFactory beanFactory = acac.getBean(BrianBeanFactory.class); WenTao wentao = null; try { wentao = beanFactory.getObject(); } catch (Exception e) { e.printStackTrace(); } System.out.println("----get--WenTao---: " + wentao); //關閉ioc容器 ((AnnotationConfigApplicationContext) acac).close(); } }
這裡擴充一點FactoryBean和 BeanFactory的區別,FactoryBean是建立bean物件,BeanFactory是獲取bean物件。