专栏首页EAWorldSpring Boot深度实践之自动装配

Spring Boot深度实践之自动装配

前言:

在Java服务端领域,Spring框架已是声名远扬,人们在使用其强大功能辅助开发的过程中,却也渐渐感受到随着项目规模的扩大,需要引入的Spring相关配置也越来越多,令人不胜其烦,而由Pivotal团队基于Spring框架推出的开源轻量级框架Spring Boot,就很好的解决了Spring时代项目配置繁琐的问题,至于Spring Boot是如何做到简化配置的,这就引出了我们今天的主题—Spring Boot自动装配。

目录:

1、什么是自动装配

2、Spring Boot自动装配之前世今生

  • Spring Framework手动装配
  • Spring Boot自动装配

3、Spring Boot自动装配实践

1.什么是自动装配

在机械制造工程中,机器装配的自动化已在多年前运用到实际的生产线上,例如,生产一台电动机,大量的零部件生产出来后,如果仍由手工装配,则劳动强度大、效率低、质量也不能保证,在数控装配机、自动装配线等工业技术问世后,大量零件自动装配成一台合格的机器已成为现实,毫无疑问,机械制造领域的自动装配大大地提升了生产力。

机械制造工程中的自动装配指的是零件直接自动组装成机器,同理,软件工程中的自动装配自然指的是软件模块之间的自动组装,最终成型一个完整的软件。

那么在SpringBoot框架中,仅通过少量代码,就实现了Spring框架各个组件的自动组装,一个完整的服务端项目便被轻松构建出来,这,就是SpringBoot的自动装配。

2.Spring Boot自动装配之前世今生

Spring Boot的自动装配源于Spring Framework的手动装配,在Spring Boot场景下,基于约定大于配置的原则,实现Spring组件自动装配的目的。其中使用了以下前三种Spring Framework手动装配技术:

底层装配技术

  • Spring 模式注解装配
  • Spring @Enable 模块装配
  • Spring 条件装配
  • Spring 工厂加载机制

所以要明白SpringBoot自动自动装配是如何实现的,就必须了解Spring Framework的手动装配。

Spring Framework是一个强大的开源轻量级应用开发框架,主要用于Java企业开发。相信做Java Web开发的同学对它应该比较熟悉,那么上述几种Spring Framework的装配方式,其具体的使用又是如何呢,下面我们会一一介绍到。

Spring Framework手动装配

Spring模式注解装配(Stereotype Annotations)

  • 模式注解定义

定义:一种用于声明在应用中扮演“组件”角色的注解。

怎么理解上述模式注解的定义呢,通俗的来讲,将一个Spring项目比作一座工厂,其创建的各种Java对象会被赋予不同的职责(即各种角色),这些Java对象就类似于流水线上的各类工人,各自负责一小段商品生产任务。各种流水线工人的职责可通过不同的工作服来标识,而在Spring Framework中,Java对象的职责则由模式注解来标识。

上图为Spring官方文档中对于模式注解(Stereotype Annotations)的说明,可以看到,@Component作为一种由Spring容器托管的通用模式组件,任何被@Component标识的组件均为组件扫描的候选对象,包括被@Component元标注的注解,当任何组件标识它时,也会被视作组件扫描的候选对象。以@Service为例,我们来看看它的源码:

可以看到,@Service确实被@Component标注了,所以被@Service注解的组件也将会被Spring容器扫描到。

  • 常用模式注解整理

将常用模式注解整理成下方表格:

  • 装配方式

装配方式分为两种:

  • <context:component-scan>方式
  • @ComponentScan方式

先来看看<context:component-scan>方式:

如上图所示,通过xml配置的方式,<context:annotation-config />激活注解驱动特性,<context:component-scan base-package=”com.example.demo” />指定扫描组件的根路径,将被@Component注解的类加载进Spring容器。

再来看看@ComponentScan方式:

只需要在主配置类上添加@ComponentScan(value="需要扫描的包名前缀")注解即可。

模式注解在基于Spring框架开发Web服务时是极常用的注解,相信做Java Web开发的同学一定深有体会。

当我们了解完Spring Framework的模式注解装配,接着来到第二部分@Enable模块装配。

Spring @Enable模块装配

  • Enable 模块定义

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

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

  • 常见@Enable注解模块整理

将常见@Enable注解模块整理成下方表格:

  • 装配方式

装配方式分为两种:

  • 注解驱动方式
  • 接口编程方式

