我有一个Spring自动配置库,我是Swagger的开发者。它是使用Spring Boot2.2.6
用Kotlin编写的。
我的主要自动配置定义为:
package io.opengood.autoconfig.swagger
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory.getLogger
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import springfox.documentation.builders.AuthorizationCodeGrantBuilder
import springfox.documentation.builders.OAuthBuilder
import springfox.documentation.builders.PathSelectors
import springfox.documentation.service.*
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spi.service.contexts.SecurityContext
import springfox.documentation.spring.web.plugins.Docket
import springfox.documentation.swagger.web.SecurityConfiguration
import springfox.documentation.swagger.web.SecurityConfigurationBuilder
import springfox.documentation.swagger2.annotations.EnableSwagger2
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.sql.Date as SqlDate
import java.sql.Time as SqlTime
import java.util.Date as UtilDate
@Configuration
@ConditionalOnProperty("swagger.enabled")
@EnableConfigurationProperties(value = [SwaggerProperties::class, OAuth2Properties::class])
@EnableSwagger2
class SwaggerAutoConfiguration(
val swaggerProperties: SwaggerProperties = SwaggerProperties(),
val swaggerVersion: SwaggerVersion = DefaultSwaggerVersion(),
val oAuth2Properties: OAuth2Properties = OAuth2Properties()
) {
val paths = swaggerProperties.paths
.takeIf { !it.isNullOrEmpty() }
.let { it?.joinToString(",") } ?: SwaggerProperties.DEFAULT_PATH
val version = swaggerVersion.version
.takeIf { it.isNotBlank() } ?: swaggerProperties.version
val authUri = oAuth2Properties.resource.authorizationServerUri
.takeIf { it.isNotBlank() } ?: OAuth2Properties.DEFAULT_AUTH_URI
val tokenUri = oAuth2Properties.tokenUri
.takeIf { it.isNotBlank() } ?: OAuth2Properties.DEFAULT_TOKEN_URI
@Bean
fun productApi(): Docket {
log.info("Setup Swagger product configuration")
val productApi = Docket(DocumentationType.SWAGGER_2)
.groupName(swaggerProperties.groupName)
.directModelSubstitute(LocalDateTime::class.java, UtilDate::class.java)
.directModelSubstitute(LocalDate::class.java, SqlDate::class.java)
.directModelSubstitute(LocalTime::class.java, SqlTime::class.java)
.apiInfo(apiInfo())
.select()
.paths(PathSelectors.regex(paths))
.build()
if (oAuth2Properties.enabled && !authUri.contains("localhost")) {
productApi.securitySchemes(listOf(securitySchemes()))
productApi.securityContexts(listOf(securityContext()))
}
return productApi
}
@Bean
fun apiInfo(): ApiInfo {
log.info("Setup Swagger API configuration")
return ApiInfo(
swaggerProperties.title,
swaggerProperties.description,
version,
swaggerProperties.termsOfServiceUrl,
Contact(
swaggerProperties.contact.name,
swaggerProperties.contact.url,
swaggerProperties.contact.email),
swaggerProperties.license.type,
swaggerProperties.license.url,
listOf())
}
@Bean
@ConditionalOnProperty("swagger.security.oauth2.enabled")
fun securityInfo(): SecurityConfiguration {
log.info("Setup Swagger security configuration")
return if (OAuth2Properties.GrantType.CLIENT_CREDENTIALS == oAuth2Properties.grantType) {
SecurityConfigurationBuilder.builder()
.clientId(StringUtils.EMPTY)
.clientSecret(StringUtils.EMPTY)
.scopeSeparator(" ")
.build()
} else {
SecurityConfigurationBuilder.builder()
.useBasicAuthenticationWithAccessCodeGrant(true)
.build()
}
}
private fun securitySchemes(): SecurityScheme {
return if (OAuth2Properties.GrantType.CLIENT_CREDENTIALS == oAuth2Properties.grantType) {
OAuthBuilder()
.name(SECURITY_REFERENCE_NAME)
.grantTypes(listOf(ClientCredentialsGrant(authUri)))
.scopes(scopes())
.build()
} else {
OAuthBuilder()
.name(SECURITY_REFERENCE_NAME)
.grantTypes(listOf(AuthorizationCodeGrantBuilder()
.tokenEndpoint(TokenEndpoint(tokenUri, TOKEN_NAME))
.tokenRequestEndpoint(TokenRequestEndpoint(authUri, "", ""))
.build()))
.scopes(scopes())
.build()
}
}
private fun securityContext(): SecurityContext {
return SecurityContext.builder()
.securityReferences(listOf(SecurityReference(SECURITY_REFERENCE_NAME, scopes().toTypedArray())))
.forPaths(PathSelectors.regex(paths))
.build()
}
private fun scopes(): List<AuthorizationScope> {
return oAuth2Properties.client.scopes
.takeIf { it.isNotEmpty() }
.let { it?.values?.map { s -> AuthorizationScope(s, "") } }
?: emptyList()
}
companion object {
const val SECURITY_REFERENCE_NAME = "spring_oauth2"
const val TOKEN_NAME = "oauth2_token"
@Suppress("JAVA_CLASS_ON_COMPANION")
@JvmStatic
private val log = getLogger(javaClass.enclosingClass)
}
}
我有几个类,见下面的主类,它们作为bean使用@ConfigurationProperties
注入到上面的类中。我想使用新的@ConstructorBind
从我的主自动配置类中删除丑陋的lateint var
。
package io.opengood.autoconfig.swagger
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
@ConfigurationProperties(prefix = "swagger")
@ConstructorBinding
data class SwaggerProperties(
val enabled: Boolean = true,
val groupName: String = "",
val paths: List<String> = listOf(DEFAULT_PATH),
val title: String = "",
val description: String = "",
val version: String = "",
val termsOfServiceUrl: String = "",
val contact: Contact = Contact(),
val license: License = License()
) {
@ConstructorBinding
data class Contact(
val name: String = "",
val url: String = "",
val email: String = ""
)
@ConstructorBinding
data class License(
val type: String = "",
val url: String = ""
)
companion object {
const val DEFAULT_PATH = ".*"
}
}
源代码存储在我的GitHub回购https://github.com/opengoodio/swagger-auto-configuration.
主自动配置项目位于lib/src/main/kotlin/io/opengood/autoconfig/swagger下。
我有另一个项目testapp
,它有一个测试类testapp/src/test/kotlin/io/opengood/autoconfig/swagger/app
,名为AccessSwaggerTest
:
package io.opengood.autoconfig.swagger.app
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@SpringBootTest(classes = [SwaggerTestApplication::class])
@ExtendWith(SpringExtension::class)
@AutoConfigureMockMvc
class AccessSwaggerTest {
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `swagger UI endpoint is accessible`() {
mockMvc.perform(get("/swagger-ui.html"))
.andExpect(status().is2xxSuccessful)
.andReturn();
}
@Test
fun `swagger API docs endpoint is accessible`() {
mockMvc.perform(get("/v2/api-docs?group=test-group"))
.andExpect(status().is2xxSuccessful)
.andReturn();
}
}
如果您运行第一个测试,它将失败:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:98)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$5(ClassBasedTestDescriptor.java:337)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:342)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:337)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1654)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:312)
at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:735)
at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:336)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:259)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:252)
at java.base/java.util.Optional.orElseGet(Optional.java:369)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:251)
at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:29)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:106)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:105)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:69)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:107)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:107)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:75)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'io.opengood.autoconfig.swagger.OAuth2Properties': @EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add @ConstructorBinding type io.opengood.autoconfig.swagger.OAuth2Properties
at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.validate(ConfigurationPropertiesBeanDefinitionValidator.java:66)
at org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator.postProcessBeanFactory(ConfigurationPropertiesBeanDefinitionValidator.java:45)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:286)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:126)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
... 63 more
如果我启动运行这个简单的应用程序,它会失败,并出现类似的错误。
如错误所示,主自动配置具有@EnableConfigurationProperties(值=[SwaggerProperties::class,OAuth2Properties::class])
,但仍然失败。我在测试应用程序
主类上尝试了@ConfigurationPropertiesScan
,但出现了相同的错误。
在过去的几个月里,我一直在寻找解决方案,但找不到一个确凿的原因来解释为什么会发生这种情况。
是什么导致@ConstructorBinding
无法正确绑定?
我的猜测是:您的@springbootplication
注释类在包io中。好的。自动配置。大摇大摆应用程序
并扫描此包和所有子包中的组件。lib中的配置文件位于packageio中。好的。自动配置。招摇过市
,因此它可能不会被扫描。所以你有两个选择:
@springboot应用程序的包
注释类Spring Boot支持用于的Kotlin
我是科特林的新手。因此,我在Android Studio中创建了一个kotlin项目,并导入了片段ktx和活动ktx依赖项。在片段中,我右键单击片段,然后转到- 这是我的应用程序。梯度锉
我试图用OkHttp和Cucumber在静态编程语言中设置一个Spring启动项目,并且在运行Cucumber任务时遇到以下错误。如何修复? 还有build gradle kts片段 我看到了这个错误https://github.com/square/okio/issues/647看起来可能是它,并修复了这个build.gradle,我如何将其翻译为kotlinbuild.gradle.kts?
我在我的一个项目中使用RxJava,我使用Android Studio插件将我的一个类转换为静态编程语言,并在maplambda(java中的Func1)之一中,中间体返回如下所示。 我不知道这意味着什么。
如图所示,https://stackoverflow.com/a/16639438/8949356,在Java中,当声明的类是公共类时,可以重写其函数 但是我想知道如何用静态编程语言编写完全相同的代码,我已经尝试了很多,但没有找到任何关于这个主题的东西。我可以在Java中去做这件事,但我的其余代码是用静态编程语言编写的,而且我不能一直带着这种怀疑;静态编程语言对我来说是一个很好的工具,我想学习它。
它与扩展函数有什么关系?为什么带有的是函数,而不是关键字? 这个主题似乎没有明确的留档,只有关于扩展的知识假设。