初尝Spring的条件装配
为了了解SpringBoot自动化装配的机制,所以必须先了解Spring的一些基础,Spring的条件装配就是其中之一
- 前提概要 
  - 条件装配知识脑图
- 条件装配的定义
- 条件装配的实现
- 条件装配的作用
- 条件装配的应用
 
- 条件装配的源码体现 
  - 注解模式
- 编程模式
 
- 自定义条件装配 
  - 注解模式
- 编程模式
 
- 参考资料
前提概要
条件装配知识脑图
 
 
条件装配的定义
- Bean装配的前置条件判断
- 简而言之就是根据条件来判断这个Bean是否要被Spring容器装配
条件装配的实现
条件装配有两种实现方式:
- 注解模式:@Profile
- 编程模式:@Conditional
条件装配的作用
可以根据条件来选择是否需要在Spring容器中装配这个Bean,比如说:
- 当Servlet依赖没有的时候,不装配WebMvc的各种Bean
- 当某个Bean已经不存在的时候,就不重复装配
- 根据不同的系统环境,装配不同的Bean
- …
条件装配的应用
- Spring Framework
 @Profile
 …
- Spring Boot
 @ConditionalOnMissingBean
 @ConditionalOnClass
 …
条件装配的源码体现
注解模式
…这个没什么好说的,@Profile貌似使用的比较少,但是我们通常也知道一个SpringBoot多环境配置的特性,可以通过注解模式实现。但是在Spring 4.0开始,@Profile模式被修改过,实际的底层实现依然是编程模式
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();
}
从源码中,我们可以看到,它使用了编程模式的@Conditional注解,并指定了一个Condition接口的实现类ProfileCondition
编程模式
我们就从@ConditionalOnMissingBean(是否存在某个Bean)说起。
第一步,在IDE中寻找了一下,使用了@ConditionalOnMissingBean的地方,我们就从spring boot的webmvc的配置类开始说起 WebMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
		TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
我们看到 WebMvcAutoConfiguration配置类的定义,可以看到,类上是有@ConditionalOnMissingBean注解的声明,并且指定了一个类WebMvcConfigurationSupport,意思就是说当Spring容器中没有WebMvcConfigurationSupport这个Bean的时候,就会装配WebMvcAutoConfiguration 配置类
第二步,我们看check一下@ConditionalOnMissingBean注解的源码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
	Class<?>[] value() default {};
	String[] type() default {};
	Class<?>[] ignored() default {};
	String[] ignoredType() default {};
	Class<? extends Annotation>[] annotation() default {};
	String[] name() default {};
	SearchStrategy search() default SearchStrategy.ALL;
	Class<?>[] parameterizedContainer() default {};
}
嗯,成员属性还挺多的,但我们也注意到注解的声明中,存在@Conditional注解,其指定了一个Condition接口的实现类OnBeanCondition.
第三步,我们查看一下@Conditional注解指向的实现类OnBeanCondition. Oh~,它还蛮复杂的,所以我们只能简化一下,只看与Condition接口相关的方法,最后发现OnBeanCondition类,最终是通过继承SpringBootCondition去实现Condition方法的,为了方便理解,这里放了结构图:
 
第四步,寻找matchs()方法,发现是由SpringBootContidion实现的(SpringBootContidion就是SpringBoot对Condition的再封装)
	
	@Override
	public final boolean matches(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			//如何获取
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			//重点
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException(
					"Could not evaluate condition on " + classOrMethodName + " due to "
							+ ex.getMessage() + " not "
							+ "found. Make sure your own configuration does not rely on "
							+ "that class. This can also happen if you are "
							+ "@ComponentScanning a springframework package (e.g. if you "
							+ "put a @ComponentScan in the default package by mistake)",
					ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException(
					"Error processing condition on " + getName(metadata), ex);
		}
	}
以上是SpringBootCondition实现的matches方法,OnBeanCondition继承了下来。我们可以看到这个方法的返回值是通过outcome.isMatch(),而这个outcome又是通过getMatchOutcome()方法获取的,这个方法又是谁的呢?是SpringBootCondition抽象类的抽象方法,最终是OnBeanCondition实现的
第五步,查看OnBeanCondition的getMatchOutcome()方法,发现…emmm,更加复杂了。那就再简化简化
- 总之这个方法会判断传入的metadate是不是ConditionalOnMissingBean注解类型,类似switch的功能,判断好久跳转到对应的逻辑块执行.
- 然后调用一个getMatchingBeans()方法,获取Bean然后调用一个getMatchingBeans()方法,获取Bean
- 根据返回的matchResult判断是否有对应的Bean,返回一个matchMessage结果
- 最后这个matchMessage就会被SpringBootCondition获取到,用于给matches方法判断是true,还是false
第六步,当matchs方法返回了true 或者false之后,Spring容器就会根据这个flag判断是否装配WebMvcAutoConfiguration配置类
 
