SpringBoot项目自定义一个 Starter

        在很多大厂中项目中,项目中大部分都是使用自己封装的架构,比如我目前所在的平安(外包),就是对Spring在进行了二次封装,它们把它称之为ark架构。比如我们常常需要用到的一些工具类或者可以共用的类,其它组里也需要用到,那我们就可以在使用统一框架的前提下倒入我们自己的starter,来达到一个自动化配置的效果。

SpringBoot介绍

        那我们就以目前流行的SpringBoot中举例,如何在项目中倒入自己自定义的一些jar包吧。首先在此之前,必须对SpringBoot有一定了解。它是基于Spring4的条件注册的一套快速开发整合包,同时又整合了Spring MVC了,所以说SpringMVC的那一套注解可以原封不动地搬来用。同时为了解决Spring框架需要进行大量的配置的问题又引入自动配置的概念,也就是说能用注解我绝不用配置文件。

        在构建自己的boot项目,需要在pom或者garden中引入自己的相关包

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

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

解释一下几个关键的包

  • spring-boot-starter-parent:项目可以通过继承spring-boot-starter-parent包来获得一些合理的默认配置,在在dependencies里的部分配置可以不用填写version信息,自动继承parent包的版本,当然也可以不用
  • spring-boot-starter:这是Spring Boot的核心启动器,包含了自动配置、日志和yaml/yml/properties
  • spring-boot-starter-web:构建Web,包含RESTful风格框架SpringMVC和默认的嵌入式容器Tomcat,就是这个包整合了Spring mvc,同时自带嵌入式tomcat意味着我们启动项目时就只要运行main方法就行,不用再跑eclipse上自带的tomcat了

在我们启动项目的时候,有个关键的注解@SpringBootApplication,control键然后点击该注解看源码可知它替代了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解的功能。接下来解释这个三个注解的作用,理解了这三个注解,自然理解了@SpringBootApplication。

  • @SpringBootConfiguration:该注解继承自@Configuration,一般与@Bean配合使用,使用这两个注解就可以创建一个简单的spring配置类,可以用来替代相应的xml配置文件,所以我们在一些配置文件中是如此使用的,比如我们要倒入kafka创建topic如此。
  • @EnableAutoConfiguration:该注解的意思就是Springboot可以根据你添加的jar包来配置你项目的默认配置,比如当你添加了mvc的jar包,它就会自动配置web项目所需的配置。
  • @ComponentScan:顾名思义该注解是用来扫描组件的,只要组件上有@component及其子注解@Service、@Repository、@Controller等,springboot会自动扫描到并纳入Spring 容器进行管理,有点类似xml文件中的<context:component-scan>,该注解不填属性的话就是默认扫描启动类所在的包,或者启动类所在包的下一级,所以启动类要放在最外层。比如说你想注入一个service类交给Spring容器管理,但是你这个类的路径不再项目启动类路径之下,是不会被扫描到的,自然就不会被加载到Spring容器。

yml文件引出自动配置

        在我们使用yml文件中,我们可以发现只要你引入了相应的jar包,比如mybatis,当我们在用yml对mybatis属性进行配置的时候,比如只要我们输入以“mybatis”前缀开头的内容,就会出现各种提示,如下。很奇怪,究竟是怎么做到的呢?

层层揭秘:

1. 进入我们的pom文件,左键点击我们引入的mybatis包,会发现里面引入了mybatis-spring-boot-autoconfigure这个包

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

2.然后打开mybatis-spring-boot-autoconfigure 包,在META-INF文件夹中找到spring.factories和spring-configuration-metadata.json。首先我们查看spring.factories文件,查看它后发现它引入的正是下面Java文件中的一个MybatisAutoConfiguration。有一定sprig基础的都知道在我们众多Spring jar包中,并不是该jar包下搜有的Java类都会引入spring ioc容器,而是根据我们的spring.factories的配置情况来自动引入对应的class文件,这也是Springboot加载过程中的一步,根据jar包下的meta-inf文件夹下的spring.factories配置文件,来自动引入对应的类。

