SpringBoot编写自定义的starter 专题
What’s in a name
All official starters follow a similar naming pattern; spring-boot-starter-*
, where *
is a particular type of application. This naming structure is intended to help when you need to find a starter. The Maven integration in many IDEs lets you search dependencies by name. For example, with the appropriate Eclipse or STS plugin installed, you can press ctrl-space
in the POM editor and type “spring-boot-starter” for a complete list.
As explained in the “Creating Your Own Starter” section, third party starters should not start with spring-boot
, as it is reserved for official Spring Boot artifacts. Rather, a third-party starter typically starts with the name of the project. For example, a third-party starter project called thirdpartyproject
would typically be named thirdpartyproject-spring-boot-starter
.
SpringBoot-创建自己的starter和autoconfiguration
(1)其实对于每个starter里面并没有源码,只是依赖了一个对应的autoconfiguration,这个autoconfiguration的jar包下有一个XxxxxxAutoConfiguration.class,里面配置了框架启动需要自动注入的一些bean(默认优于配置)。
所以会自动启动这个XxxxxxAutoConfiguration.class
(2)
在META-INF/spring.factories加上自动装配配置;
META-INF/spring-configuration-metadata.json下配置application.properties的key-value的提示项。
(
Tips:这个xx-metadata.json文件生成在 target/classes/META-INF/spring-configurationmetadata.json
需要copy到xxx-Starter项目的 resources/META-INF/ 目录下
)
创建步骤
(1)实现XxxxxxAutoConfiguration.class类,并加上@Conditional限制条件.这需要实现需要自动装载的bean
(2)在META-INF/spring.factories里面加上如下:
# 如果有多个XxxxxAutoConfiguration,用逗号分隔
org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx.xxx.xxx.xxx.XxxxxAutoConfiguration
(3)限制Auto-Configuration的顺序
使用 @AutoConfigureAfter or @AutoConfigureBefore可以限制XxxxxxAutoConfiguration.class的加载顺序
(4)创建metadata
(META-INF/spring-configuration-metadata.json) to make sure your keys are properly documented.
这里的配置是作用XxxxxxAutoConfiguration需要用到的一些properties的,这些properties我们可以写在application.properties里面,然后通过这个文件的规约给出提示。
此外我们可以写一个XxxxProperties.java, 然后通过 @ConfigurationProperties 注解,自动装载application.properties的值到XxxxProperties.java中。
(5)编写startermodule,引入Auto-Configuration这个module
https://blog.csdn.net/u010853261/article/details/77961716
spring.factories
简单的说一下spring.factories这个主要是提供了一个功能,就是自动配置,不需要使用@EnableXXX来开启,也就是说只要你用了springboot,并且依赖了一个jar包,这个jar包就会自动进行初始化
那么这个过程就是使用了spring.factories这个文件配置
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.start.config.AudoDemoConfig
下面的com.start.config.AudoDemoConfig就是自己定义的config,springboot启动的时候就会自动扫描,具体使用可以查看下面这个项目中的com.start.config.AudoDemoConfig
http://git.oschina.net/wangkang_daydayup/SpringBoot-Learn/tree/master/springboot-11
spring-configuration-metadata.json
如果仔细看target/classes/META-INF中,你就会发现有那么一个文件的存在spring-configuration-metadata.json,这个就是整个application.yaml提示的配置,他是自动生成的
{ "hints": [], "groups": [ { "sourceType": "com.start.config.DemoProperties", "name": "springboot.demo", "type": "com.start.config.DemoProperties" } ], "properties": [ { "sourceType": "com.start.config.DemoProperties", "name": "springboot.demo.name", "type": "java.lang.String" }, { "sourceType": "com.start.config.DemoProperties", "name": "springboot.demo.select", "type": "java.lang.String" } ] }
具体的英文文档可以查看http://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor
这个json文件主要分为三个部分,首先是group,group代表一个类,比如DemoProperties,一个项目中存在多个group,而propertis代表是类里面的属性通过sourceType来确定是那一个类的
然后是hints,语意上来说就是提示
hints的使用
具体项目中的使用,可以通过在resources/META-INF 中放入spring-configuration-metadata.json文件,文件内容如下
{ "hints": [ { "name": "springboot.demo.select", "values": [ { "value": "1", "description": "1的描述" }, { "value": "2", "description": "2的描述" } ] } ], "groups": [ { "sourceType": "com.start.config.DemoProperties", "name": "springboot.demo", "type": "com.start.config.DemoProperties" } ], "properties": [ { "sourceType": "com.start.config.DemoProperties", "name": "springboot.demo.name", "type": "java.lang.String" }, { "sourceType": "com.start.config.DemoProperties", "name": "springboot.demo.select", "type": "java.lang.String" } ] }
https://blog.csdn.net/king_is_everyone/article/details/75308620
如果想让上述的配置在application.yaml
进行自动提示,需要进行两步,第一步是确保maven中有processor
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
第二步需要对idea
编辑器进行配置Preferences->Build, Execution, Deployment->Compile->Annotation Processors->Enable annonation processing
钩上这个选项,这样的话就会自动在target/classes/META-INF中生成spring-configuration-metadata.json
文件
一个完整的demo。
todo:设置默认值,及如果有多个可能选择的值,有智能提示
QiniuUploadManagerAutoConfiguration matched: - @ConditionalOnClass found required class 'com.qiniu.storage.UploadManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) - @ConditionalOnProperty (qiniu.oss.enabled) matched (OnPropertyCondition) QiniuUploadManagerAutoConfiguration#qiniuUploadManager matched: - @ConditionalOnMissingBean (types: com.tangcheng.qiniu.oss.autoconfigure.QiniuUploadManager; SearchStrategy: all) did not find any beans (OnBeanCondition)
一个坑:
在pom文件中引入新创建的starter依赖后,Class import不了,提示 Cannot resolve symbol 'packageName'
原因及解决办法:
生成starter的jar,是一个可执行的jar
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
解决办法:
去掉
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
Tips:
common jar的目录结构 与 可执行jar目录结构的差别:
common jar的目录结构:
可执行jar的目录结构【classes文件在BOOT-INF目录下,不在根目录】:
F:.
├─BOOT-INF
│ ├─classes
│ │ └─com
│ │ └─tangcheng
│ │ └─qiniu
│ │ └─oss
│ │ │ QiniuOssSpringBootStarterApplication.class
│ │ │
│ │ └─autoconfigure
│ │ │ QiniuUploadManager.class
│ │ │ QiniuUploadManagerAutoConfiguration.class
│ │ │
│ │ └─properties
│ │ QiniuOssProperties$Bucket$ZoneEnum.class
│ │ QiniuOssProperties$Bucket.class
│ │ QiniuOssProperties.class
│ │
│ └─lib
│ gson-2.8.2.jar
│ happy-dns-java-0.1.6.jar
│ javax.annotation-api-1.3.2.jar
│ jul-to-slf4j-1.7.25.jar
│ log4j-api-2.10.0.jar
│ log4j-to-slf4j-2.10.0.jar
│ logback-classic-1.2.3.jar
│ logback-core-1.2.3.jar
│ okhttp-3.9.1.jar
│ okio-1.13.0.jar
│ qiniu-java-sdk-7.2.11.jar
│ slf4j-api-1.7.25.jar
│ snakeyaml-1.19.jar
│ spring-aop-5.0.5.RELEASE.jar
│ spring-beans-5.0.5.RELEASE.jar
│ spring-boot-2.0.1.RELEASE.jar
│ spring-boot-autoconfigure-2.0.1.RELEASE.jar
│ spring-boot-configuration-processor-2.0.1.RELEASE.jar
│ spring-boot-starter-2.0.1.RELEASE.jar
│ spring-boot-starter-logging-2.0.1.RELEASE.jar
│ spring-context-5.0.5.RELEASE.jar
│ spring-core-5.0.5.RELEASE.jar
│ spring-expression-5.0.5.RELEASE.jar
│ spring-jcl-5.0.5.RELEASE.jar
│
├─META-INF
│ │ MANIFEST.MF
│ │ spring-configuration-metadata.json
│ │ spring.factories
│ │
│ └─maven
│ └─com.tangcheng.qiniu.oss
│ └─qiniu-oss-spring-boot-starter
│ pom.properties
│ pom.xml
│
└─org
└─springframework
└─boot
└─loader
│ ExecutableArchiveLauncher.class
│ JarLauncher.class
│ LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
│ LaunchedURLClassLoader.class
│ Launcher.class
│ MainMethodRunner.class
│ PropertiesLauncher$1.class
│ PropertiesLauncher$ArchiveEntryFilter.class
│ PropertiesLauncher$PrefixMatchingArchiveFilter.class
│ PropertiesLauncher.class
│ WarLauncher.class
│
├─archive
│ Archive$Entry.class
│ Archive$EntryFilter.class
│ Archive.class
│ ExplodedArchive$1.class
│ ExplodedArchive$FileEntry.class
│ ExplodedArchive$FileEntryIterator$EntryComparator.class
│ ExplodedArchive$FileEntryIterator.class
│ ExplodedArchive.class
│ JarFileArchive$EntryIterator.class
│ JarFileArchive$JarFileEntry.class
│ JarFileArchive.class
│
├─data
│ RandomAccessData.class
│ RandomAccessDataFile$1.class
│ RandomAccessDataFile$DataInputStream.class
│ RandomAccessDataFile$FileAccess.class
│ RandomAccessDataFile.class
│
├─jar
│ AsciiBytes.class
│ Bytes.class
│ CentralDirectoryEndRecord.class
│ CentralDirectoryFileHeader.class
│ CentralDirectoryParser.class
│ CentralDirectoryVisitor.class
│ FileHeader.class
│ Handler.class
│ JarEntry.class
│ JarEntryFilter.class
│ JarFile$1.class
│ JarFile$2.class
│ JarFile$JarFileType.class
│ JarFile.class
│ JarFileEntries$1.class
│ JarFileEntries$EntryIterator.class
│ JarFileEntries.class
│ JarURLConnection$1.class
│ JarURLConnection$JarEntryName.class
│ JarURLConnection.class
│ StringSequence.class
│ ZipInflaterInputStream.class
│
└─util
SystemPropertyUtils.class
在之前的文章中,我们分析过SpringBoot内部的自动化配置原理和自动化配置注解开关原理。
我们先简单分析一下mybatis starter的编写,然后再编写自定义的starter。
mybatis中的autoconfigure模块中使用了一个叫做MybatisAutoConfiguration的自动化配置类。
这个MybatisAutoConfiguration需要在这些Condition条件下才会执行:
- @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })。需要SqlSessionFactory和SqlSessionFactoryBean在classpath中都存在
- @ConditionalOnBean(DataSource.class)。 spring factory中需要存在一个DataSource的bean
- @AutoConfigureAfter(DataSourceAutoConfiguration.class)。需要在DataSourceAutoConfiguration自动化配置之后进行配置,因为mybatis需要数据源的支持
同时在META-INF目录下有个spring.factories这个properties文件,而且它的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这样才会被springboot加载:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
有了这些东西之后,mybatis相关的配置会被自动加入到spring container中,只要在maven中加入starter即可:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
编写自定义的starter
接下来,我们来编写自定义的starter:log-starter。
这个starter内部定义了一个注解,使用这个注解修饰方法之后,该方法的调用会在日志中被打印并且还会打印出方法的耗时。starter支持exclude配置,在exclude中出现的方法不会进行计算。
pom文件:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
定义修饰方法的注解@Log:
package me.format.springboot.log.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log { }
然后是配置类:
package me.format.springboot.log.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
@ConfigurationProperties(prefix = "mylog")
public class LogProperties {
private String exclude;
private String[] excludeArr;
@PostConstruct
public void init() {
this.excludeArr = StringUtils.split(exclude, ",");
}
public String getExclude() {
return exclude;
}
public void setExclude(String exclude) {
this.exclude = exclude;
}
public String[] getExcludeArr() {
return excludeArr;
}
}
接下来是AutoConfiguration:
package me.format.springboot.log.autoconfigure;
import me.format.springboot.log.annotation.Log;
import me.format.springboot.log.aop.LogMethodInterceptor;
import org.aopalliance.aop.Advice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@EnableConfigurationProperties(LogProperties.class)
public class LogAutoConfiguration extends AbstractPointcutAdvisor {
private Logger logger = LoggerFactory.getLogger(LogAutoConfiguration.class);
private Pointcut pointcut;
private Advice advice;
@Autowired
private LogProperties logProperties;
@PostConstruct
public void init() {
logger.info("init LogAutoConfiguration start");
this.pointcut = new AnnotationMatchingPointcut(null, Log.class);
this.advice = new LogMethodInterceptor(logProperties.getExcludeArr());
logger.info("init LogAutoConfiguration end");
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
}
由于计算方法调用的时候需要使用aop相关的lib,所以我们的AutoConfiguration继承了AbstractPointcutAdvisor。这样就有了Pointcut和Advice。Pointcut是一个支持注解的修饰方法的Pointcut,Advice则自己实现:
package me.format.springboot.log.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
public class LogMethodInterceptor implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(LogMethodInterceptor.class);
private List<String> exclude;
public LogMethodInterceptor(String[] exclude) {
this.exclude = Arrays.asList(exclude);
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
if(exclude.contains(methodName)) {
return invocation.proceed();
}
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
logger.info("====method({}), cost({}) ", methodName, (end - start));
return result;
}
}
最后resources/META-INF/spring.factories中加入这个AutoConfiguration:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
me.format.springboot.log.autoconfigure.LogAutoConfiguration
我们在项目中使用这个log-starter:
<dependency>
<groupId>me.format.springboot</groupId>
<artifactId>log-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
使用配置:
mylog.exclude=core,log
然后编写一个简单的Service:
@Service
public class SimpleService {
@Log
public void test(int num) {
System.out.println("----test---- " + num);
}
@Log
public void core(int num) {
System.out.println("----core---- " + num);
}
public void work(int num) {
System.out.println("----work---- " + num);
}
}
使用单元测试分别调用这3个方法,由于work方法没有加上@Log注解,core方法虽然加上了@Log注解,但是在配置中被exclude了,只有test方法可以正常计算耗时:
----test---- 666
2016-11-16 01:29:32.255 INFO 41010 --- [ main] m.f.s.log.aop.LogMethodInterceptor : ====method(test), cost(36)
----work---- 666
----core---- 666
总结:
自定义springboot的starter,注意这两点。
- 如果自动化配置类需要在程序启动的时候就加载,可以在META-INF/spring.factories文件中定义。如果本次加载还需要其他一些lib的话,可以使用ConditionalOnClass注解协助
- 如果自动化配置类要在使用自定义注解后才加载,可以使用自定义注解+@Import注解或@ImportSelector注解完成
参考:
http://www.jianshu.com/p/85460c1d835a
http://fangjian0423.github.io/2016/11/16/springboot-custom-starter/
Appendix B. Configuration Metadata
Spring Boot jars include metadata files that provide details of all supported configuration properties. The files are designed to let IDE developers offer contextual help and “code completion” as users are working with application.properties
or application.yml
files.
The majority of the metadata file is generated automatically at compile time by processing all items annotated with @ConfigurationProperties
. However, it is possible to write part of the metadata manually for corner cases or more advanced use cases.
Configuration metadata files are located inside jars under META-INF/spring-configuration-metadata.json
They use a simple JSON format with items categorized under either “groups” or “properties” and additional values hints categorized under "hints", as shown in the following example:
{"groups": [
{
"name": "server",
"type": "org.springframework.boot.autoconfigure.web.ServerProperties",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
},
{
"name": "spring.jpa.hibernate",
"type": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties$Hibernate",
"sourceType": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties",
"sourceMethod": "getHibernate()"
}
...
],"properties": [
{
"name": "server.port",
"type": "java.lang.Integer",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties"
},
{
"name": "server.servlet.path",
"type": "java.lang.String",
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
"defaultValue": "/"
},
{
"name": "spring.jpa.hibernate.ddl-auto",
"type": "java.lang.String",
"description": "DDL mode. This is actually a shortcut for the \"hibernate.hbm2ddl.auto\" property.",
"sourceType": "org.springframework.boot.autoconfigure.orm.jpa.JpaProperties$Hibernate"
}
...
],"hints": [
{
"name": "spring.jpa.hibernate.ddl-auto",
"values": [
{
"value": "none",
"description": "Disable DDL handling."
},
{
"value": "validate",
"description": "Validate the schema, make no changes to the database."
},
{
"value": "update",
"description": "Update the schema if necessary."
},
{
"value": "create",
"description": "Create the schema and destroy previous data."
},
{
"value": "create-drop",
"description": "Create and then destroy the schema at the end of the session."
}
]
}
]}
Each “property” is a configuration item that the user specifies with a given value. For example, server.port
and server.servlet.path
might be specified inapplication.properties
, as follows:
server.port=9090
server.servlet.path=/home
The “groups” are higher level items that do not themselves specify a value but instead provide a contextual grouping for properties. For example, the server.port
andserver.servlet.path
properties are part of the server
group.
It is not required that every “property” has a “group”. Some properties might exist in their own right. |
Finally, “hints” are additional information used to assist the user in configuring a given property. For example, when a developer is configuring thespring.jpa.hibernate.ddl-auto
property, a tool can use the hints to offer some auto-completion help for the none
, validate
, update
, create
, and create-drop
values.
The JSON object contained in the groups
array can contain the attributes shown in the following table:
Name | Type | Purpose |
---|---|---|
|
String |
The full name of the group. This attribute is mandatory. |
|
String |
The class name of the data type of the group. For example, if the group were based on a class annotated with |
|
String |
A short description of the group that can be displayed to users. If not description is available, it may be omitted. It is recommended that descriptions be short paragraphs, with the first line providing a concise summary. The last line in the description should end with a period ( |
|
String |
The class name of the source that contributed this group. For example, if the group were based on a |
|
String |
The full name of the method (include parenthesis and argument types) that contributed this group (for example, the name of a |
The JSON object contained in the properties
array can contain the attributes described in the following table:
Name | Type | Purpose |
---|---|---|
|
String |
The full name of the property. Names are in lower-case period-separated form (for example, |
|
String |
The full signature of the data type of the property (for example, |
|
String |
A short description of the group that can be displayed to users. If no description is available, it may be omitted. It is recommended that descriptions be short paragraphs, with the first line providing a concise summary. The last line in the description should end with a period ( |
|
String |
The class name of the source that contributed this property. For example, if the property were from a class annotated with |
|
Object |
The default value, which is used if the property is not specified. If the type of the property is an array, it can be an array of value(s). If the default value is unknown, it may be omitted. |
|
Deprecation |
Specify whether the property is deprecated. If the field is not deprecated or if that information is not known, it may be omitted. The next table offers more detail about the |
The JSON object contained in the deprecation
attribute of each properties
element can contain the following attributes:
Name | Type | Purpose |
---|---|---|
|
String |
The level of deprecation, which can be either |
|
String |
A short description of the reason why the property was deprecated. If no reason is available, it may be omitted. It is recommended that descriptions be short paragraphs, with the first line providing a concise summary. The last line in the description should end with a period ( |
|
String |
The full name of the property that replaces this deprecated property. If there is no replacement for this property, it may be omitted. |
Prior to Spring Boot 1.3, a single |
Deprecation can also be specified declaratively in code by adding the @DeprecatedConfigurationProperty
annotation to the getter exposing the deprecated property. For instance, assume that the app.acme.target
property was confusing and was renamed to app.acme.name
. The following example shows how to handle that situation:
@ConfigurationProperties("app.acme") public class AcmeProperties { private String name; public String getName() { ... } public void setName(String name) { ... } @DeprecatedConfigurationProperty(replacement = "app.acme.name") @Deprecated public String getTarget() { return getName(); } @Deprecated public void setTarget(String target) { setName(target); } }
There is no way to set a |
The preceding code makes sure that the deprecated property still works (delegating to the name
property behind the scenes). Once the getTarget
and setTarget
methods can be removed from your public API, the automatic deprecation hint in the metadata goes away as well. If you want to keep a hint, adding manual metadata with an error
deprecation level ensures that users are still informed about that property. Doing so is particularly useful when a replacement
is provided.
The JSON object contained in the hints
array can contain the attributes shown in the following table:
Name | Type | Purpose |
---|---|---|
|
String |
The full name of the property to which this hint refers. Names are in lower-case period-separated form (such as |
|
ValueHint[] |
A list of valid values as defined by the |
|
ValueProvider[] |
A list of providers as defined by the |
The JSON object contained in the values
attribute of each hint
element can contain the attributes described in the following table:
Name | Type | Purpose |
---|---|---|
|
Object |
A valid value for the element to which the hint refers. If the type of the property is an array, it can also be an array of value(s). This attribute is mandatory. |
|
String |
A short description of the value that can be displayed to users. If no description is available, it may be omitted . It is recommended that descriptions be short paragraphs, with the first line providing a concise summary. The last line in the description should end with a period ( |
The JSON object contained in the providers
attribute of each hint
element can contain the attributes described in the following table:
Name | Type | Purpose |
---|---|---|
|
String |
The name of the provider to use to offer additional content assistance for the element to which the hint refers. |
|
JSON object |
Any additional parameter that the provider supports (check the documentation of the provider for more details). |
Objects with the same “property” and “group” name can appear multiple times within a metadata file. For example, you could bind two separate classes to the same prefix, with each having potentially overlapping property names. While the same names appearing in the metadata multiple times should not be common, consumers of metadata should take care to ensure that they support it.
To improve the user experience and further assist the user in configuring a given property, you can provide additional metadata that:
- Describes the list of potential values for a property.
- Associates a provider, to attach a well defined semantic to a property, so that a tool can discover the list of potential values based on the project’s context.
The name
attribute of each hint refers to the name
of a property. In the initial example shown earlier, we provide five values for the spring.jpa.hibernate.ddl-auto
property: none
, validate
, update
, create
, and create-drop
. Each value may have a description as well.
If your property is of type Map
, you can provide hints for both the keys and the values (but not for the map itself). The special .keys
and .values
suffixes must refer to the keys and the values, respectively.
Assume a sample.contexts
maps magic String
values to an integer, as shown in the following example:
@ConfigurationProperties("sample") public class SampleProperties { private Map<String,Integer> contexts; // getters and setters }
The magic values are (in this example) are sample1
and sample2
. In order to offer additional content assistance for the keys, you could add the following JSON to the manual metadata of the module:
{"hints": [
{
"name": "sample.contexts.keys",
"values": [
{
"value": "sample1"
},
{
"value": "sample2"
}
]
}
]}
We recommend that you use an |
Providers are a powerful way to attach semantics to a property. In this section, we define the official providers that you can use for your own hints. However, your favorite IDE may implement some of these or none of them. Also, it could eventually provide its own.
As this is a new feature, IDE vendors must catch up with how it works. Adoption times naturally vary. |
The following table summarizes the list of supported providers:
Name | Description |
---|---|
|
Permits any additional value to be provided. |
|
Auto-completes the classes available in the project. Usually constrained by a base class that is specified by the |
|
Handles the property as if it were defined by the type defined by the mandatory |
|
Auto-completes valid logger names. Typically, package and class names available in the current project can be auto-completed. |
|
Auto-completes the available bean names in the current project. Usually constrained by a base class that is specified by the |
|
Auto-completes the available Spring profile names in the project. |
Only one provider can be active for a given property, but you can specify several providers if they can all manage the property in some way. Make sure to place the most powerful provider first, as the IDE must use the first one in the JSON section that it can handle. If no provider for a given property is supported, no special content assistance is provided, either. |
The special any provider value permits any additional values to be provided. Regular value validation based on the property type should be applied if this is supported.
This provider is typically used if you have a list of values and any extra values should still be considered as valid.
The following example offers on
and off
as auto-completion values for system.state
:
{"hints": [
{
"name": "system.state",
"values": [
{
"value": "on"
},
{
"value": "off"
}
],
"providers": [
{
"name": "any"
}
]
}
]}
Note that, in the preceding example, any other value is also allowed.
The class-reference provider auto-completes classes available in the project. This provider supports the following parameters:
Parameter | Type | Default value | Description |
---|---|---|---|
|
|
none |
The fully qualified name of the class that should be assignable to the chosen value. Typically used to filter out-non candidate classes. Note that this information can be provided by the type itself by exposing a class with the appropriate upper bound. |
|
|
true |
Specify whether only concrete classes are to be considered as valid candidates. |
The following metadata snippet corresponds to the standard server.servlet.jsp.class-name
property that defines the JspServlet
class name to use:
{"hints": [
{
"name": "server.servlet.jsp.class-name",
"providers": [
{
"name": "class-reference",
"parameters": {
"target": "javax.servlet.http.HttpServlet"
}
}
]
}
]}
The handle-as provider lets you substitute the type of the property to a more high-level type. This typically happens when the property has a java.lang.String
type, because you do not want your configuration classes to rely on classes that may not be on the classpath. This provider supports the following parameters:
Parameter | Type | Default value | Description |
---|---|---|---|
|
|
none |
The fully qualified name of the type to consider for the property. This parameter is mandatory. |
The following types can be used:
- Any
java.lang.Enum
: Lists the possible values for the property. (We recommend defining the property with theEnum
type, as no further hint should be required for the IDE to auto-complete the values.) java.nio.charset.Charset
: Supports auto-completion of charset/encoding values (such asUTF-8
)java.util.Locale
: auto-completion of locales (such asen_US
)org.springframework.util.MimeType
: Supports auto-completion of content type values (such astext/plain
)org.springframework.core.io.Resource
: Supports auto-completion of Spring’s Resource abstraction to refer to a file on the filesystem or on the classpath. (such asclasspath:/sample.properties
)
If multiple values can be provided, use a |
The following metadata snippet corresponds to the standard spring.liquibase.change-log
property that defines the path to the changelog to use. It is actually used internally as a org.springframework.core.io.Resource
but cannot be exposed as such, because we need to keep the original String value to pass it to the Liquibase API.
{"hints": [
{
"name": "spring.liquibase.change-log",
"providers": [
{
"name": "handle-as",
"parameters": {
"target": "org.springframework.core.io.Resource"
}
}
]
}
]}
The logger-name provider auto-completes valid logger names. Typically, package and class names available in the current project can be auto-completed. Specific frameworks may have extra magic logger names that can be supported as well.
Since a logger name can be any arbitrary name, this provider should allow any value but could highlight valid package and class names that are not available in the project’s classpath.
The following metadata snippet corresponds to the standard logging.level
property. Keys are logger names, and values correspond to the standard log levels or any custom level.
{"hints": [
{
"name": "logging.level.keys",
"values": [
{
"value": "root",
"description": "Root logger used to assign the default logging level."
}
],
"providers": [
{
"name": "logger-name"
}
]
},
{
"name": "logging.level.values",
"values": [
{
"value": "trace"
},
{
"value": "debug"
},
{
"value": "info"
},
{
"value": "warn"
},
{
"value": "error"
},
{
"value": "fatal"
},
{
"value": "off"
}
],
"providers": [
{
"name": "any"
}
]
}
]}
The spring-bean-reference provider auto-completes the beans that are defined in the configuration of the current project. This provider supports the following parameters:
Parameter | Type | Default value | Description |
---|---|---|---|
|
|
none |
The fully qualified name of the bean class that should be assignable to the candidate. Typically used to filter out non-candidate beans. |
The following metadata snippet corresponds to the standard spring.jmx.server
property that defines the name of the MBeanServer
bean to use:
{"hints": [
{
"name": "spring.jmx.server",
"providers": [
{
"name": "spring-bean-reference",
"parameters": {
"target": "javax.management.MBeanServer"
}
}
]
}
]}
The binder is not aware of the metadata. If you provide that hint, you still need to transform the bean name into an actual Bean reference using by the |
The spring-profile-name provider auto-completes the Spring profiles that are defined in the configuration of the current project.
The following metadata snippet corresponds to the standard spring.profiles.active
property that defines the name of the Spring profile(s) to enable:
{"hints": [
{
"name": "spring.profiles.active",
"providers": [
{
"name": "spring-profile-name"
}
]
}
]}
You can easily generate your own configuration metadata file from items annotated with @ConfigurationProperties
by using the spring-boot-configuration-processor
jar. The jar includes a Java annotation processor which is invoked as your project is compiled. To use the processor, include a dependency on spring-boot-configuration-processor
.
With Maven the dependency should be declared as optional, as shown in the following example:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
With Gradle 4.5 and earlier, the dependency should be declared in the compileOnly
configuration, as shown in the following example:
dependencies {
compileOnly "org.springframework.boot:spring-boot-configuration-processor"
}
With Gradle 4.6 and later, the dependency should be declared in the annotationProcessor
configuration, as shown in the following example:
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
If you are using an additional-spring-configuration-metadata.json
file, the compileJava
task should be configured to depend on the processResources
task, as shown in the following example:
compileJava.dependsOn(processResources)
This dependency ensures that the additional metadata is available when the annotation processor runs during compilation.
The processor picks up both classes and methods that are annotated with @ConfigurationProperties
. The Javadoc for field values within configuration classes is used to populate the description
attribute.
You should only use simple text with |
Properties are discovered through the presence of standard getters and setters with special handling for collection types (that is detected even if only a getter is present). The annotation processor also supports the use of the @Data
, @Getter
, and @Setter
lombok annotations.
If you are using AspectJ in your project, you need to make sure that the annotation processor runs only once. There are several ways to do this. With Maven, you can configure the <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<proc>none</proc>
</configuration>
</plugin>
|
The annotation processor automatically considers inner classes as nested properties. Consider the following class:
@ConfigurationProperties(prefix="server") public class ServerProperties { private String name; private Host host; // ... getter and setters private static class Host { private String ip; private int port; // ... getter and setters } }
The preceding example produces metadata information for server.name
, server.host.ip
, and server.host.port
properties. You can use the @NestedConfigurationProperty
annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
This has no effect on collections and maps, as those types are automatically identified, and a single metadata property is generated for each of them. |
Spring Boot’s configuration file handling is quite flexible, and it is often the case that properties may exist that are not bound to a @ConfigurationProperties
bean. You may also need to tune some attributes of an existing key. To support such cases and let you provide custom "hints", the annotation processor automatically merges items from META-INF/additional-spring-configuration-metadata.json
into the main metadata file.
If you refer to a property that has been detected automatically, the description, default value, and deprecation information are overridden, if specified. If the manual property declaration is not identified in the current module, it is added as a new property.
The format of the additional-spring-configuration-metadata.json
file is exactly the same as the regular spring-configuration-metadata.json
. The additional properties file is optional. If you do not have any additional properties, do not add the file.
https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor
https://www.jianshu.com/p/547fe62365f8