springboot解决了spring以及springmvc繁琐的配置的痛点,以“约定大于配置”为原则,实现了自动装配。下面来探究下springboot自动装配原理。

一、何为装配

把bean放入到Spring的Ioc容器叫做装配,那么在装配Bean的时候,我们首先要知道哪些类需要被装配,实现这一方式的途径总体上说分为两种,一种是传统的xml方式,另一种则是注解方式。下面介绍下通过注解来实现装配。

二、spring 模式注解

模式注解的作用是声明在应用中扮演“组件”角色,即告诉spring容器该类可以被装配。常见的模式注解如下:

注解名称场景说明spring起使版本

@Repository

数据仓储模式注解

2.0

@Component通用组件模式注解 2.5
@Service 服务模式注解 2.5
@Controller 控制器模式注解2.5
@Configuration  配置类模式注解3.0

其中@Repository、@Service 、@Controller 及@Configuration 均被@Component注解所标注。

@Component 作为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标准的组件均为组件扫描的候选对象。类 似地,凡是被 @Component 元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫 描的候选对象。

也就是说如果我们要自定义一个模式注解,该注解只需要被@Component所标注即可。例如定义一个@MyRepository的自定义模式注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyRepository {

    String value() default "";

}

但是要想类被spring容器扫描到,除了在类上标注模式注解外,还需要指定被模式注解标注的类的包路径,这个可以通过@ComponentScan这个注解来实现。例如:

 
@ComponentScan(basePackages = "com.chaoyue.spring.boot")
public class SpringConfiguration {
... }

三、Spring   @Enable注解

Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。常见的@Enable注解注解如下:

框架实现

注解名称

注解说明

Spring Framework

@EnableWebMvc

Web MVC 模块

 
@EnableTransactionManagement

事务管理模块

 
@EnableCaching
缓存模块

Spring Boot

@EnableAutoConfiguration

自动装配模块

 
@EnableOAuth2Sso

OAuth2 单点登录模块

Spring Cloud

@EnableEurekaServer

Eureka服务器模块

 
@EnableConfigServer

配置服务器模块

 
@EnableFeignClients

Feign客户端模块

 
@EnableZuulProxy

服务网关 Zuul 模块

 
@EnableCircuitBreaker

服务熔断模块

通过@Enable注解,我们可以在不需要借助@ComponentScan这个注解情况下,把实现同一功能的bean打包装配到Spring容器中。@Enable注解可以通过注解驱动或者接口编程方式来实现装配。

1.通过注解驱动方式

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
        ...
}

2.通过接口编程方式

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

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    

    public String[] selectImports(AdviceMode adviceMode) {
        switch(adviceMode) {
        case PROXY:
            return this.getProxyImports();
        case ASPECTJ:
            return this.getAspectJImports();
        default:
            return null;
        }
    }

    
}

四、Spring   条件注解

从 Spring Framework 3.1 开始,允许在 Bean 装配时增加前置条件判断

Spring中实现条件装配主要有两种方式,分别如下:

注解名称使用场景起始版本

@Profile

配置化条件装配

3.1

@Conditional

编程条件装配

4.0

4.1 @Profile

    我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们以前所定义的Bean,当Spring容器一启动的时候,就会一股脑的全部加载这些信息完成对Bean的创建;而使用了Profile之后,它会将Bean的定义进行更细粒度的划分,将这些定义的Bean划分为几个不同的组,当Spring容器加载配置信息的时候,首先查找激活的Profile,然后只会去加载被激活的组中所定义的Bean信息,而不被激活的Profile中所定义的Bean定义信息是不会加载用于创建Bean的。

4.1 @Conditional

    @Conditional注解是可以根据一些自定义的条件动态的选择是否加载该bean到springIOC容器中去,springBoot源码中大量使用了该注解。下面举例说明该注解的使用方法

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

    /**
     * Java 系统属性名称
     * @return
     */
    String name();

    /**
     * Java 系统属性值
     * @return
     */
    String value();
}
public class OnSystemPropertyCondition implements Condition {

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

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

        String propertyName = String.valueOf(attributes.get("name"));

        String propertyValue = String.valueOf(attributes.get("value"));

        String javaPropertyValue = System.getProperty(propertyName);

        return propertyValue.equals(javaPropertyValue);
    }
}
public class ConditionalOnSystemPropertyBootstrap {

    @Bean
    @ConditionalOnSystemProperty(name = "user.name", value = "chaoyue")
    public String helloWorld() {
        return "Hello,World ";
    }

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);
        // 通过名称和类型获取 helloWorld Bean
        String helloWorld = context.getBean("helloWorld", String.class);

        System.out.println("helloWorld Bean : " + helloWorld);

        // 关闭上下文
        context.close();
    }
}

 上面的示例说明只有当系统变量用户名设置为chaoyue时,才加载helloword这个bean  。

五、Spring 工厂加载机制

Spring 工厂加载机制,即 Spring Factories Loader,核心逻辑是使用 SpringFactoriesLoader加载由用户实现的类,并配置在约定好的META-INF/spring.factories 路径下,该机制可以为框架上下文动态的增加扩展。
该机制类似于 Java SPI,给用户提供可扩展的钩子,从而达到对框架的自定义扩展功能。

SpringFactoriesLoader 是 Spring 工厂加载机制的核心底层实现类。它的主要作用是 从 META-INF/spring.factories 路径下加载指定接口的实现类。该文件可能存在于工程类路径下或者 jar 包之内,所以会存在多个该文件。下面是spring-boot-actuator-autoconfigure-2.0.2.RELEASE.jar中的spring.factories文件部分内容。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpointAutoConfiguration,\

SpringBoot项目启动时,就是加载上述XXAutoConfiguration从而实现自动装配。

六、SpringBoot自动装配原理

    SpringBoot利用Spring 模式注解装配、Spring @Enable 模块装配、Spring 条件装配装配及Spring 工厂加载机制来实现其自动装配的原理。下面自定义实现一个自动装配,自动配置helloWorld这个bean。

    在项目/src/main/resources/META-INF路径下新建spring.factories文件:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.chaoyue.diveinspringboot.configuration.HelloWorldAutoConfiguration
@SpringBootApplication
public class EnableAutoConfigurationBootstrap {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);

        // helloWorld Bean 是否存在
        String helloWorld =
                context.getBean("helloWorld", String.class);

        System.out.println("helloWorld Bean : " + helloWorld);

        // 关闭上下文
        context.close();

    }
}
/**
 * HelloWorld 自动装配
 */
@Configuration // Spring 模式注解装配
@EnableHelloWorld // Spring @Enable 模块装配
@ConditionalOnSystemProperty(name = "user.name", value = "chaoyue") // 条件装配
public class HelloWorldAutoConfiguration {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldConfiguration.class)
//@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String helloWorld() { // 方法名即 Bean 名称
        return "Hello,World";
    }
}

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