Spring Boot 自动装配开始篇

137 阅读3分钟

​ 我们在学习spring-boot的路上,是否看见一堆 @Enable...的注解类, 例如 @EnableWebMvc , @EnableAutoConfiguration , @EnableDiscoveryClient , 此时我们是否想过为什么 ? 到底是做啥了?

1. 体验自动装配

1. 第一种实现方式

自定义一个Bean :

@Configuration("myConfigBean")
public class MyConfigBean {

    @Override
    public String toString() {
        return "This is MyConfigBean";
    }
}

自定义一个 @Enable 自动装配类 :

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 导入自己想要加载的类 : 
@Import(MyConfigBean.class)
public @interface EnableConfigBean {

}

启动类 : 这里有些特殊 , 大家也可以学着点 .

// 导入自动装配
@EnableConfigBean
public class ConditionSpringbootApplication{

	public static void main(String[] args) {
		SpringApplicationBuilder builder = new SpringApplicationBuilder();
// 配置bean注入,此时就是ConditionSpringbootApplication,和原来spring的@Configuration类一样,叫配置Bean
		ConfigurableApplicationContext context = builder.sources(ConditionSpringbootApplication.class)
				.bannerMode(Banner.Mode.OFF)
            // spring-boot 模式有三种,我们选择NONE
				.web(WebApplicationType.NONE)
            // 必须run 来启动spring-boot
				.run(args);
        
        // 大家需要注意一下,如果是一个内部类注册Bean,获取bean的名字就不是首字母小写了.
//例如 com.spring.conditionspringboot.import_example.boot.ConditionSpringbootApplication$MyBean
//需要上面这种写法 . 
		// 打印自己注入的bean
		Object bean = context.getBean("myConfigBean");
		System.out.println(bean);
	}
}

运行结果 :

This is MyConfigBean
// 其他日志忽略

通过上面的代码我们已经大致了解了 @Enable 做了什么 ,他的主要关键点在哪 @Import 注解这,会自动注入一个Bean.

2. 第二种实现方式

@Enable 实现类

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({MyImportSelector.class})
public @interface EnableConfigBean {

}

MyImportSelector 实现 方式 ,可以看看 @EnableAutoConfiguration这个注解, 他就用到了一个 Selector .

public class MyImportSelector implements ImportSelector {

    // AnnotationMetadata 是一个 注解元信息 ,可以获取注解信息,以及注解所在类的其他信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyConfigBean.class.getName()};
    }
}
public class MyConfigBean {

    @Bean
    public String helloWorld(){
        return "Hello World";
    }
}

2. 条件装配

​ 从spring Framework 3.1 开始的

@Profile

接口类:

public interface CalculateService {

    Integer sum(Integer ...values);
}

@Profile注解 标注的实现类第一个

@Profile("Java7")
@Service
public class Java7CalculateService implements CalculateService{
    
    @Override
    public Integer sum(Integer... values) {
        Integer sum = 0;
        for (Integer value : values) {
            sum += value;
        }
        return sum;
    }
}

@Profile注解 标注的实现类第二个

@Profile("Java8")
@Service
public class Java8CalculateService implements CalculateService{

    @Override
    public Integer sum(Integer... values) {

        return Stream.of(values).reduce(Integer::sum).get();
    }
}

启动类 :

@ComponentScan("com.spring.conditionspringboot.condition_example.bean")
public class CalculateBootStrap {

    public static void main(String[] args) {

        SpringApplicationBuilder builder = new SpringApplicationBuilder();

        ConfigurableApplicationContext context = builder.sources(CalculateBootStrap.class)
                .bannerMode(Banner.Mode.OFF)
                .web(WebApplicationType.NONE)
           		// 这里需要指定profile ,不然会报错 ,
                .profiles("Java7")
                .run(args);


        CalculateService service = context.getBean(CalculateService.class);

        Integer sum = service.sum(1, 2, 3, 4);

        System.out.println(service+" : "+sum);

        context.close();
    }

}

输出结果 :

com.spring.conditionspringboot.condition_example.bean.Java7CalculateService@3e6ef8ad : 10

@Conditional

注解实现类 : 主要是实现一个 @Conditional 这个注解,指向一个Condition的实现类 .

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ConditionProfile.class)
public @interface MyConditional {


    String key();

    String value();

}

ConditionProfile 实现类 :

// 这个类可以获取所有被 @MyConditional 注解所标记类的 , 注解信息 . 判断是否可以注入bean
public class ConditionProfile implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

        Map<String, Object> attributes = metadata.getAnnotationAttributes(MyConditional.class.getName());

        Object key = attributes.get("key");

        Object value = attributes.get("value");

        if (key.equals("name")) {

            return true;
        }
        return false;
    }
}

启动类 :

public class ConditionBootstrap {

    @MyConditional(key = "name",value = "sb")
    @Bean
    public String helloWorld(){
        return "Hello world";
    }


    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder();


        ConfigurableApplicationContext context = builder.sources(ConditionBootstrap.class)
                .bannerMode(Banner.Mode.OFF)
                .web(WebApplicationType.NONE)
                .run(args);


        Object bean = context.getBean("helloWorld");

        System.out.println(bean);
    }

}

输出结果 :

Hello world

3. 走向 @EnableAutoConfiguration

首先实现一个 MyAutoConfiguration ,

这个类需要实现你想加载的Bean ,我们注入了 @Enable 模块 和 @Conditional 模块 , 其实不用配置 @Configuration ,但是源码中好多都加了 , 可以去spring-boot-autoconfigure-2.0.4.RELEASE.jar 这个包中有大量的自动加载的类

@Configuration
@MyConditional(key = "name",value = "key")
@EnableConfigBean
public class MyAutoConfiguration {

}

在resource /META-INF/spring.factories 中添加

# Auto Configure  其中 \是换行符,不能有乱带空格之类的,
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.spring.conditionspringboot.autoconfiguration.bean.MyAutoConfiguration

我们的启动类 : 只要加上 @EnableAutoConfiguration 的注解轻轻松松 ,其实 @SpringBootApplication就是里面包含了@EnableAutoConfiguration ,所以可以自动装配实现

@EnableAutoConfiguration
public class AutoBootStrap {

    public static void main(String[] args) {

        SpringApplicationBuilder builder = new SpringApplicationBuilder();

        ConfigurableApplicationContext context = builder.sources(AutoBootStrap.class)
                .bannerMode(Banner.Mode.OFF)
                .web(WebApplicationType.NONE)
                .run(args);

        Object bean = context.getBean("helloWorld");

        System.out.println(bean);

        context.close();
    }

}

输出

Hello World

加载机制分析 :

1 . SpringApplication 启动的时候去找运行环境,此时就会去找SpringFactoriesLoader, SpringFactoriesLoader 会加载 "META-INF/spring.factories" 下面的文件

2 . org.springframework.boot.autoconfigure.EnableAutoConfiguration= 会自动加载你写的那些自动装配的自动装配类 ,例如本例子中的 com.spring.conditionspringboot.autoconfiguration.bean.MyAutoConfiguration 类 ,

3 . 第一步 条件加载 @MyConditional

4 . 第二步 模式注解 : 这个类须被 @Configuration 修饰 , 其实没必要

5 . @Enable 模块 : @EnableConfigBean的自动装配的类