SpringBoot装配那些事

这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战

Spring Framework 3.1 开始支持”@Enable 模块驱动“。所谓“模块”是指具备相同领域的功能组件集合, 组合所形成一个独立的单元。比如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管 理扩展)模块、Async(异步处理)模块等。

手动装配

定义具备相同领域的功能组件集合,组合所形成的一个独立的单元

框架实现 @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 单点登录模块
SpringCloud @EnableEurekaServer Eureka服务器模块
@EnableConfigServer 配置服务器模块
@EnableFeignClients Feign客户端模块
@EnableZuulProxy 服务网关 Zuul 模块
@EnableCircuitBreaker 服务熔断模块

手动装配实现原理,以@EnableWebMvc为例

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
复制代码

该注解中的关键是使用了@Import注解,@Import是这样的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}
复制代码

java doc 给出这样一段注释:
Indicates one or more {@link Configuration @Configuration} classes to import.
大概意思就是:指示要导入的一个或多个{@link Configuration@Configuration}类。
可以大概的理解为向SpringIOC容器中注入一个或者多个配置类,就像我们下面这样

@Configuration
public class XConfigure {
    @Bean
    public xxx(){
        return new Xxx();
    }
}
复制代码

自定义@EnableXXX模块

基于注解驱动实现

  1. 创建一个需要被装载的类
public class HelloWorld {
    public HelloWorld(){
        System.out.println("hello world");
    }
}
复制代码
  1. 创建一个配置类,装载HelloWorld.
public class HelloWorldConfig {
    @Bean
    public HelloWorld helloWorld(){
        return new HelloWorld();
    }
}
复制代码
  1. 自定义模块驱动注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({HelloWorldConfig.class})
public @interface EnableHelloWorld {
}
复制代码
  1. 测试,在启动类/配置类上写@EnableHelloWorld装载该组件,启动SpringBoo项目,控制台打印helloworld,说明自定义注解生效,HelloWorld类被装载至SpringIOC容器中。

基于接口编程方式

  1. 新建配置类,并实现ImportSelector接口
public class HelloWorldSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{HelloWorld.class.getName()};
    }
}
复制代码
  1. 修改@EnableHelloWorld注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({HelloWorldSelector.class})
public @interface EnableHelloWorld {
}
复制代码
  1. 启动容器,控制台打印HelloWorld,说明接口生效,基于注解驱动实现HelloWorldImportSelector—>HelloWorldConfiguration—>HelloWorld

这样通过HelloWorldImportSelector间接的进行装配更加的灵活

条件装配

从Spring Framework3.1开始,允许Bean装配时增加前置条件判断

配置方式-@Profile

编程方式-@Condifional

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    /**
     * The classes that must be present. Since this annotation is parsed by loading class
     * bytecode, it is safe to specify classes here that may ultimately not be on the
     * classpath, only if this annotation is directly on the affected component and
     * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
     * use this annotation as a meta-annotation, only use the {@link #name} attribute.
     * @return the classes that must be present
     */
    Class<?>[] value() default {};
    /**
     * The classes names that must be present.
     * @return the class names that must be present.
     */
    String[] name() default {};
}
复制代码

自定义条件装配

eg:实现一个方法,java 7使用for循环的形式实现累加,java 8使用Lambda的形式实现累加。

基于配置方式实现-@Profile

  • 接口:
public interface CalculateService {

    /**
     * 从多个整数sum求和
     * @param values 多个整数
     * @return sum累加值
     */
    Integer sum(Integer ...values);
}
复制代码
  • java7实现
@Profile("Java7")
@Service
public class Java7CalculateServiceImpl implements CalculateService {
    @Override
    public Integer sum(Integer... values) {
        System.out.println("Java7 for 循环实现{@link CalculateService}");
        int sum=0;
        for (Integer value : values) {
            sum += value;
        }
        return sum;
    }
}
复制代码
  • java8实现
