Spring5源码深度分析之理解@Conditional,@Import注解
Posted hlkawa
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring5源码深度分析之理解@Conditional,@Import注解相关的知识,希望对你有一定的参考价值。
代码地址:https://github.com/showkawa/spring-annotation/tree/master/src/main/java/com/brian
1.源码分析二主要分析的内容
1.使用@Condition多条件注册bean对象
[email protected]注解快速注入第三方bean对象
[email protected] 开启原理
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 [快速的给容器导入一个组件] * [email protected](要导入的组件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注解的使用也是简单的
[email protected]注解快速注入第三方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 [快速的给容器导入一个组件] * [email protected](要导入的组件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 [快速的给容器导入一个组件] * [email protected](要导入的组件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包
[email protected]的bean id是当前完整路径地址注册到IOC容器,@Bean的bean id是以方法名注册到IOC容器,相比来说@Import注入类更加简单
[email protected] 开启原理
enable字面意思启动,亦即开关的概念,有@EnableXXXX注解的地方,基本会看到@Import这个注解,一般他们都是结合起来使用的
比如看到我代码里面的MainConfigOfAutowired这个配置类,上有加上@EnableTransactionManagement这个注解,亦即打开事务管理
/** * 自动装配 * Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值 *1)[email protected],自动注入: * 1.默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class); * 2.如果找到多个相同类型的组件,再将属性方法的名称作为组件的id去容器中查找 * applicationContext.getBean("bookDao"); * [email protected]("bookDao"):使用@Qualifier指定需要装配的组件id,而不是使用属性名 * 4.自动装配默认一定要将属性赋值好,没有就会报错 * 使用@Autoeired(required=false),没有默认值也不会报错 * [email protected], 让Spring进行自动装配的时候,默认使用首先的Bean * * 2).Spring还支持使用@Resource(JSR250)和@Inject(JSR330) [java规范的注解] * 3)[email protected] :构造器,参数,方法,属性, * */ @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 [快速的给容器导入一个组件] * [email protected](要导入的组件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对象。
最后说一下,我的博客可能不是首创,但也属于我自己根据自己理解一点点分析的,如果有帮助到你,转载请注明出处!
以上是关于Spring5源码深度分析之理解@Conditional,@Import注解的主要内容,如果未能解决你的问题,请参考以下文章
Spring5源码分析(007)——IoC篇之加载 BeanDefinition(的大致流程)
Spring5源码分析(006)——IoC篇之核心类DefaultListableBeanFactory和XmlBeanDefinitionReader