前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot深度实践之自动装配

Spring Boot深度实践之自动装配

作者头像
yuanyi928
发布2020-06-24 10:39:40
7900
发布2020-06-24 10:39:40
举报
文章被收录于专栏:EAWorldEAWorld

前言:

在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,源码如下:

代码语言:javascript
复制
package com.example.demo;

import org.springframework.context.annotation.Bean;

public class TestConfiguration {

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

(左右查看全部代码)

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

代码语言:javascript
复制
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,源码如下:

代码语言:javascript
复制
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,源码如下:

代码语言:javascript
复制
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,源码如下:

代码语言:javascript
复制
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,源码如下:

代码语言:javascript
复制
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,源码如下:

代码语言:javascript
复制
# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.configuration.TestAutoConfiguration

(左右查看全部代码)

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

代码语言:javascript
复制
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的自动装配是一个很复杂的功能,本文只是粗略地讲述其过程,若有兴趣深入了解,仍需搜寻更多资料补充。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 EAWorld 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档