先来看看注解驱动方式,我们以@EnableWebMvc注解为例,贴上其源码。

可以看到,@EnableWebMvc被@Import标注了,@Import的作用是将DelegatingWebMvcConfiguration装载进Spring容器,我们再进入到DelegatingWebMvcConfiguration中,发现其已被@Configuration标注,如下图所示:

看到@Configuration注解,就能知道,DelegatingWebMvcConfiguration将会被Spring容器扫描到并加载进容器,这种方式,就是Enable模块的注解驱动方式。

再来说说接口编程方式,这里我们以@EnableCaching为例,@EnableCaching的作用是激活Spring的缓存。贴上源码:

从源码可以看到,@Import引入的是CachingConfigurationSelector类,再进入到CachingConfigurationSelector类,下图为其源码:

这里我们就发现,CachingConfigurationSelector继承了AdviceModelImportSelector,而AdviceModelImportSelector是一个抽象类,其实现了ImportSelector接口,selectImports方法为ImportSelector接口的抽象方法,该方法的作用是获取需要加载进Spring容器的配置类。正如CachingConfigurationSelector是通过实现ImportSelector方法来选择相应的配置类导入进Spring容器,这就是第二种装配方式—接口编程方式。

相较于注解驱动方式,接口编程方式显得更为灵活,ImportSelector方法中可通过条件判断语句实现不同配置类的引入。

以上就是Enable模块装配的两种方式,接下来,我们来看看Spring的条件装配。

Spring 条件装配

  • 条件装配定义

定义:Bean装配的前置判断。

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

  • 实现方式

Spring提供的条件装配实现方式有两种

  • @Profile(配置化条件装配,Spring起始版本3.1)
  • @Conditional(编程条件装配,Spring起始版本4.0)

先来看看@Profile是如何实现条件装配的。

Spring中的@Profile与maven中的profile类似,能根据当前环境来选择性地向Spring容器注入相应的Bean。

请看以下示例:

创建一个SpringBoot项目,在项目下新建TestService接口,代码如下

新建Test1Service,Test2Service类,分别实现TestService接口

可以看到,Test1Service与Test2Service均被@Profile标注,但value不同,一个为test1,另一个为test2。

接着在启动类中设置profile参数,并从Spring容器中获取TestService的实现类对象,如下所示

此时我们启动该类,控制台打印如下所示:

说明此时Spring容器中TestService的实现类对象是Test1Service对象,当把profiles参数值改为test2时,启动Application,控制台便会打印Hello World---222,此处不再贴图。

上述示例标明了@Profile可做到条件装配Bean进Spring容器。

自Spring 4.0之后,@Profile的实现方式发生变动,其内部也是通过@Conditional注解来实现条件装配的;所以接下来,我们来看看如何使用@Conditional方式做到条件装配。

@Conditional方式实现,贴上源码

可以看到,它的value是一个Condition子类字节码对象,让我们再进到Condition中

发现存在matches方法,那么这个方法就是用来判断组件是否能被注册进Spring容器,如果返回true,则被@Conditional标注的组件可以被装载到Spring容器,反之,则无法注入。

我们来简单看一个具体的源码示例。

在Spring Boot中,有时需要控制配置类是否生效,可以使用@ConditionalOnProperty注解来控制@Configuration是否生效。

新建一个配置类,并标注上@Configuration和@ConditionalOnProperty

进入到@ConditionalOnProperty,发现其条件判断由OnPropertyCondition实现,源码如下:

这里我们忽略具体逻辑实现,只看其实现流程。再进入到OnPropertyCondition类中,贴上类图:

从图中能看到,OnPropertyCondition继承了SpringBootCondition,而SpringBootCondition实现了Condition,上文我们说到,判断配置类是否注入需要实现Condition中的matches方法,此时我们进入SpringBootCondition,发现matches方法已被其实现,如下图所示:

所以OnPropertyCondition作为子类也会继承这个方法实现,最终OnPropertyCondition作为@Conditional的判断条件,根据其内部的matches方法返回值判断组件是否能被注入。

以上就是Spring Framework手动装配的几种方式,限于篇幅,在这里只是粗略的做个介绍,有兴趣的同学可以去更深入的了解下。

说完了Spring Framework的手动装配,自然该说到基于Spring Framework手动装配的SpringBoot自动装配了。

Spring Boot自动装配

Spring Boot 自动装配定义

