I am playing around with Kofu functional Bean DSL. I am using Spring-Data-JDBC with Spring-MVC and trying to autowire NamedParameterJdbcTemplate. However, I am have been receiving this error that no beans found for it while running tests. In a annotation based approach, we don’t have to supply an explicit NamedParameterJdbcTemplate. My sample app here: https://github.com/overfullstack/kofu-mvc-jdbc. And PFB some code snippets from it:
val app = application(WebApplicationType.SERVLET) {
beans {
bean<SampleService>()
bean<UserHandler>()
}
enable(dataConfig)
enable(webConfig)
}
val dataConfig = configuration {
beans {
bean<UserRepository>()
}
listener<ApplicationReadyEvent> {
ref<UserRepository>().init()
}
}
val webConfig = configuration {
webMvc {
port = if (profiles.contains("test")) 8181 else 8080
router {
val handler = ref<UserHandler>()
GET("/", handler::hello)
GET("/api", handler::json)
}
converters {
string()
jackson()
}
}
}
class UserRepository(private val client: NamedParameterJdbcTemplate) {
fun count() =
client.queryForObject("SELECT COUNT(*) FROM users", emptyMap<String, String>(), Int::class.java)
}
open class UserRepositoryTests {
private val dataApp = application(WebApplicationType.NONE) {
enable(dataConfig)
}
private lateinit var context: ConfigurableApplicationContext
#BeforeAll
fun beforeAll() {
context = dataApp.run(profiles = "test")
}
#Test
fun count() {
val repository = context.getBean<UserRepository>()
assertEquals(3, repository.count())
}
#AfterAll
fun afterAll() {
context.close()
}
}
This is the error:
Parameter 0 of constructor in com.sample.UserRepository required a bean of type 'org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate' in your configuration.
Please help, thanks
Apparently Kofu doesn't pick datasource from application.properties file. Everything is meant to be declarative and no implicit derivations. (Basically no Spring magic 🙂). This worked for me:
val dataConfig = configuration {
beans {
bean {
val dataSourceBuilder = DataSourceBuilder.create()
dataSourceBuilder.driverClassName(“org.h2.Driver”)
dataSourceBuilder.url(“jdbc:h2:mem:test”)
dataSourceBuilder.username(“SA”)
dataSourceBuilder.password(“”)
dataSourceBuilder.build()
}
bean<NamedParameterJdbcTemplate>()
bean<UserRepository>()
}
listener<ApplicationReadyEvent> {
ref<UserRepository>().init()
}
}
Related
I need to configure default coroutine context for all requests in Spring MVC. For example MDCContext (similar question as this but for MVC not WebFlux).
What I have tried
Hook into Spring - the coroutine code is here but there is no way to change the default behavior (need to change InvocableHandlerMethod.doInvoke implementation)
Use AOP - AOP and coroutines do not play well together
Any ideas?
This seems to work:
#Configuration
class ContextConfig: WebMvcRegistrations {
override fun getRequestMappingHandlerAdapter(): RequestMappingHandlerAdapter {
return object: RequestMappingHandlerAdapter() {
override fun createInvocableHandlerMethod(handlerMethod: HandlerMethod): ServletInvocableHandlerMethod {
return object : ServletInvocableHandlerMethod(handlerMethod) {
override fun doInvoke(vararg args: Any?): Any? {
val method = bridgedMethod
ReflectionUtils.makeAccessible(method)
if (KotlinDetector.isSuspendingFunction(method)) {
// Exception handling skipped for brevity, copy it from super.doInvoke()
return invokeSuspendingFunctionX(method, bean, *args)
}
return super.doInvoke(*args)
}
/**
* Copied from CoroutinesUtils in order to be able to set CoroutineContext
*/
#Suppress("UNCHECKED_CAST")
private fun invokeSuspendingFunctionX(method: Method, target: Any, vararg args: Any?): Publisher<*> {
val function = method.kotlinFunction!!
val mono = mono(YOUR_CONTEXT_HERE) {
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
return if (function.returnType.classifier == Flow::class) {
mono.flatMapMany { (it as Flow<Any>).asFlux() }
}
else {
mono
}
}
}
}
}
}
}
I have test that works properly with Spring 2.4.0-M2 but after upgrading to 2.4.0-M3 it breaks - returns 404 for a route that is registered.
My app:
#SpringBootApplication(proxyBeanMethods = false)
class ExampleApp
fun main(args: Array<String>) {
runApplication<ExampleApp>(
init = {
addInitializers(BeansInitializer())
},
args = args
)
}
beans:
class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {
#Suppress("LongMethod")
override fun initialize(applicationContext: GenericApplicationContext) {
beans {
bean {
router {
"/routes".nest {
GET("/{id}") { ServerResponse.ok().bodyValue(Foo("ok")) }
POST("/") { ServerResponse.ok().bodyValue(Foo("ok")) }
}
}
}
}
.initialize(applicationContext)
}
}
data class Foo(val status: String)
My test:
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = [
ExampleApp::class
]
)
class FailingTest #Autowired constructor(
context: ApplicationContext,
) {
val webTestClient: WebTestClient = WebTestClient.bindToApplicationContext(context)
.configureClient()
.build()
#Test
fun `should interact with routes`() {
webTestClient
.post()
.uri("/routes")
.bodyValue(SampleBody("123"))
.exchange()
.expectStatus()
.isOk // returns 404 on 2.4.0-M3 / passes on 2.4.0-M2
}
data class SampleBody(val id: String)
}
test application.yml
context:
initializer:
classes: com.example.BeansInitializer
On 2.4.0-M3 tests fail with following message:
java.lang.AssertionError: Status expected:<200 OK> but was:<404 NOT_FOUND>
On 2.4.0-M2 they pass.
Is there something that changed through the versions? Or this is a bug?
The change in behaviour that you are seeing is due to an improvement in Spring Framework during the development of 5.3.
By default, Spring Framework will match an optional trailing path separator (/). This optional / should be in addition to the path specified in your routes.
You have two routes:
GET /routes/{id}
POST /routes/
The support for an optional trailing path separator means that you could make a get request to /routes/56/ (an additional trailing /), but it should not mean that you can make a request to POST /routes (removal of a trailing /).
If you want to be able to make POST requests to both /routes and /routes/, you should define the route as /routes:
beans {
bean {
router {
"/routes".nest {
GET("/{id}") { ServerResponse.ok().bodyValue(Foo("ok")) }
POST("") { ServerResponse.ok().bodyValue(Foo("ok")) }
}
}
}
}
I have an application.yml with some configuration properties required by my application.
SF:
baseurl: https://xxxxx
case:
recordTypeId: 0124a0000004Ifb
application:
recordTypeId: 0125P000000MkDa
address:
personal:
recordTypeId: 0125P000000MnuO
business:
recordTypeId: 0125P000000MnuT
I have defined a configuration class to read those properties as follows:
#Configuration
class SFProperties(
#Value("\${sf.case.recordTypeId}") val caseRecordTypeId: String,
#Value("\${sf.application.recordTypeId}") val applicationRecordTypeId: String,
#Value("\${sf.address.personal.recordTypeId}") val addressPersonalRecordTypeId:String,
#Value("\${sf.address.business.recordTypeId}") val addressBusinessRecordTypeId: String
)
The class is wired within a service without any issues,
#Service
class SFClientManagementServiceImpl( val webClientBuilder: WebClient.Builder):
ClientManagementService {
....
#Autowired
lateinit var sfProperties: SFProperties
override fun createCase(caseRequest: CaseRequestDto): Mono<CaseResponseDto> {
...
var myValue= sfProperties.caseRecordTypeId
....
}
}
When trying to test this service, I get a "lateinit property sfProperties has not been initialized" exception:
The test looks as follows:
#SpringBootTest(classes = [SFProperties::class])
class SalesforceClientManagementServiceImplTests {
#Autowired
open lateinit var sfProperties: SFProperties
#Test
fun `createCase should return case id when case is created`() {
val clientResponse: ClientResponse = ClientResponse
.create(HttpStatus.OK)
.header("Content-Type", "application/json")
.body(ObjectMapper().writeValueAsString(Fakes().GetFakeCaseResponseDto())).build()
val shortCircuitingExchangeFunction = ExchangeFunction {
Mono.just(clientResponse)
}
val webClientBuilder: WebClient.Builder = WebClient.builder().exchangeFunction(shortCircuitingExchangeFunction)
val sfClientManagementServiceImpl =
SFClientManagementServiceImpl(webClientBuilder)
var caseResponseDto =
salesforceClientManagementServiceImpl.createCase(Fakes().GetFakeCaseRequestDto())
var response = caseResponseDto.block()
if (response != null) {
assertEquals(Fakes().GetFakeCaseResponseDto().id, response.id)
}
}
I have tried many other annotations on the Test class but without success, I would appreciate any ideas.
I know this kind of question has been asked before.
I have a method which is annotated with #PostConstruct.
The methods assumes that all Flyway scripts have been executed before invocation.
It seems that Flyway also uses #PostConstruct annotated methods and that these methods are called after my method.
I tried to annotate my method with #DependOn and different flyway beennames.
Unfortunately without success. Can anybody help me.
Solution:
I would set a dependency on the FlywayMigrationInitializer in the constructor. When the Initializer is created and set up, the migrations are run.
Or you can depend on the flywayInitializer bean (#DependsOn("flywayInitializer")). The bean is named flywayInitializer, of the class FlywayMigrationInitializer and it is created in FlywayAutoConfiguration.java.
FlywayMigrationInitializer implements InitializingBean and calls the migrate method in the afterPropertiesSet method.
Example:
#Component
// #DependsOn("flywayInitializer")
#Slf4j
public class TestPostConstruct {
public TestPostConstruct(FlywayMigrationInitializer flywayForceInitialization) {
}
#PostConstruct
public void testPostConstruct() {
log.info("----> in testPostConstruct");
}
}
The Spring Boot log:
INFO 4760 --- [main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.130s)
INFO 4760 --- [main] c.example.flywayinit.TestPostConstruct : ----> in testPostConstruct
For new Flyway this work (use Flyway callbacks)
#Configuration
class FlywayConfig(env: Environment) {
private val env: Environment
init {
this.env = env
}
#Bean(initMethod = "migrate")
fun flyway(dbLoadService: DbLoadService): Flyway {
return Flyway(
Flyway.configure()
.baselineOnMigrate(true)
.dataSource(
env.getRequiredProperty("spring.datasource.url"),
env.getRequiredProperty("spring.datasource.username"),
env.getRequiredProperty("spring.datasource.password")
)
//запуск загрузки из базы после окончания миграции
.callbacks(FlywayMigrationsCompleteCallback {
dbLoadService.loadAllCertificateInformation()
})
)
}
class FlywayMigrationsCompleteCallback(private val callback: () -> Unit) : Callback {
override fun supports(event: Event?, context: Context?): Boolean {
return event == Event.AFTER_MIGRATE
}
override fun canHandleInTransaction(event: Event?, context: Context?): Boolean {
return true
}
override fun handle(event: Event?, context: Context?) {
callback()
}
override fun getCallbackName(): String {
return FlywayMigrationsCompleteCallback::class.simpleName!!
}
}
#Component
class DbLoadService(private val certificateRepository:CertificateRepository) {
#Volatile var certificate: List<Certificate>?=null
fun loadAllCertificateInformation(){
val findAll = certificateRepository.findAll()
runBlocking {
certificate = findAll.toList()
}
}
}
I have tried all 3 solutions suggested in what is the right way to handle errors in spring-webflux, but WebExceptionHandler is not getting called. I am using Spring Boot 2.0.0.M7. Github repo here
#Configuration
class RoutesConfiguration {
#Autowired
private lateinit var testService: TestService
#Autowired
private lateinit var globalErrorHandler: GlobalErrorHandler
#Bean
fun routerFunction():
RouterFunction<ServerResponse> = router {
("/test").nest {
GET("/") {
ServerResponse.ok().body(testService.test())
}
}
}
}
#Component
class GlobalErrorHandler() : WebExceptionHandler {
companion object {
private val log = LoggerFactory.getLogger(GlobalErrorHandler::class.java)
}
override fun handle(exchange: ServerWebExchange?, ex: Throwable?): Mono<Void> {
log.info("inside handle")
/* Handle different exceptions here */
when(ex!!) {
is ClientException -> exchange!!.response.statusCode = HttpStatus.BAD_REQUEST
is Exception -> exchange!!.response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
}
return Mono.empty()
}
}
UPDATE:
When I change Spring Boot version to 2.0.0.M2, the WebExceptionHandler is getting called. Do I need to do something for 2.0.0.M7?
SOLUTION:
As per Brian's suggestion, it worked as
#Bean
#Order(-2)
fun globalErrorHandler() = GlobalErrorHandler()
You can provide your own WebExceptionHandler, but you have to order it relatively to others, otherwise they might handle the error before yours get a chance to try.
the DefaultErrorWebExceptionHandler provided by Spring Boot for error handling (see reference documentation) is ordered at -1
the ResponseStatusExceptionHandler provided by Spring Framework is ordered at 0
So you can add #Order(-2) on your error handling component, to order it before the existing ones.
An error response should have standard payload info. This can be done by extending AbstractErrorWebExceptionHandler
ErrorResponse: Data Class
data class ErrorResponse(
val timestamp: String,
val path: String,
val status: Int,
val error: String,
val message: String
)
ServerResponseBuilder: 2 different methods to build an error response
default: handle standard errors
webClient: handle webClient exceptions (WebClientResponseException), not for this case
class ServerResponseBuilder(
private val request: ServerRequest,
private val status: HttpStatus) {
fun default(): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
status.value(),
status.name,
status.reasonPhrase)))
fun webClient(e: WebClientResponseException): Mono<ServerResponse> =
ServerResponse
.status(status)
.body(BodyInserters.fromObject(ErrorResponse(
Date().format(),
request.path(),
e.statusCode.value(),
e.message.toString(),
e.responseBodyAsString)))
}
GlobalErrorHandlerConfiguration: Error handler
#Configuration
#Order(-2)
class GlobalErrorHandlerConfiguration #Autowired constructor(
errorAttributes: ErrorAttributes,
resourceProperties: ResourceProperties,
applicationContext: ApplicationContext,
viewResolversProvider: ObjectProvider<List<ViewResolver>>,
serverCodecConfigurer: ServerCodecConfigurer) :
AbstractErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
applicationContext
) {
init {
setViewResolvers(viewResolversProvider.getIfAvailable { emptyList() })
setMessageWriters(serverCodecConfigurer.writers)
setMessageReaders(serverCodecConfigurer.readers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?): RouterFunction<ServerResponse> =
RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { response(it, errorAttributes) })
private fun response(request: ServerRequest, errorAttributes: ErrorAttributes?): Mono<ServerResponse> =
ServerResponseBuilder(request, status(request, errorAttributes)).default()
private fun status(request: ServerRequest, errorAttributes: ErrorAttributes?) =
HttpStatus.valueOf(errorAttributesMap(request, errorAttributes)["status"] as Int)
private fun errorAttributesMap(request: ServerRequest, errorAttributes: ErrorAttributes?) =
errorAttributes!!.getErrorAttributes(request, false)
}