掘金链接:

一  Spring boot 组件自动装配

      在Spring boot框架中,我们可以不必配置xml文件,就能完成Bean注入IOC的动作。这在之前的Spring是通过xml配置来说明要装配的组件,Spring boot为了简化开发人员的配置工作,遵循"约定大于配置,配置大于编码"原则,

通过约定来实现IOC的注入,通过默认配置来代替常规的配置,从而简化了开发人员的配置和开发过程,提升研发效率。

二 Spring boot 如何实现自动装配

      整体流程是@EnableAutoConfiguration中的实现类扫描“classpath下即Pom依赖”中所有包含/META-INF/spring.factories的包,并根据其xxxAutoConfiguration和/META-INF/spring-autoconfigure-metadata.properties中的条件判断来决定是否进行自动装配。

主类设置如下

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
@EnableScheduling
@EnableConfigurationProperties({GlobalConfig.class})
public class ProxyApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }
}

我们知道,注解SpringBootApplication是一系列注解的组合,其中包含@EnableAutoConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM,
				classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

而根据@Enable模块驱动设计模式即Import的类被@Configuration注解或实现ImportSelector接口,可实现注入),@EnableAutoConfiguration必然Import了“实现ImportSelector接口的实现类”,并实现其selectImport方法

// 1. Import了AutoConfigurationImportSelector类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)  
public @interface EnableAutoConfiguration {
}

//2. 实现了DeferredImportSelector接口,并重写selectImports方法
public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {
}


//3. 继承ImportSelector接口
public interface DeferredImportSelector extends ImportSelector {}

基于@Enable和selectImports装配的方式我们知道,通过selectImports方法返回需要装配的对象数组,整个搜索流程如下:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);//1. 获取classpath下自动加载配置的元数据
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

    protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);//2.获取注解的属性信息
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);//3. 获取classpath下所有enable的候装配组件
		configurations = removeDuplicates(configurations); //4. 组件去重
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);//5. exclude去重
		checkExcludedClasses(configurations, exclusions);//6. 组件去exclude
		configurations.removeAll(exclusions);//6. 组件去exclude
		configurations = filter(configurations, autoConfigurationMetadata);//7. 组件过滤
		fireAutoConfigurationImportEvents(configurations, exclusions);//8. 触发自动装配监听
		return new AutoConfigurationEntry(configurations, exclusions);
	}

整体流程:

1. 获取自动加载配置的元数据:主要是获取classpath下所有META-INF/spring-autoconfigure-metadata.properties中的配置信息,该文件里面配置的作用等同于在xxxAutoConfiguration上面的@ConditionalOnClass等注解,这么做的好处就是参考官网说明(Spring Boot uses an annotation processor to collect the conditions on auto-configurations in a metadata file (META-INF/spring-autoconfigure-metadata.properties). If that file is present, it is used to eagerly filter auto-configurations that do not match, which will improve startup time.)总而言之,加速启动时间。

2.获取注解的属性信息:这里的注解是指在主类上的注解,所以获取出来的属性信息如图所示:

spring boot 登录多次锁定 springboot lock_spring boot自动配置

3. 获取classpath下所有enable的候装配组件:获取的是META-INF/spring.factories中key为EnableAutoConfiguration的Value值的集合,此处META-INF/spring.factories可以是多个,EnableAutoConfiguration也可以是多个,此处原始数据DEBUG为118个(在spring.factories中定义的是)

spring boot 登录多次锁定 springboot lock_maven_02

4.5.6去重(此处无重复),去exclude(共2个)后,剩余116个

spring boot 登录多次锁定 springboot lock_自动装配_03

7.组件过滤:过滤对象是configurations,过滤条件是1中获取的autoConfigurationMetadata,过滤后,可被自动装配对象变为35个,比对删除的configurations,此处可以猜到autoConfigurationMetadata作用是配合configurations去除不符合条件的(比如当前classloader下不存在的class)configurations,同时也表明META-INF/spring-autoconfigure-metadata.properties中的Key-Value是对META-INF/spring.factories定义自动配置条件的提取。