小结:
- 的确,内部的逻辑比较复杂,所以暂时就忽略了很多部分,有一些地方我自己也没有弄懂,所以就不能太详细的说明,所以如有错误,请指出
- 虽然OnBeanCondition非常的复杂,那是因为SpringBoot本身就是一个复杂的东西,所以我们自己实现的时候,未必需要实现的跟它一样复杂。
自定义条件装配
注解模式
目录结构,总共四个类

 这里要实现一个计算服务,根据不同的JDK版本,执行不同的代码
- CalculateService
 计算服务接口
- Java7CalculateService
 Java 7 计算服务实现类,通过for循环实现
- Java8CalculateService
 Java 8 计算服务实现类,通过lambda和Stream实现
- Demo2Application
 SpringBoot启动引导类
CalculateService
public interface CalculateService {
    Integer sum(Integer...values);
}
Java7CalculateService
**
 * Java7计算服务
 * @author liwenjie
 */
@Profile("Java7")
@Service
@Slf4j
public class Java7CalculateService implements CalculateService {
    @Override
    public Integer sum(Integer... values) {
        int sum = 0;
        for (Integer i : values){
            sum = sum + i;
        }
        log.error("Java7 Calculating");
        return sum;
    }
}
**Java8CalculateService
/**
 * Java8计算服务
 * @author liwenjie
 */
@Profile("Java8")
@Service
@Slf4j
public class Java8CalculateService implements CalculateService {
    @Override
    public Integer sum(Integer... values) {
        log.error("Java8 Calculating");
        return Stream.of(values).reduce(0,Integer::sum);
    }
}
Demo2Application
@Slf4j
@SpringBootApplication
public class Demo2Application {
	//获取计算服务实现类
    @Autowired
    CalculateService calculateService;
	
	//通过SpringApplication的方式修改profiles
	//当然你也可以在application.properties中配置spring.profiles.active=Java7
    public static void main(String[] args) {
        new SpringApplicationBuilder(Demo2Application.class).profiles("Java7").run(args);
    }
    @PostConstruct
    public void init(){
        calculateService.sum(1,2,3,4,5);
    }
}
- 最后就会实际运行的就是Java7的实现类
- 要注意的是,我这里使用了lombok插件(强烈推荐),所以没有的把log.error修改为System.out.println就好了
编程模式
目录结构,三个类
- ConditionOnSystemProperty注解
 我们这里的作用就是判断系统的Property值是否跟注解的属性值相同,如果相同则装配该Bean
- OnSystemPropertyCondition实现类
 具体的Condition实现类
- Demo2Application类
 SpringBoot启动引导类
ConditionOnSystemProperty
模拟@ConditionalOnMissingBean注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionOnSystemProperty {
    /**
     *  系统名称,property的key
     * @return
     */
    String name();
    /**
     * 系统属性值,property的value
     * @return
     */
    String value();
}
OnSystemPropertyCondition
public class OnSystemPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		
		//获取ConditionOnSystemProperty的成员属性,最终map有两个元素,name和value
        Map<String ,Object> attributes = metadata.getAnnotationAttributes(ConditionOnSystemProperty.class.getName());
		
		//获取属性名称
        String propertyName = String.valueOf(attributes.get("name"));
        //获取属性值
        String propertyValue = String.valueOf(attributes.get("value"));
		//从系统的Property中传入key,获取value
        String systemPropertyValue = System.getProperty(propertyName);
		
		//最后比较propertyValue 和 systemPropertyValue 是否相等,并返回
        return systemPropertyValue.equals(propertyValue);
    }
}
Demo2Application
@SpringBootApplication
public class Demo2Application {
	@Bean
	@ConditionOnSystemProperty(name="user.name",value = "xxxx")
	public String Hello(){
		return "Hello World";
	}
	
    public static void main(String[] args) {
        new SpringApplicationBuilder(Demo2Application.class).run(args);
    }
}
- 结果就是,如果系统的property中没有key为user.name的元素,或者key为user.name的值不为xxxx,那么Spring容器就不会装配hello这个Bean
小结:
- 自定义条件判断注解,注解内部需要有@Conditional,并指向一个Condition接口的实现类
- Condition接口的实现类需要实现matchs方法
- Spring会根据matchs方法返回flag来决定是否装配被自定义条件判断注解修饰的Bean
 
                   
                   
                   
                   
                             本文深入探讨Spring框架中的条件装配机制,包括注解模式和编程模式的实现,以及自定义条件装配的方法。通过实例展示了如何根据不同环境和需求,选择性地在Spring容器中装配Bean。
本文深入探讨Spring框架中的条件装配机制,包括注解模式和编程模式的实现,以及自定义条件装配的方法。通过实例展示了如何根据不同环境和需求,选择性地在Spring容器中装配Bean。
           
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
              
             
                   1401
					1401
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
            


 
            