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 |
|
Web MVC 模块 |
|
事务管理模块 |
|
|
缓存模块 | |
Spring Boot |
|
自动装配模块 |
|
OAuth2 单点登录模块 |
|
Spring Cloud |
|
Eureka服务器模块 |
|
配置服务器模块 |
|
|
Feign客户端模块 |
|
|
服务网关 Zuul 模块 |
|
|
服务熔断模块 |
通过@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 {
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 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";
}
}