定义:基于约定大于配置的原则,实现Spring组件自动装配的目的。

在Spring Boot中,约定大于配置可以从以下两个方面来理解:

  1. 开发人员仅需规定应用中不符合约定的部分。
  2. 在没有规定配置的地方,采用默认配置,以力求最简配置为核心思想。简单来说,就是减少人为配置,尽量使用默认配置。这样可以大大减少配置工作,这就是所谓的“约定”。

Spring Boot自动装配的底层实现机制:模式注解,@Enable模块,条件装配,工厂加载机制,其中模式注解,@Enable模块,条件装配在上文已经提到,而工厂加载机制其实也是Spring Framework中的一个机制,我们来看看其是如何实现的。

Spring工厂加载机制

Spring Framework中有一个SpringFactoriesLoader类,它是实现工厂加载机制的核心。

他的主要作用是从META-INF/spring.factories文件中加载指定接口的实现类,该文件可能存在于工程类路径下或jar包内,所以会存在多个spring.factories文件。下图所示META-INF/spring.factories路径配置在其源码中。

SpringFactoriesLoader通过它的loadFactories方法从FACTORIES_RESOURCE_LOCATION文件中实例化指定类型的工厂实现类,且spring.factories文件必须采用Properties格式,我们来看看一个spring.factories文件内部是怎样的:

可以看到在该文件中,键是接口或抽象类的全路径名,值是逗号分隔的实现类列表。至于SpringFactoriesLoader具体是如何实例化这些实现类列表的,接下来我会以Spring Boot自动装配为例说明。

Spring手动装配和Spring工厂加载机制在Spring Boot自动装配中的应用

在我们开启Spring的自动装配功能时,会使用到@EnableAutoConfiguration这个注解,贴上其源码:

分析源码我们看到,@EnableAutoConfiguration被@Import标注,引入AutoConfigurationImportSelector类,此处就是用到了Spring的Enable模块装配,AutoConfigurationImportSelector类实现了ImportSelector接口,在Enable模块装配方式中我们提到通过实现ImportSelector接口的selectImports抽象方法来选择要注入Spring容器的配置类。在AutoConfigurationImportSelector类中,可以发现存在这样一个方法会被selectImports方法调用,该方法如下图:

可以看到方法内部调用了SpringFactoriesLoader的loadFactories方法。贴上loadFactories源码:

该方法会加载所有以factoryClass为key的实现类,它会调用loadFactoryNames方法,而loadFactoryNames方法又会调用loadSpringFactories方法,从spring.factories文件中获取到所有实现类的全路径名。下图为loadSpringFactories方法源码:

下图为spring.factories文件源码:

那么到这里,Spring Boot已经实例化了上图的EnableAutoConfiguration的配置实现类集合,我们以WebMvcAutoConfiguration为例,看看其内部做了什么,贴上源码:

发现其被@Configuration以及@ConditionalXXX标注,而@Configuration是Spring 模式注解中的一种,@ConditionalXXX则是Spring 条件装配。

所以从整个SpringBoot自动装配的流程来看,@EnableAutoConfiguration用于激活自动装配,它通过@Import注解加载EnableAutoConfiguration的实现配置类集合进Spring容器,而大量的EnableAutoConfiguration的实现配置类被注入Spring容器就意味着,Spring Framework的许多功能模块会被装配到Spring工程中,最终我们发现,只需要在启动类上标注@EnableAutoConfiguration,Spring的功能模块就会自动被装配进工厂,这就是SpringBoot的自动装配。

接下来,我们来看一个自定义实现Spring Boot自动装配的例子。

3.Spring Boot自动装配实践

整个过程分为三步:

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

具体步骤

新建一个SpringBoot项目。

自定义@Enable模块注解

1.在项目下新建一个配置类TestConfiguration,源码如下:

package com.example.demo;

import org.springframework.context.annotation.Bean;

public class TestConfiguration {

@Bean
public String test(){  //方法名即Bean名称
return "test";
    }
}

(左右查看全部代码)

2.新建文件TestImportSelector,代码如下:

package com.example.demo;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{TestConfiguration.class.getName()};
    }
}

(左右查看全部代码)

此文件中实现ImportSelector接口的selectImports方法,返回TestConfiguration名称。

3.新建文件EnableTest,源码如下:

package com.example.demo;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(TestImportSelector.class)
public @interface EnableTest {
}

(左右查看全部代码)