spring boot 登录多次锁定 springboot lock_spring_04

8. 触发自动装配监听

至此,selectImports流程结束,返回需要加载对象,@Enable驱动模块完成bean的装配

三 如何自定义starter?

完成自定义starter需要包含下面三个因素:

  • 主类激活@EnableAutoConfiguration注解
  • 组件配置/META-INF/spring.factories(约定,是否将组件下某个class加入自动装配管理)
  • 实现xxxAutoConfiguration组件配置类(符合什么条件才加入自动装配)

 那么,如何自定义一个自动装配组件呢?首先,我们先要创建个普通的maven工程,并且采用{name}-spring-boot-starter 的命名风格[自定义第三方均采取此风格],也就是我们平常常说的starter启动器,引入maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zyq</groupId>
    <artifactId>arthorn-spring-boot-starter</artifactId>
    <version>0.1.2.Release</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.1.4.RELEASE</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

项目的工程目录如图所示:

spring boot 登录多次锁定 springboot lock_maven_05

1. 在resources/META-INF/spring.factories下面添加配置类ArthornAutoconfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zyq.ArthornAutoconfiguration

2. ArthornAutoconfiguration来决定组件装配的条件;

   在此处决定是否装载ArthornService.class,比如,此处只有当classpath下存在ArthornService.class,且不存在该Bean,且存在属性arthorn.enabled=true三个条件时,才装配ArthornService.class组件.

@Configuration
@EnableConfigurationProperties(ArthornProperties.class)
@ConditionalOnClass(ArthornService.class)
public class ArthornAutoconfiguration {

    @Autowired
    private ArthornProperties arthornProperties;

    @Bean
    @ConditionalOnMissingBean(ArthornService.class)
    @ConditionalOnProperty(name = "enabled",prefix = "arthorn", havingValue = "true")
    public ArthornService arthornService(){
        ArthornService arthornService = new ArthornService(arthornProperties);
        return arthornService;
    }
    
}

  ArthornService是我们要装配的业务组件,主要是组件的业务逻辑

public class ArthornService {

    private ArthornProperties arthornProperties;

    public ArthornService(ArthornProperties arthornProperties){
        this.arthornProperties = arthornProperties;
    }

    public String kill(String blood){
        if  ("first".equals(blood))
            return "Death is like the wind; always by my side";
        else if ("second".equals(blood))
            return "A sword's poor company for a long road.";
        else
            return "My honor left a long time ago.";
    }

}

ArthornProperties用于接收在application.properties配置的属性

/**
 * @author zyq
 * @description 亚索属性配置
 */
@ConfigurationProperties(prefix = "arthorn")
public class ArthornProperties {
    private String killNum;

    public String getKillNum() {
        return killNum;
    }

    public void setKillNum(String killNum) {
        this.killNum = killNum;
    }
}



执行maven install安装该组件到本地repository,完成starter启动器的制作,IDEA操作如图

spring boot 登录多次锁定 springboot lock_maven_06

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

接下来,就是在我们的Spring Boot项目中引入该依赖,并完成自动装配,新建Spring Boot项目testdemo,并引入我们的arthorn-spring-boot-starter依赖


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>testdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>testdemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.zyq</groupId>
            <artifactId>arthorn-spring-boot-starter</artifactId>
            <version>0.1.2.Release</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

在application.properties文件中添加属性配置


arthorn.blood=first

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

任意写一个接口,调用自动配置的类


@RestController
@RequestMapping("/test")
public class testController {

    @Autowired
    ArthornService arthornService;

    @GetMapping("/{blood}")
    public String test(@PathVariable String blood){
        return arthornService.kill(blood);
    }
}

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

启动报错,什么鬼?

spring boot 登录多次锁定 springboot lock_maven_11

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

原来是我们之前配置的条件注解,调用该组件需要在配置属性中添加arthorn.enabled=true,修改后启动成功,调用接口

