Spring Boot 2.0之走向自动装配 - Go语言中文社区

Spring Boot 2.0之走向自动装配


Spring 模式注解装配

模式注解

模式注解是一种用于声明在应用中扮演“组件”角色的注解。如 Spring Framework 中的 @Repository 标注在任何类上 ,用于扮演仓储角色的模式注解。
@Component 作为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标注的组件均为组件扫描的候选对象。类似地,凡是被 @Component 元标注(meta-annotated)的注解,如 @Service ,当任何组件标注它时,也被视作组件扫
描的候选对象

模式注解举例

Spring Framework 注解 场景说明 起始版本
@Repository 数据仓储模式注解 2.0
@Component 通用组件模式注解 2.5
@Service 服务模式注解 2.5
@Controller Web 控制器模式注解 25
@Configuration 配置类模式注解 3.0

以上其余四个注解,都标注了@Component

自定义模式注解

@Component “派生性

/**
 * 一级 {@link Repository @Repository}
 */

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {
    String value() default "";
}
  • @Component

    • @Repository

      • @FirstLevelRepository

使用自定义注解@FirstLevelRepository的类也会被注册到容器中去

/**
 * {@link FirstLevelRepository}
 */

@FirstLevelRepository(value = "myFirstLevelRepository")
public class MyFirstLevelRepository {
}

@Component “层次性”

/**
 * 二级 {@link FirstLevelRepository}
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@FirstLevelRepository
public @interface SecondLevelRepository {

    String value() default "";
}
  • @Component

    • @Repository

      • @FirstLevelRepository

        • @SecondLevelRepository

使用自定义注解@SecondLevelRepository的类也会被注册到容器中去

/**
 * {{@link SecondLevelRepository}}
 * Created by Yuk on 2019/3/9.
 */

@SecondLevelRepository(value = "mySecondLevelRepository")
public class MySecondLevelRepository {
}

装配方式

需要有一种配置方式来发现这些被模式注解标注的类

xml配置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/springcontext.xsd">
<!-- 激活注解驱动特性 -->
<context:annotation-config />
<!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
<context:component-scan base-package="com.yk.dive.in.spring.boot" />
</beans>

@ComponentScan 方式

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

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模块
- @EnableAspectAutoProxy AspectJ代理模块

实现方式

Spring Framework的@Enable具是通过@Import引入来实现的,具体可以看我之前写的一篇spring源码spring加载流程之ConfigurationClassPostProcessor其中有处理@Import注解的详细讲解

注解驱动方式

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@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> {
	public String[] selectImports(AdviceMode adviceMode) {
			switch (adviceMode) {
				case PROXY:
					return getProxyImports();
				case ASPECTJ:
					return getAspectJImports();
				default:
					return null;
			}
	}
}

其中AdviceModeImportSelector实现了ImportSelector接口,所以要实现selectImports方法

自定义 @Enable 模块

基于注解驱动实现

/**
 * HelloWorld 配置
 */
@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String helloWorld(){
        return "hello world 2018";
    }
}

自定义的配置不能再@ComponentScan的扫描范围内,否则就毫无意义了

/**
 * 激活 HelloWorld 模块
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}

在启动配置类加上@EnableHelloWorld注解,就可以注入helloWorld

基于接口驱动实现

需要注入的Bean

public class HelloWorld {
}
/**
 * HelloWorld {@link ImportSelector}
 */
public class HelloWorldImportSelector implements ImportSelector{

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 根据importingClassMetadata条件,可自定义实现返回

        return new String[]{HelloWorld.class.getName()};
    }
}
/**
 * 激活 HelloWorld 模块
 *
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}

在启动配置类加上@EnableHelloWorld注解,就可以注入helloWorld

Spring 条件装配

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

条件注解举例

Spring 注解 场景说明 起始版本
@Profile 配置化条件装配 3.1
@Conditional 编程条件装配 4.0

基于配置方式实现 - @Profile

计算服务,多整数求和 sum
@Profile(“Java7”) : for 循环
@Profile(“Java8”) : Lambda

/**
 * 计算服务
 */
public interface CalculateService {

    /**
     * 多个整数求和
     * @param values
     * @return sum 累加值
     */
    public Integer sum(Integer... values);
}
/**
 * java7 for循环实现 {@link CalculateService}
 */
@Profile("java7")
@Service
public class Java7CalculateService implements CalculateService{

    @Override
    public Integer sum(Integer... values) {
        System.out.println("java7 for循环实现");
        int sum = 0;
        for (int i = 0; i < values.length; i++){
            sum += values[i];
        }
        return sum;
    }

}
/**
 * java8 lambda表达式实现 {@link CalculateService}
 */
@Profile("java8")
@Service
public class Java8CalculateService implements CalculateService{

    @Override
    public Integer sum(Integer... values) {
        System.out.println("java8 lambda表达式实现");
        int sum = Stream.of(values).reduce(0,Integer::sum);
        return sum;
    }
}
@SpringBootApplication(scanBasePackages = {"com.imooc.diveinspringboot.service"})
public class DiveInSpringBootApplication {

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

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

		System.out.println("CalculateService.sum(1...10):"+
				service.sum(1,2,3,4,5,6,7,8,9,10));

		context.close();

	}
}

在启动时没有配置.profiles(),直接报错No qualifying bean of type 'com.imooc.diveinspringboot.service.CalculateService' available
那么加上.profiles("java7"),就会装载Java7CalculateService到容器
在这里插入图片描述

基于实现接口的编程方式实现 - @Conditional

