Spring Boot之 Spring组件装配
一. @Enable模块驱动
@Enable
模块驱动在Spring Framework 3.1后开始支持。这里的模块通俗的来说就是一些为了实现某个功能的组件的集合。通过@Enable
模块驱动,我们可以开启相应的模块功能。
@Enable
模块驱动可以分为“注解驱动”和“接口编程”两种实现方式,下面逐一进行演示:
注解驱动
Spring中,基于注解驱动的示例可以查看@EnableWebMvc
源码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
该注解通过@Import
导入一个配置类DelegatingWebMvcConfiguration
:
该配置类又继承自WebMvcConfigurationSupport
,里面定义了一些Bean的声明。
所以,基于注解驱动的
@Enable
模块驱动其实就是通过@Import
来导入一个配置类,以此实现相应模块的组件注册,当这些组件注册到IOC容器中,这个模块对应的功能也就可以使用了。
我们来定义一个基于注解驱动的@Enable
模块驱动。
在com.example.demo
下新建configuration
包,然后创建一个HelloWorldConfiguration
配置类:
@Configuration
public class HelloWorldConfiguration {
@Bean
public String hello() {
return "hello world";
}
}
这个配置类里定义了一个名为hello
的Bean,内容为hello world
。
在com.example.demo.annotation
下创建一个EnableHelloWorld
注解定义:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
我们在该注解类上通过@Import
导入了刚刚创建的配置类。
接着在com.example.demo.bootstrap
下创建一个TestEnableBootstap
启动类来测试@EnableHelloWorld
注解是否生效:
@EnableHelloWorld
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(DemoApplication.class)
.web(WebApplicationType.NONE)
.run(args);
String hello = context.getBean("hello", String.class);
System.out.println("hello Bean: " + hello);
context.close();
}
}
运行该类的main方法,控制台输出如下:
hello Bean: hello world
说明我们自定义的基于注解驱动的@EnableHelloWorld
是可行的。
接口编程
除了使用上面这个方式外,我们还可以通过接口编程的方式来实现@Enable
模块驱动。Spring中,基于接口编程方式的有@EnableCaching
注解,查看其源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
EnableCaching
注解通过@Import
导入了CachingConfigurationSelector
类,该类间接实现了ImportSelector
接口
所以通过接口编程实现
@Enable
模块驱动的本质是:通过@Import
来导入接口ImportSelector
实现类,该实现类里可以定义需要注册到IOC容器中的组件,以此实现相应模块对应组件的注册。
接下来我们根据这个思路来自个实现一遍:
在com.example.demo
下新建selector
包,然后在该路径下新建一个HelloWorldImportSelector
实现ImportSelector
接口
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
如果看不懂上面这段代码含义的朋友可以阅读深入学习Spring组件注册一文。
接着我们修改EnableHelloWorld
:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
上面导入的是HelloWorldImportSelector
,而非HelloWorldConfiguration
。
再次运行TestEnableBootstap
的main方法,你会发现输出是一样的。
二. 自动装配
Spring Boot中的自动装配技术底层主要用到了下面这些技术:
- Spring 模式注解装配
- Spring @Enable 模块装配
- Spring 条件装配装
- Spring 工厂加载机制
Spring 工厂加载机制的实现类为SpringFactoriesLoader
,查看其源码:
该类的方法会读取META-INF目录下的spring.factories配置文件,我们查看spring-boot-autoconfigure-2.1.0.RELEASE.jar下的该文件:
当启动类被@EnableAutoConfiguration
标注后,上面截图中的所有类Spring都会去扫描,看是否可以纳入到IOC容器中进行管理。
可看到该类上标注了一些注解,其中@Configuration
为模式注解,@EnableConfigurationProperties
为模块装配技术,ConditionalOnClass
为条件装配技术。这和我们上面列出的Spring Boot自动装配底层主要技术一致,所以我们可以根据这个思路来自定义一个自动装配实现。
新建一个配置类HelloWorldAutoConfiguration
:
@Configuration
@EnableHelloWorld
@ConditionalOnProperty(name = "helloworld", havingValue = "true")
public class HelloWorldAutoConfiguration {
}
然后在resources目录下新建META-INF目录,并创建spring.factories文件:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.configuration.HelloWorldAutoConfiguration
接着在配置文件application.properties中添加helloworld=true
配置
helloworld=true
最后创建EnableAutoConfigurationBootstrap
,测试下HelloWorldAutoConfiguration
是否生效:
@EnableAutoConfiguration
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(DemoApplication.class)
.web(WebApplicationType.NONE)
.run(args);
String hello = context.getBean("hello", String.class);
System.out.println("hello Bean: " + hello);
context.close();
}
}
运行该main方法,控制台输出如下:
hello Bean: hello world
说明我们自定义的自动装配已经成功了。
下面简要分析下代码的运行逻辑:
-
Spring 的工厂加载机制会自动读取META-INF目录下spring.factories文件内容;
-
我们在spring.factories定义了:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.demo.configuration.HelloWorldAutoConfiguration
我们在测试类上使用了
@EnableAutoConfiguration
注解标注,那么HelloWorldAutoConfiguration
就会被Spring扫描,看是否符合要求,如果符合则纳入到IOC容器中; -
HelloWorldAutoConfiguration
上的@ConditionalOnProperty
的注解作用为:当配置文件中配置了helloworld=true
(我们确实添加了这个配置,所以符合要求)则这个类符合扫描规则;@EnableHelloWorld
注解是我们前面例子中自定义的模块驱动注解,其引入了hello这个Bean,所以IOC容器中便会存在hello这个Bean了; -
通过上面的步骤,我们就可以通过上下文获取到hello这个Bean了。