spring boot 登录多次锁定 springboot lock_spring_13

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07  

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_15

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

SUCCESS!面对疾风吧

四 进阶:JPA的starter实现原理

 接下来,我们实例化一下通用组件JPA是如何在Spring Boot中完成自动装配的。我们不讨论Jpa的源码,只对实现自动装配的几个元素进行研究

  • spring-boot-starter-data-jpa的starter启动器结构
  • spring-boot-starter-data-jpa的xxxAutoConfiguration

随便创建一个Spring Boot项目,并引入jpa依赖

spring boot 登录多次锁定 springboot lock_自动装配_17

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

首先,来看下整个项目的maven依赖和Jpa的依赖,可以看到spring-boot-starter-data-jpa本身依赖于spring-data-jpa(注意区别,真正的服务实体在这个jar包里面,spring-boot-starter-data-jpa只是作为一个启动依赖),hibernate依赖,aop依赖等。

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_19

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

到maven仓库解压这个jar包,会发现里面有效信息只有一个pom,我们这里可以把spring-boot-starter-data-jpa看成是依赖多个jar包的空组件,它的作用主要是提供spring boot jpa自动注册需要的依赖。我们看到所有的官方自动配置组件的starter启动器都在该目录下

spring boot 登录多次锁定 springboot lock_自动装配_21

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

接下来,我们看下在IDEA双击shift,输入spring.factories来查看类路径下面所有的spring.factories文件,发现只有spring-boot-autoconfigure下面的spring.factories文件存在XXXEnableAutoConfiguration类,找到其中Jpa相关的XXXEnableAutoConfiguration,

spring boot 登录多次锁定 springboot lock_maven_23

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

该类的路径存在于spring-boot-starter-data-jpa的路径下,所有官方配置组件的XXXEnableAutoConfiguration类都在该路径下

spring boot 登录多次锁定 springboot lock_spring boot自动配置_25

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

spring boot 登录多次锁定 springboot lock_maven_27

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

其中,spring-boot-starter-data-jpa的路径下只存在对组件的自动注册类实现等,看一下具体的JpaRepositoriesAutoConfiguration


/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.data.jpa;

import java.util.Map;

import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's JPA Repositories.
 * <p>
 * Activates when there is a bean of type {@link javax.sql.DataSource} configured in the
 * context, the Spring Data JPA
 * {@link org.springframework.data.jpa.repository.JpaRepository} type is on the classpath,
 * and there is no other, existing
 * {@link org.springframework.data.jpa.repository.JpaRepository} configured.
 * <p>
 * Once in effect, the auto-configuration is the equivalent of enabling JPA repositories
 * using the {@link org.springframework.data.jpa.repository.config.EnableJpaRepositories}
 * annotation.
 * <p>
 * This configuration class will activate <em>after</em> the Hibernate auto-configuration.
 *
 * @author Phillip Webb
 * @author Josh Long
 * @see EnableJpaRepositories
 */
@Configuration
@ConditionalOnBean(DataSource.class)
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class,
		JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled",
		havingValue = "true", matchIfMissing = true)
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class,
		TaskExecutionAutoConfiguration.class })
public class JpaRepositoriesAutoConfiguration {

	@Bean
	@Conditional(BootstrapExecutorCondition.class)
	public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer(
			Map<String, AsyncTaskExecutor> taskExecutors) {
		return (builder) -> {
			AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(
					taskExecutors);
			if (bootstrapExecutor != null) {
				builder.setBootstrapExecutor(bootstrapExecutor);
			}
		};
	}

	//省略
}

spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07

可以看到,只有满足上面定义的条件(@ConditionalOnBean等注解的含义请自行百度)才会自动装配,装配的时候采取调用原始的spring-data-jpa的jar包。这样设计既不侵入spring-data-jpa等原生组件的逻辑,又完成了spring-boot的自动装配。所以,这符合哪种设计模式呢?~~~~~


spring boot 登录多次锁定 springboot lock_spring boot 登录多次锁定_07