SpringBoot深度实践之自动装配
① 模式注解
模式注解是一种用于声明在应用中扮演"组件"角色的注解。如spring framework中的@Repository标注在任何类上,用于扮演仓储角色的模式注解。模式注解会在类路径扫描的时候被装载并注册成BeanDefinition。
模式注解举例
SpringFramework注解 | 场景说明 |
---|---|
@Repository | 数据仓储模式注解 |
@Component | 通用组件模式注解 |
@Service | 服务模式注解 |
@Controller | Web控制器模式注解 |
@Configuration | 配置类模式注解 |
装载方式
<context:component-scan>方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring- context.xsd">
<!-- 激活注解驱动特性 -->
<context:annotation-config />
<!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
<context:component-scan base-package="com.imooc.dive.in.spring.boot" /> </beans>
@ComponentScan方式
@ComponentScan(basePackages = "com.imooc.dive.in.spring.boot")
public class SpringConfiguration { ... }
自定义模式注解
@Component "派生性"
/*** 一级 {@link Repository @Repository}
** @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since 1.0.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {
String value() default "";
}
- @Component
- @Repository
- @FirstLevelRepository
@Component “层次性”
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@FirstLevelRepository
public @interface SecondLevelRepository {
String value() default "";
}
-
@Component
- @Repository
- @FirstLevelRepository
- @SecondLevelRepository测试: 和使用@Component一样,注解到Class上面。然后进行被扫描就ok。 @SecondLevelRepository也可以换成@FirstLevelRepository一样的效果
/**
* 我的 {@link FirstLevelRepository}
* @author 小马哥
* @since 2018/5/14
*/
@SecondLevelRepository(value = "myFirstLevelRepository") // Bean 名称
public class MyFirstLevelRepository {
}
测试
/**
* 仓储的引导类
*
* @author 小马哥
* @since 2018/5/14
*/
@ComponentScan(basePackages = "com.imooc.diveinspringboot.repository")
public class RepositoryBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(RepositoryBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// myFirstLevelRepository Bean 是否存在
MyFirstLevelRepository myFirstLevelRepository =
context.getBean("myFirstLevelRepository",MyFirstLevelRepository.class);
System.out.println("myFirstLevelRepository Bean : "+myFirstLevelRepository);
// 关闭上下文
context.close();
}
}
结果会打印出myFirstLevelRepository这个类,因为@SecondLevelRepository本质上是一个@Component,会被Spring IOC容器扫描到
ps: @Configuration也是派生于@Component
Spring @Enable模块装配
Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。比如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处理模块等。
@Enable注解模块举例
框架实现 | @Enable注解模块 | 激活模块 |
---|---|---|
Spring Framework | @EnableWebMvc | Web MVC模块 |
@EnableTransactionManagement | 事务管理模块 | |
@EnableCaching | Caching模块 | |
@EnableMBeanExport | JMX模块 | |
@EnableAsync | 异步处理模块 | |
@EnableWebFlux | Web Flux模块 | |
@EnableAspectJAutoProxy | AspectJ代理模块 | |
Spring Boot | @EnableAutoConfiguration | 自动装配模块 |
@EnableManagementContext | Actuator管理模块 | |
@EnableConfigurationProperties | 配置属性绑定模块 | |
@EnableOAuth2SSO | OAuth2单点登录模块 | |
Spring Cloud | @EnableEurekaServer | Eureka服务器模块 |
@EnableConfigServer | 配置服务器模块 | |
@EnableFeignClients | Feign客户端模块 | |
@EnableZuulProxy | 服务网关Zuul模块 | |
@EnableCircuitBreaker | 服务熔断模块 |
实现方式
① 注解驱动方式
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching { ... }
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { ... }
② 接口编程方式
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching { ... }
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
/**
* {@inheritDoc}
* @return {@link ProxyCachingConfiguration} or {@code AspectJCacheConfiguration} for
* {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
respectively */
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY: return new String[] { AutoProxyRegistrar.class.getName(),ProxyCachingConfiguration.class.getName() };
case ASPECTJ: return new String[] { AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME }; default: return null;
}
}
自定义@Enable模块
① 基于注解驱动实现
/**
* 激活 HelloWorld 模块
*
* @author 小马哥
* @since 2018/5/14
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}
/**
* HelloWorld 配置
*
* @author 小马哥
* @since 2018/5/14
*/
@Configuration
public class HelloWorldConfiguration {
@Bean
public String helloWorld() { // 方法名即 Bean 名称
return "Hello,World 2018";
}
}
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
// helloWorld Bean 是否存在
String helloWorld =
context.getBean("helloWorld", String.class);
System.out.println("helloWorld Bean : " + helloWorld);
// 关闭上下文
context.close();
}
}
② 基于接口驱动实现
/**
* HelloWorld {@link ImportSelector} 实现
*
* @author 小马哥
* @since 2018/5/14
*/
public class HelloWorldImportSelector implements ImportSelector {
// sekectImports方法返回配置类的集合(HelloWorldImportSelector -> HelloWorldConfiguration -> HelloWorld)
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}
/**
* 激活 HelloWorld 模块
*
* @author 小马哥
* @since 2018/5/14
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}
/**
* HelloWorld 配置
*
* @author 小马哥
* @since 2018/5/14
*/
@Configuration
public class HelloWorldConfiguration {
@Bean
public String helloWorld() { // 方法名即 Bean 名称
return "Hello,World 2018";
}
}
Spring条件装配
从Spring Framework3.1开始,允许在Bean装配时增加前置条件判断
Spring注解 | 场景说明 | 起始版本 |
---|---|---|
@Profile | 配置化条件装配 | 3.1 |
@Conditional | 编程条件装配 | 4.0 |
@Profile应用于动态选择service的implements,比如下面的例子,一个计算服务在jdk7和jdk8中有两种不同的实现方式,可以使用@Profile来切换使用
/**
* 计算服务
*
* @author 小马哥
* @since 2018/5/15
*/
public interface CalculateService {
/**
* 从多个整数 sum 求和
* @param values 多个整数
* @return sum 累加值
*/
Integer sum(Integer... values);
}
/**
* Java 7 for 循环实现 {@link CalculateService}
*
* @author 小马哥
* @since 2018/5/15
*/
@Profile("Java7")
@Service
public class Java7CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("Java 7 for 循环实现 ");
int sum = 0;
for (int i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
}
/**
* Java 8 Lambda 实现 {@link CalculateService}
*
* @author 小马哥
* @since 2018/5/15
*/
@Profile("Java8")
@Service
public class Java8CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("Java 8 Lambda 实现");
int sum = Stream.of(values).reduce(0, Integer::sum);
return sum;
}
}
/**
* {@link CalculateService} 引导类
*
* @author 小马哥
* @since 2018/5/15
*/
@SpringBootApplication(scanBasePackages = "com.imooc.diveinspringboot.service")
public class CalculateServiceBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("Java8") // 此处通过profiles方法来动态切换service实现
.run(args);
// CalculateService Bean 是否存在
CalculateService calculateService = context.getBean(CalculateService.class);
System.out.println("calculateService.sum(1...10) : " +
calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// 关闭上下文
context.close();
}
}
@Conditional用于条件判断
/**
* Java 系统属性 条件判断
*
* @author 小马哥
* @since 2018/5/15
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
/**
* Java 系统属性名称
* @return
*/
String name();
/**
* Java 系统属性值
* @return
*/
String value();
}
/**
* 系统属性条件判断
*
* @author 小马哥
* @since 2018/5/15
*/
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);
}
}
ps: 实现Condition中的matches方法,可以根据自己的判断条件进行变更。我们这里使用的是取出注解上面的参数和我们系统中的数据进行比对。True则创建Bean,否则不创建Bean。
/**
* 系统属性条件引导类
*
* @author 小马哥
* @since 2018/5/15
*/
public class ConditionalOnSystemPropertyBootstrap {
// 当注解中的name和value与系统中的一样时,就会创建helloWorld这个bean
@Bean
@ConditionalOnSystemProperty(name = "user.name", value = "Mercy")
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();
}
}
Spring Boot自动装配
在Spring Boot场景下,基于规定大于配置的原则,实现Spring组件自动装配。
底层装配技术
- Spring模式注解装配(@Configuration)
- Spring @Enable 模块装配(@EnableAutoConfiguration)
- Spring 条件装配(@Conditional)
- Spring 工厂加载机制(SPI)
- 实现类:SpringFactoriesLoader
- 配置资源:META-INF/spring.factories
自定义自动装配:
首先需要定义一个xxxAutoConfiguration类,其次需要将其配置到META-INF/spring.factories中,最终在驱动类上标注@EnableAutoConfiguration
/**
* HelloWorld 自动装配
*
* @author 小马哥
* @since 2018/5/15
*/
@Configuration // Spring 模式注解装配
@EnableHelloWorld // Spring @Enable 模块装配
@ConditionalOnSystemProperty(name = "user.name", value = "Mercy") // 条件装配
public class HelloWorldAutoConfiguration {
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.imooc.diveinspringboot.configuration.HelloWorldAutoConfiguration
/**
* {@link EnableAutoConfiguration} 引导类
*
* @author 小马哥
* @since 2018/5/15
*/
@EnableAutoConfiguration
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();
}
}
启动流程
@EnableAutoConfiguration –> spring.factories –> conditional –> importselector –> configuration –> helloWorld的Bean
驱动类被@EnableAutoConfiguration注解,spring去spring.factories中寻找自动配置的类,找到之后查看我们是否符合conditional的条件,如果符合spring去选择符合条件的configuration类,根据我们的需要生成Bean。
总结:
通过了解spring boot的自动化配置,让我对springboot又多了一点的了解,不再迷茫为什么驱动类上要有自动配置注解,不再迷茫为什么@EnableXXX注解配置之后,我们有了那么多的功能。