3.当我们在打开 spring-configuration-metadata.json文件时则又会发现其实yml/properties文件中所有的提示都是来自于此元信息,我这里只截取部分,正好对应上面mybatis的提示(其实就是MybatisProperties类中的一个属性而已,它们把所有的配置类会生成json文件,而json文件的属性正是我们配置文件的各种属性)

4.通过上面我们大概就可以猜出来,就拿mybatis来说应该是引入一个jar包之后,Springboot  在启动时候,它会扫描启动类路径下所有的注解类和jar包下带有spring.factories文件所指向的自动配置类,当然并不是指向了对应类它就一定会加载到ioc容器中,这个的根据该类上面的条件注解,如下图(截取了常用的一些注解)。

MybatisAutoConfiguration源码阅读与发现

        既然我们要弄懂其究竟是如何加载进去的,我们则继续探究,我们这里就以mybatis jar包下的MybatisAutoConfiguration类作为入手,我们打开其源码发现。首先它是一个配置类,引入MybatisAutoConfiguration类到ioc容器的前提条件是容器Map<String,Class<?>>中已经加载了SqlSessionFactory和sqlSessionDactoryBean。其次是容器中必须包含DataSource的实例对象才会生效,其实说白了,你要写sql你不可能不把数据库的连接信息配置上吧。再其次是后面会去加载MybatisProperties这个类,而这个类的属性来自于yml/properties配置文件。再其次是要想让MybatisAutoConfiguration这个类生效必须是加载完DataSourceAutoConfiguration这个类才会。ok至此,我们已经知道了MybatisAutoConfiguration这个类生效的前提条件。 

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
    private final MybatisProperties properties;
    private final Interceptor[] interceptors;
    private final ResourceLoader resourceLoader;
    private final DatabaseIdProvider databaseIdProvider;
    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
    }

    @PostConstruct
    public void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
        }

    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        //省略  都是设置SqlSessionFactory的一些属性
        return factory.getObject();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

  //省略
}

         数据源的配置是通过类DataSourceAutoConfiguration.class,而在这个类生效的条件是通过@EnableConfigurationProperties(DataSourceProperties.class)这个注解就是去加载配置文件的一些配置,然后通过配置文件的属性赋予到DataSourceProperties这个类,而配置文件的一些属性啊,无非就是用户名、密码、数据库驱动、数据库连接池等等,从而达到自动配置的目的,那这些属性又是如何自动配置的。

DataSourceAutoConfiguration源码阅读与发现

        打开DataSourceAutoConfiguration这个类的源码发现,它默认是引入了5个数据源的,也就是DataSource.class 的实例对象。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@Conditional(EmbeddedDatabaseCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import(EmbeddedDataSourceConfiguration.class)
	protected static class EmbeddedDatabaseConfiguration {

	}

	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
			DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration {

	}
    //省略
}

        然后我们点击进入DataSourceConfiguration类来观察其关系,其中里面也写的很清楚,引入各类,当你在配置文件中配置的属性spring.datasource.type与value值是否相同,相同则去引入对应的数据库连接池,否则为默认的数据库连接池为HikariPool,至于为什么不是Generic,相比其它连接池子有何优势,另外再去说,这里先记住。

abstract class DataSourceConfiguration {

	@SuppressWarnings("unchecked")
	protected static <T> T createDataSource(DataSourceProperties properties, Class<? extends DataSource> type) {
		return (T) properties.initializeDataSourceBuilder().type(type).build();
	}