自定义注解@ConditionalOnSystemProperty@Conditional就是判断配置是否符合条件

/**
 * 系统属性条件判断 {@link OnSystemPropertyConditional}
 *
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnSystemPropertyConditional.class)
public @interface ConditionalOnSystemProperty {

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

    /**
     * Java 系统属性值
     * @return
     */
    String value();

}

实现接口Condition


/**
 * 实现Condition
 * 实现方法matches返回true或false
 */
public class OnSystemPropertyConditional implements Condition{

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

        // 获取注解的属性
        Map<String, Object> map = metadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());

        String propertyName = (String) map.get("name");
        String propertyValue= (String) map.get("value");

        String javaPropertyValue = System.getProperty(propertyName);

        System.out.println("javaPropertyValue:"+javaPropertyValue);
        System.out.println("propertyValue:"+propertyValue);

        return propertyValue.equals(javaPropertyValue);
    }
}
/**
 * 判断引导类 {@link ConditionalOnSystemProperty}
 *
 * Created by Yuk on 2018/12/2.
 */

public class ConditionalOnSystemPropertyBootstrap {

    @Bean
    @ConditionalOnSystemProperty(name = "user.name",value = "yk")
    public String helloWorld(){
        return "hello world.";
    }

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

        String helloWorld = context.getBean("helloWorld",String.class);
        System.out.println(helloWorld);
        context.close();
    }
}

user.name在系统中是电脑名称,我的user是“浴缸”,而我传的value是“yk”,matches方法肯定返回false,也就不会注入helloWorld,直接报错:
No bean named 'helloWorld' available
在这里插入图片描述
修改一下配置

@Bean
@ConditionalOnSystemProperty(name = "user.name",value = "浴缸")
 public String helloWorld(){
     return "hello world.";
 }

在这里插入图片描述

Spring Boot 自动装配

在 Spring Boot 场景下,基于约定大于配置的原则,实现 Spring 组件自动装配的目的。其中使用了

底层装配技术

  • Spring 模式注解装配

  • Spring @Enable 模块装配

  • Spring 条件装配

  • Spring 工厂加载机制

    • 实现类: SpringFactoriesLoader
    • 配置资源: META-INF/spring.factories

@Enable 注解模块举例

框架实现 @Enable 注解模块 激活模块
Spring Boot @EnableAutoConfiguration 自动装配模块
- @EnableAutoConfiguration Actuator 管理模块
- @EnableManagementContext Caching 模块
- @EnableConfigurationProperties 配置属性绑定模块
- @EnableOAuth2Sso OAuth2 单点登录模块

自动装配举例

参考 META-INF/spring.factories
在这里插入图片描述
spring boot启动时,SpringFactoriesLoader的loadFactories方法会找到所有的 META-INF/spring.factories,并加载里面的配置,是以key-value的形式配置了需要装配的类。

实现自动装配方法

  1. 激活自动装配 - @EnableAutoConfiguration
  2. 实现自动装配 - XXXAutoConfiguration
  3. 配置自动装配实现 - META-INF/spring.factories

自定义自动装配

  • 激活自动装配
    需要注意的是,@SpringBootApplication包含了@EnableAutoConfiguration
/**
 * 自动装配引导类 {@link HelloWorldAutoConfiguration}
 */
@EnableAutoConfiguration // 激活自动装配
public class EnableAutoConfigurationBootstrap {

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

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

        context.close();
    }
}
  • 实现自动装配
    @EnableHelloWorld 会注入HelloWorld,按照上面@Enable的接口编程方式
/**
 * 实现自动装配
 *
 * Created by Yuk on 2018/12/2.
 */
@Configuration // Spring 模式注解
@EnableHelloWorld // Spring @Enable模块装配
@ConditionalOnSystemProperty(name = "user.name",value = "浴缸") // 条件装装配
public class HelloWorldAutoConfiguration {


}
  • 配置自动装配实现
    在这里插入图片描述
# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.imooc.diveinspringboot.configuration.HelloWorldAutoConfiguration

HelloWorldAutoConfiguration

  • 模式注解: @Configuration
  • @Enable 模块: @EnableHelloWorld -> HelloWorldImportSelector -> -HelloWorldImportSelector- > helloWorld
  • 条件判断: @ConditionalOnSystemProperty

自动装配测试

1.去掉模式注解: @Configuration,结果运行正常

//@Configuration // Spring 模式注解
@EnableHelloWorld // Spring @Enable模块装配
@ConditionalOnSystemProperty(name = "user.name",value = "浴缸") // 条件装装配

2.修改条件判断

@ConditionalOnSystemProperty(name = "user.name",value = "yk") // 条件装装配

报错: No qualifying bean of type 'com.imooc.diveinspringboot.configuration.HelloWorld' available
在这里插入图片描述
3.去掉条件判断,也是运行正常的

//@Configuration // Spring 模式注解
@EnableHelloWorld // Spring @Enable模块装配
//@ConditionalOnSystemProperty(name = "user.name",value = "浴缸") // 条件装装配

手动装配与自动装配

Spring Framework的三种手动装配

  1. Spring 模式注解装配
  2. Spring @Enable 模块装配
  3. Spring 条件装配

spring boot自动装配

spring boot自动装配不是一种新特性,而是依赖于Spring Framework的三种手动装配,可以灵活组合使用

自动装配的好处

1.减少各种@Enable的配置,避免在代码中使用过多的@Enable
2.通过修改配置文件META-INF/spring.factories来实现插拔式注册,并不需要重新编译

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yu_kang/article/details/88364354
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