springBoot的核心功能是为集成第三方框架提供自动配置,本文带你实现自己的自动配置和Starter。一旦你真正掌握了本文的内容,你就会明白SpringBoot营造出的“一览众山小”的感觉。
在springboot中,所有自定义条件注解实际上都是基于@Conditional,使用@Conditiona l定义新的条件注释键 必须有Condition实现类,负责条件注解的处理逻辑。该实现类实现的matches()方法判断是否满足条件注释的要求。
@Conditiona
@Conditiona l定义新的条件注释键 必须有Condition实现类,负责条件注解的处理逻辑。该实现类实现的matches()方法判断是否满足条件注释的要求。
matches()
以下是自定义条件注解的Condition实现类的代码。
src/main/java/com/example/_003configtest/condition/www.sychzs.cn
package com.example._003configtest.condition;import com.example._003configtest.annotation.ConditionalCustom;importorg.spring框架.上下文.注释.条件;导入 org.springframework.context.annotation.ConditionContext;导入 org.springframework.core.env.Environment;导入 org.springframe work.core.type.AnnotatedTypeMetadata; 导入 www.sychzs.cn;公共 类 MyCondition 实现 条件 { @Override 公共 布尔值 匹配(ConditionContext上下文,AnnotatedTypeMetadata元数据) { //获取骚自定义注解的全部属性,其中ConditionalCustom是自定义的注解 MapannotationAttributes = metadata.getAnnotationAttributes(ConditionalCustom.班级. getName()); //获取注解的值属性值 String[] vals = (String[]) annotationAt三butes.get("value"); //env是application.properties或application.yml中配置的属性 Environ ment env = context.getEnvironment(); // 遍历每个值的各个属性值 //如果某个属性值对应的配置属性不存在则返回 false if(env .getProperty(val.toString())== null){ return false ; true; }}
从上面的逻辑可以看到,自定义条件注释的处理逻辑比较简单:它要求所有value 属性指定的配置属性必须存在。这些配置属性的值是什么并不重要,这些配置属性是否有值也不重要。
有了上面的Condition实现类,接下来就可以基于@Conditional定义自定义条件注解。下面是自定义条件注释的代码。
@Conditional
src/main/java/com/example/_003configtest/annotation/www.sychzs.cn
package com.example._003configtest.annotation;导入 com.example._003configtest.condition.MyCondition;导入 org.spring框架.上下文.注释.条件;导入 java.lang.annotation.*;@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolic y.RUNTIME) ) @Documented//只要通过@Conditional指定Condition实现类,Condition实现类就会负责条件注解的判断逻辑@Conditional(MyCondition.类) 公共 @接口 条件自定义 { String[] value() default {}; }
以下配置类演示了如何使用此自定义条件注释:
src/main/java/com/example/_003configtest/config/www.sychzs.cn
// proxyBeanMethods = true : 单例模式,保证每个@Bean方法被调用多少次返回的组件是相同的 // proxyBeanMethods = false : 原型模式,每个@Bean方法被调用多少次这次返回的组件都是新创建的 @Configuration(proxyBeanMethods = true)public 类MyConfigTest{ @Bean //只有当application.properties或application.yml中都存在配置属性org.test1和org.test2时才生效 @Conditional自定义({"org.test1","org.test2"})公共 MyBean myBean( ){ 返回 新 MyBean(); }}
在application.properties文件中添加以下配置:
application.properties
org.test1 = 1org.test2 = 2
运行测试发现容器中对应的类成功获取:
开发自己的自动配置非常简单。其实只需要两步:
使用@Configuration和条件注释来定义自动配置类。
@Configuration
在META-INF/spring.factories文件中注册自动配置类。
META-INF/spring.factories
为了清楚地演示Spring Boot自动配置的效果,避免引入第三方框架带来的额外复杂性,本示例首先开发了一个搞笑的框架。该框架的作用是将程序的输出信息保存在文件或数据库中。
新建一个Maven项目滑稽(注意不要使用SpringInitializr来创建项目),添加MySQL-connector-java 和 slf4j- api两个依赖项。由于本项目是我们自己开发的框架,所以不需要在项目中添加任何Spring Boot依赖。
SpringInitializr
MySQL-connector-java 和 slf4j- api两个依赖项。由于本项目是我们自己开发的框架,所以不需要在项目中添加任何Spring Boot依赖。
slf4j- api
以下是项目的pom.xml文件代码:
<项目 xmlns=“http://www.sychzs.cn/POM/4.0.0” xmlns:xsi=“http://www.sychzs.cn/2001/XMLSchema-instance” xsi:schemaLocation=“http://www.sychzs.cn/POM/4.0.0 http://www.sychzs.cn/xsd/maven-4.0.0.xsd"> <模型版本>4.0.0 型号版本> <groupId>org.examplegroupId> artifactId>搞笑artifactId> <版本>1.0-SNAPSHOT版本> <属性> <maven.compiler.source >8maven.compiler.source> <www.sychzs.cn>8www.sychzs.cn > <www.sychzs.cn。 sourceEncoding>UTF-8project.build.sourceEncoding> 属性> <依赖项> <依赖> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <版本>8.0.27版本> 依赖> <依赖> <groupId>org.slf4jgroupId> <artifactId>slf4j- apiartifactId> <版本>1.7.36版本> 依赖> 依赖项>项目>
后续为这个框架项目开发如下类。
src/main/java/io/www.sychzs.cn
包 io;导入 org.slf4j.Logger;导入 org.slf4j.LoggerFactory;导入 java.io.File;导入 java.io.FileNotFoundException;导入 java.io.IOException;导入java.io.RandomAccessFile;导入java.nio.charset。字符集;导入java.sql.Connection;导入java.sql.ResultSet;导入java.sql.SQLException;导入java。 util.Objects;导入 javax.sql.DataSource;public class WriterTemplate { 记录器 log = LoggerFactory。 getLogger(this.getClass()); private final数据源dataSource; 私有连接conn; 私人文件目的地; 私人 最终 字符集 字符集; 私有 RandomAccessFile raf; publicWriterTemplate(数据源数据源)抛出SQLException{ 这个.dataSource = 数据源; 这个 .dest = null; 这个。字符集 = null; if(Objects.nonNull(this) .dataSource)){ log.debug("== ======获取数据库连接========"); this.conn = dataSource.getConnection(); } } public WriterTemplate(文件目标,字符集字符集) 抛出 FileNotFoundException{ 这个。 dest = dest; 这个.charset = 字符集; 这个.dataSource = null; 这个 .raf = newRandomAccessFile(这个.dest,"rw"); } 公共 void write(字符串消息) 抛出IOException,SQLException{ if(Objects.nonNull()这个.conn)){? ,"搞笑消息",null); if(!www.sychzs.cn()){ log .debug(" ~~~~~~~~~创建funny_message表~~~~~~~~~"); conn.createStatement().execute("创建funny_message表" + "(id int 主键 auto_increment,message_text text)"); } log.debug( "~~~~~~~~ ~输出到数据表~ ~~~~~~~~”); "插入"+u"Funny_Message值(null, '"+Message+"')"); ? raf .seek(这个.dest.length());raf.write((message + "n").getBytes(这个.charset)); } } //关闭资源 公共 void 关闭() 抛出 SQLException,IOException{ if (这个.conn != null){ 这个.conn.close(); } if(这个.raf != null){ 这个.raf.close(); } }}
该工具类根据确定的数据源来决定输出目标:如果为该工具类确定了数据源,就会向该数据源所连接的数据库中的 unny_message表输出内容(如果该表不存在,该工具类将自动建表);如果没有为该工具类创建数据源,它就会向指定文件输出内容。
unny_message
接下来使用install资源到maven仓库:
有了该框架之后,接下来开始为该框架开发自动配置。如果为整合现有的第三方框架开发自动配置,则可以直接从这一步开始(因为框架已经存在了,直接为框架开发自动配置)配置即可)。
funny-spring-boot-starter(
spring-boot-starter依赖
另外,由于本项目不是Spring Boot应用,所以不需要主类,不需要运行,所以删除spring-boot-maven-plugin插件。修改后的pom.xml文件内容如下。
spring-boot-maven-plugin
<项目 xmlns=“http://www.sychzs.cn/POM/4.0.0” xmlns:xsi=“http://www.sychzs.cn/2001/XMLSchema-instance” xsi:schemaLocation=“http://www.sychzs.cn/POM/4.0.0 https://www.sychzs.cn/xsd/maven-4.0.0.xsd"> <模型版本>4.0.0 型号版本> <groupId>com.examplegroupId> artifactId>有趣的弹簧启动启动器artifactId> <版本>0.0.1-SNAPSHOT版本> <名称>有趣的弹簧靴-启动器名称> <描述>有趣的弹簧启动器描述> <属性> <java.version>1.8java.version> <project.build.sourceEncoding>UTF-8 project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASEspring-boot.version> 属性> <依赖项> 依赖> <groupId>org.springframework.boot groupId> <artifactId>spring-boot-starter artifactId> 依赖> <依赖> <groupId>org.examplegroupId> <artifactId>有趣artifactId> <版本>1.0-SNAPSHOT 版本> 依赖> 依赖关系> <依赖关系管理> <依赖关系> <依赖性> <groupId>org.springframework.bootgroupId><artifactId>spring-boot-dependencyartifactId> <版本>${spring-boot.version}版本> <类型>pom类型> <范围>导入范围> 依赖项> 依赖项> dependencyManagement>project>
自动下一个定义配置以下类。
src/main/java/com/example/funnyspringbootstarter/autoconfig/www.sychzs.cn
packagecom.example.funnyspringbootstarter.autoconfig;导入io.WriterTemplate;导入org.springframework.boot.autoconfigure.AutoConfigureAfter; 导入 org.springframework.boot.autoconfigure.AutoConfigureOrder;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean ;导入 org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;导入 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;导入 org.springframework.boot.context .properties.EnableConfigurationProperties;导入 org.springframework.context.annotation.Bean;导入 org.springframework.context.annotation.Configuration;导入 javax.sql。数据源;导入www.sychzs.cn;导入java.io.File;导入java.io.FileNotFoundException;import java.nio.charset.Charset;import java.sql.SQLException;@Configuration//当WriteTemplate类存在时配置生效 @ConditionalOnClass(WriterTemplate.class)//FunnyProperties 是自定义类,稍后定义。这里的意思是启动 FunnyProperties@EnableConfigurationProperties(FunnyProperties. class)//让自动配置类位于DataSourceAutoConfiguration处理完自动配置类后@AutoConfigureAfter(DataSourceAutoConfiguration .类)公共类有趣的自动配置{私人 finalFunnyProperties属性;//FunnyProperties类负责用于加载配置属性 public FunnyAutoConfiguration (FunnyProperties 属性) { 这个.properties = 属性; } @Bean(destroyMethod = “关闭”) //单例的DataSource Bean存在时配置生效@ConditionalOnSingleCandidate(DataSource.class) //只有当容器中没有WriterTemplate时才会有此配置 Bean时生效 @ConditionalOnMissingBean // 通过@AutoConfigureOrder注解指定此配置方法而不是下一个配置 W riterTemplate的方法优先级更高 @自动配置订单(99) public WriterTemp迟到writerTemplate(数据源数据源) 抛出 SQLException{ 返回 新WriterTemplate(数据源);} @Bean(destroyMethod = "close") //只有当前面的WriteTemplate配置不生效时,该方法的配置才会生效 @ConditionalOnMissingBean@AutoConfigureOrder(199)公共 WriterTemplate writerTemplate 2() 抛出 FileNotFoundException { 文件 f = 新 文件(这个.properties.getDest()); Charset charset = Charset.forName( 这个.properties.getCharset()); 返回 new WriterTemplate(f ,字符集); }}
FunnyAutoConfiguration 自动配置类中定义了两个 @Bean 方法。两个@Bean方法都用于自动配置WriterTemplate。为了指定它们的优先级,程序使用 @AutoConfigureOrder 注解来修改它们。注解指定的值越小,优先级越高。
FunnyAutoConfiguration
@Bean
@AutoConfigureOrder
FunnyAutoConfiguration自动配置类中的@Bean方法还使用了@Conditional的OnMissingBean、@Conditional的OnSingleCandidate修改等条件注释,保证只有当不存在时容器中存在WriterTemplate,自动配置类会配置WriterTemplate Bean,并且会先配置基于DataSource的WriterTemplate。
FunnyAutoConfiguration
@Conditional的OnMissingBean
@Conditional的OnSingleCandidate修改等条件注释,保证只有当不存在时容器中存在WriterTemplate
WriterTemplate Bean
WriterTemplate
上面的自动配置类还使用了FunnyProperties属性处理类。该类的代码如下:
FunnyProperties
packagecom.example.funnyspringbootstarter.autoconfig;导入org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(前缀=FunnyProperties.FUNNY_PREFIX)公共 类 有趣的属性 { 公共 静态 最终字符串FUNNY_PREFIX = "org.test"; 私有 字符串 dest; private 字符串字符集; public字符串getDest() { 返回d est; } 公共 空 setDest(字符串目的地) { 这个.d est = dest; } 公共 字符串 getCharset() { return 字符集; } public void setCharset (字符串字符集) { 这个.charset = 字符集; }}
上面的属性处理类负责处理以“org.test”开头的属性。这个“org.test”是必要的,相当于这个组配置属性的“命名空间”,通过它可以将这些配置属性与其他框架的配置属性区分开来。
org.test
有了上面的自动配置类之后,使用下面的META-INF/spring.factories文件来注册自动配置类。
src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.example.funnyspringbootstarter.autoconfig.FunnyAutoConfiguration
经过以上步骤,自动配置开发完成,然后使用install打包到maven仓库中:
有了自定义的Starter之后,使用该Starter和使用官方的Spring Boot Starter没有任何区别。
首先新建一个Maven项目myfunnytest,并在pom文件中引入starter:
<project xmlns="http://www.sychzs.cn/POM/4.0.0" xmlns:xsi="http://www.sychzs.cn/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sychzs.cn/POM/4.0.0 https://www.sychzs.cn/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>com.examplegroupId> <artifactId>myfunnytestartifactId> <version>0.0.1-SNAPSHOTversion> <name>myfunnytestname> <description>myfunnytestdescription> <properties> <java.version>1.8java.version> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASEspring-boot.version> properties> <dependencies> <dependency> <groupId>com.examplegroupId> <artifactId>funny-spring-boot-starterartifactId> <version>0.0.1-SNAPSHOTversion> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> <exclusions> <exclusion> <groupId>org.junit.vintagegroupId> <artifactId>junit-vintage-engineartifactId> exclusion> exclusions> dependency> dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-dependenciesartifactId> <version>${spring-boot.version}version> <type>pomtype> <scope>importscope> dependency> dependencies> dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-compiler-pluginartifactId> <version>3.8.1version> <configuration> <source>1.8source> <target>1.8target> <encoding>UTF-8encoding> configuration> plugin> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> <version>2.3.7.RELEASEversion> <configuration> <mainClass>com.example.myfunnytest.MyfunnytestApplicationmainClass> configuration> <executions> <execution> <id>repackageid> <goals> <goal>repackagegoal> goals> execution> executions> plugin> plugins> build>project>
由于 funny-spring-boot-starter 本身需要依赖 spring-boot-starter,因此不再需要显式配置依赖spring-boot-starter。
funny-spring-boot-starter
spring-boot-starter
在添加了上面的funny-spring-boot-starter依赖之后,该Starter包含的自动配置生效,它会尝试在容器中自动配置WriterTemplate,并且还会读取application.properties因此还需要在application.properties文件中进行配置。
src/main/resources/application.properties
# 应用名称www.sychzs.cn=myfunnytestorg.test.dest = F:/abc-12345.txtorg.test.charset=UTF-8spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDateTimeCode=false&serverTimezone=GMT%2B8spring.datasource.username=rootspring.datasource.password=root
该示例的主类很简单,它直接获取容器中的WriterTemplate Bean,并调用该Bean的write()方法执行输出。下面是该主类的代码:
write()
package com.example.myfunnytest;import io.WriterTemplate;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplicationpublic class MyfunnytestApplication { public static void main(String[] args) throws Exception{ ConfigurableApplicationContext run = www.sychzs.cn(MyfunnytestApplication.class, args); WriterTemplate writerTemplate = run.getBean(WriterTemplate.class); System.out.println(writerTemplate); writerTemplate.write("自动配置"); }}
运行该程序,由于当前Spring容器中没有DataSource Bean,因此FunnyAutoConfiguration将会自动配置输出到文件的WriterTemplate。因此,运行该程序,可以看到程序向“f:/abc-12345.txt”文件(由前面的org.test.dest属性配置)输出内容:
DataSource Bean
f:/abc-12345.txt
org.test.dest
运行结果如下:
如果在项目的pom.xml文件中通过如下配置来添加依赖。
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-jdbcartifactId>dependency>
此时为项目添加了spring-boot-starter-jdbc依赖,该依赖将会在容器中自动配置一个DataSource Bean,这个自动配置的DataSource Bean将导致FunnyAutoConfiguration会自动配置输出到数据库的WriterTemplate。因此,运行该程序,可以看到程序向数据库名为springboot数据库的funny_message表输出内容:
spring-boot-starter-jdbc
funny_message
Spring Boot的核心功能就是为整合第三方框架提供自动配置,而本文则带着大家实现了自己的自动配置和Starter,一旦真正掌握了本文的内容,就会对Spring Boot产生“一览众山小”的感觉。