	/**
	 * Tomcat Pool DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource",
			matchIfMissing = true)
	static class Tomcat {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.tomcat")
		org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
			org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
					org.apache.tomcat.jdbc.pool.DataSource.class);
			DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
			String validationQuery = databaseDriver.getValidationQuery();
			if (validationQuery != null) {
				dataSource.setTestOnBorrow(true);
				dataSource.setValidationQuery(validationQuery);
			}
			return dataSource;
		}

	}

	/**
	 * Hikari DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
			matchIfMissing = true)
	static class Hikari {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		HikariDataSource dataSource(DataSourceProperties properties) {
			HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

	}

	/**
	 * DBCP DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(org.apache.commons.dbcp2.BasicDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.commons.dbcp2.BasicDataSource",
			matchIfMissing = true)
	static class Dbcp2 {

		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
		org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
			return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
		}

	}

	/**
	 * Generic DataSource configuration.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type")
	static class Generic {

		@Bean
		DataSource dataSource(DataSourceProperties properties) {
			return properties.initializeDataSourceBuilder().build();
		}

	}

}

SpringFactoriesLoader类部分源码:主要是项目启动过程中去加载资源到ioc容器中

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
            //在此路径下去加载 META-INF/spring.factories 所有的文件到ioc容器
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

终极奥义:自定义starter通用组件,供项目使用

        现在我们已经知道starter实际上就是一个集成集合器,主要完成两件事:1.引入相关jar   2.自动配置,那我们如何给我们的项目中使用一个自starter组件呢?这样以后在我们的项目中也可以是配置显得更加灵活和降低耦合,这其实也就是Springcloud的一些核心概念。那我们就来构建一个超级简单的demo starter组件。


1.构建一个简单的maven项目,我这里需要一个女朋友所以就创建了一个女朋友,而这个girl正是我们在项目启动时候注入到ioc容器中去的实例

package com.dn.study.girl;

public class Girl {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.构建这个女孩的properties,提供给配置文件来注入这个girl的属性,我这里以com.girl为前缀

package com.dn.study.girl;

import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "com.girl")
public class GirlProperties {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "GirlProperties{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3.构建项目自启动来实例话这个bean的配置类

package com.dn.study.girl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@EnableConfigurationProperties(GirlProperties.class)
public class GirlAutoConfigure {

    @Autowired
    private GirlProperties girlProperties;

    @Bean
    @ConditionalOnMissingBean(Girl.class)
    public Girl getGirl(){
        Girl girl = new Girl();
        girl.setAge(girlProperties.getAge());
        girl.setName(girlProperties.getName());
        return girl;
    }

}

4.配置发现配置文件,要提供给Spring发现注册

5.编译打成jar包

mvn install:install-file -Dfile=/Users/humingming/Desktop/myStarter/target/girl-spring-boot-starter.jar -DgroupId=com.lcloud -DartifactId=girl-spring-boot-starter -Dversion=1.0-SNAPSHOT -Dpackaging=jar

6.启动我们的Springboot项目中倒入我们的starter组件,然后我们就可以在yml配置文件中输入我们刚才输入的那个girl的配置信息了

        <!-- 倒入自定义的starter -->
        <dependency>
            <groupId>com.lcloud</groupId>
            <artifactId>girl-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

7. 启动项目,并观察girl是否被注入到容器内

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

    @Autowired
    private Girl girl;

    @GetMapping("test")
    public void fun(){
        System.out.println("注入的类" + girl);
    }

}

总结:

ok,satrter组件制作完成,这对后面的Springcloud组件的学习至关重要,keep on!

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot是一个快速开发微服务的框架,它提供了大量的自动化配置和快速开发的工具。在Spring Boot中,我们可以使用Starter来快速集成一些常用的功能,如数据库、缓存、web等。而自定义Starter则可以让我们将自己的功能快速集成到Spring Boot中,下面是自定义Starter的流程: 1. 创建Maven项目 首先,我们需要创建一个Maven项目作为我们自定义Starter项目。在项目的pom.xml中添加Spring Boot的依赖,以及其他需要集成的依赖。 2. 编写自动配置类 自动配置类是自定义Starter的核心,它负责将我们自定义的功能集成到Spring Boot中。在自动配置类中,我们可以使用@Conditional注解来判断是否需要进行配置。 3. 编写Starter类 Starter类是我们自定义Starter的入口,它负责将自动配置类注入到Spring容器中。在Starter类中,我们需要使用@EnableAutoConfiguration注解来启用自动配置。 4. 打包和发布Starter 当我们完成了自动配置类和Starter类的编写后,我们需要将自定义Starter打包成jar包,并发布到Maven仓库中,以便其他项目可以通过Maven依赖的方式使用我们的Starter。 5. 在项目中使用自定义Starter 在其他项目中使用自定义Starter非常简单,只需要在项目中的pom.xml中添加我们自定义Starter的依赖即可。Spring Boot会自动将我们的自定义Starter集成到项目中,并进行自动配置。 以上就是自定义Starter的流程,通过自定义Starter,我们可以将自己的功能快速集成到Spring Boot中,提高开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值