@EnableTest被@Import引入TestImportSelector类,此时,我们已经完成了自定义@Enable模块注解。

自定义@Conditional条件注解

1.新建文件ConditionalOnSystemProperty,源码如下:

package com.example.demo;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnSystemPropertyCondition.class)
public @interface ConditionalOnSystemProperty {
/**
     * 系统属性名
     * @return
     */
String name();

/**
     * 属性值
     * @return
     */
String value();
}

(左右查看全部代码)

2.新建文件OnSystemPropertyCondition,源码如下:

package com.example.demo;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

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);
    }
}

(左右查看全部代码)

此时我们就完成了自定义@Conditional条件注解,该注解会根据传入的name,获取系统属性,并匹配传入的value,如果相等,则返回true,表明条件满足,反之,则不满足。

自定义配置实现类

1.新建文件TestAutoConfiguration,源码如下:

package com.example.demo.configuration;

import com.example.demo.ConditionalOnSystemProperty;
import com.example.demo.EnableTest;
import org.springframework.context.annotation.Configuration;

@Configuration //Spring 模式注解
@EnableTest //Spring @Enable模块装配
@ConditionalOnSystemProperty(name = "user.name", value = "Alienware") //Spring 条件装配,value为你电脑的当前用户名
public class TestAutoConfiguration {
}

(左右查看全部代码)

此时,我们可以看到这个类中已经用到了Spring Framework的三种手动装配。

2.Resources目录下新建文件META-INF/spring.factories,源码如下:

# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.configuration.TestAutoConfiguration

(左右查看全部代码)

3.最后,我们在项目下新建启动类EnableAutoConfigurationApplication,源码如下:

package com.example.demo;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@EnableAutoConfiguration
public class EnableAutoConfigurationApplication {

public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationApplication.class)
                .web(WebApplicationType.NONE)
                .run(args);
//test Bean 是否存在
String testStr = context.getBean("test",String.class);

System.out.println("test Bean:" + testStr);

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

(左右查看全部代码)

到此,我们的自定义SpringBoot自动装配已经完成,启动EnableAutoConfigurationApplication,会发现控制台打印如下:

以上就是Spring Boot的自动装配实践,Spring Boot的自动装配是一个很复杂的功能,本文只是粗略地讲述其过程,若有兴趣深入了解,仍需搜寻更多资料补充。

本文分享自微信公众号 - EAWorld(eaworld),作者:拙言

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 详解Spring Boot 自动配置机制

    一、简介 Spring boot 是一个基于 Spring框架开发,高于 Spring 框架,它对 Spring 做了更好的封装,提供了更多的产品级特性,极大的...

    yuanyi928
  • 【详解】为什么选择Spring Boot作为微服务的入门级微框架(PPT)

    ? 1. Spring Boot是什么,解决哪些问题 1) Spring Boot使编码变简单 2) Spring Boot使配置变简单...

    yuanyi928
  • 对没有监控的微服务Say No!

    目录: 一、监控简介 二、监控策略 三、总结 一、监控简介 微服务的特点决定了功能模块的部署是分布式的,大部分功能模块都是运行在不同的机器上,彼此通过服务调用...

    yuanyi928
  • Spring源码解析——核心类介绍

      Spring用了这么久,虽然Spring的两大核心:IOC和AOP一直在用,但是始终没有搞懂Spring内部是怎么去实现的,于是决定撸一把Spring源码,...

    会说话的丶猫
  • 【续】java面试Spring必备知识点,怼死面试官,从我做起

    小海有话说 这篇文章是是上一篇文章Spring必备知识点的续文,还是那句话,如果感觉有用就收藏,我没有办法满足所有人的要求,我只能尽量满足大部分...

    用户1257393
  • 面试题:70道Spring面试题和答案

    Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标...

    搜云库技术团队
  • 【必收藏】面试常问最全spring面试题汇总(附带答案解析)

    Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标...

    框架师
  • SSHM(SPRING+STRUTS+MYBATIS+HIBERNATE)书籍介绍

    持久化——数据在程序实例之外留存的功能——是现代应用程序的核心。Hibernate是最流行的Java持久化工具,提供了自动且透明的对象/关系映射,使得在Java...

    用户5224393
  • Spring经典面试题和答案

    Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目...

    用户1257393
  • 69 个经典 Spring 面试题和答案

    Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标...

    哲洛不闹

扫码关注云+社区

领取腾讯云代金券