@Profile("Java8")
@Service
public class Java8CalculateServiceImpl implements CalculateService {
    @Override
    public Integer sum(Integer... values) {
        System.out.println("Java8 Lambda 循环实现{@link CalculateService}");
        return Stream.of(values).reduce(0,Integer::sum);
    }
}
复制代码
  • 启动类

    只扫描service包

@SpringBootApplication(scanBasePackages = "org.ywb.service")
public class CalculateServiceBootStrap {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootStrap.class)
                .web(WebApplicationType.NONE)
                .profiles("Java8")
                .run(args);

        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();
    }
}
复制代码

基于编程方式实现

场景:只有name的值符合要求才返回相应的Bean

  1. 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionOnSystemProperty {
    /**
     * Java 系统属性名称
     * @return
     */
    String name();

    /**
     * Java 系统属性名称
     * @return
     */
    String value();
}
复制代码
  1. 创建Condition类
public class OnSystemPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {

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

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

        return "happy".equals(propertyName);
    }
}
复制代码
  • 启动类
public class ConditionOnSystemPropertyBootstrap {

    @Bean
    @ConditionOnSystemProperty(name="happy",value = "balabala") // 只有name的值等于happy的时候,该“hello,world:-)”才会被装载
    public String helloWorld(){
        return "hello,world :-)";
    }

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

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

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

    }
}
复制代码

自动装配

SpringBoot自动装配依赖的技术

  • Spring模式注解装配
  • Spring @Enable模块装配 传送门
  • Spring 条件装配
  • Spring 工厂加载机制
    • 实现类: SpringFactoriesLoader
    • 配置资源: META-INF/spring.factories
  1. SpringFactoriesLoader部分源码
/**
 * Load and instantiate the factory implementations of the given type from
 * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
 * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
 * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
 * to obtain all registered factory names.
 * @param factoryClass the interface or abstract class representing the factory
 * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
 * @throws IllegalArgumentException if any factory implementation class cannot
 * be loaded or if an error occurs while instantiating any factory
 * @see #loadFactoryNames
 */
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
   Assert.notNull(factoryClass, "'factoryClass' must not be null");
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
   if (logger.isTraceEnabled()) {
      logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
   }
   List<T> result = new ArrayList<>(factoryNames.size());
   for (String factoryName : factoryNames) {
      result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
   }
   AnnotationAwareOrderComparator.sort(result);
   return result;
}
复制代码
  1. META-INF/spring.facotries
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
...
复制代码

可以看出,该方法通过实例化配置文件中的工厂对象,通过工厂对象创建Bean。完成自动化装配。

具体实现方式

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

自定义实现自动装配

  1. 创建一个要被装载的Bean
public class HelloWorld {
    public HelloWorld(){
        System.out.println("hello world :-)");
    }
}
复制代码
  1. 创建配置类
public class HelloWorldConfig {
    @Bean
    public HelloWorld helloWorld() {
        return new HelloWorld();
    }
}
复制代码
  1. 创建自定义模块装配注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({HelloWorldConfig.class})
public @interface EnableHelloWorld {
}
复制代码
  1. 创建条件装配注解,并定义条件装配规则
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionOnSystemProperty {
    String name();
    String value();
}
public class OnSystemPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnSystemProperty.class.getName());
        String propertyName = String.valueOf(attributes.get("name"));
        String propertyValue = String.valueOf(attributes.get("value"));
        return "happy".equals(propertyName);
    }
}
复制代码
  1. 创建自动装配类
@Configuration //Spring 模式装配
@EnableHelloWorld //Spring @Enable模块装配
@ConditionOnSystemProperty(name = "happy", value = "balabala") //条件装配
public class HelloWorldAutoConfiguration {
}
复制代码
  1. 在resources目录下创建META-INF文件夹并创建spring.factories文件

image.png
文件中添加自动装配类

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.HelloWorldAutoConfiguration
复制代码
  1. 启动SpringBoot,控制台打印HelloWorld构造方法内容
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享