Commons library containing reusable patterns, extensions, properties, beans, and objects for Spring and Spring Boot
- Java 21
- Spring Boot 3
implementation("io.opengood.commons:spring-commons:VERSION")<dependency>
<groupId>io.opengood.commons</groupId>
<artifactId>spring-commons</artifactId>
<version>VERSION</version>
</dependency>Note: See Release version badge above for latest version.
Note: All examples are provided in Kotlin
EXPERIMENTAL
Sometimes one needs to dynamically refresh a Spring bean at runtime without restarting the application container. For example, a Spring bean may need to fetch updated data from a database and cache the results inside a bean.
The BeanRefresher component allows one to specify a bean name and its fully
qualified class type to trigger a refresh of a bean without restarting the
application container. On the next request for dependencies that use the bean,
the updated instance will be injected.
To enable the BeanRefresher, add the following configuration to
application.yml:
spring-commons:
bean:
refresh:
enabled: true
controller:
enabled: trueOne can use the BeanRefresher Kotlin/Java API to programmatically to refresh
a bean:
val oldBean = applicationContext.getBean("greetingBean") as GreetingBean
beanRefresher.refresh(
BeanRefreshConfig(
beanName = "greetingBean",
classType = GreetingBean::class.java,
)
)
val newBean = applicationContext.getBean("greetingBean") as GreetingBean
newBean shouldNotBe oldBeanA REST controller endpoint is also provided to allow HTTP(S) refresh of Spring beans:
Request:
POST http://localhost:8080/spring-commons/bean/refresh
Accept: application/json
Content-Type: application/json
{"beanName":"greetingBean", "classType":"app.bean.GreetingBean"}Response:
{"message":"Successfully refreshed bean 'greetingBean'"}By default, BeanRefresher only reloads a Spring bean's definition. Sometimes,
specific beans need to be recreated. To do so with BeanRefresher, add
recreateBean = true to either the BeanRefresherConfig or to request JSON,
for Kotlin/Java API and REST API, respectively.
Examples:
BeanRefreshConfig(
beanName = "greetingBean",
classType = GreetingBean::class.java,
recreateBean = true,
)POST http://localhost:8080/spring-commons/bean/refresh
Accept: application/json
Content-Type: application/json
{"beanName":"greetingBean", "classType":"app.bean.GreetingBean", "recreateBean": true}By default, using WebClient does not provide logging of request and response
information. Functions are provided to simplify this:
logRequest will log debug:
- Request
- Method
- URI
- Headers
logResponse will log debug:
- Response
- Status Code
- Headers
Example:
import io.opengood.commons.spring.webclient.logRequest
import io.opengood.commons.spring.webclient.logResponse
@Configuration
class WebClientConfig {
@Bean
fun webClient(): WebClient {
return WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultHeader(
HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE
)
.filters { exchangeFilterFunctions ->
exchangeFilterFunctions.add(logRequest(log))
exchangeFilterFunctions.add(logResponse(log))
}
.build()
}
companion object {
@Suppress("JAVA_CLASS_ON_COMPANION")
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
}
}When one needs to log details of Spring WebClient requests and responses, one
can configure the Netty HttpClient to wiretap and log request and response
details at level DEBUG.
A loggingWebClientBuilder Spring bean is provided that is auto configured when
the following configuration is added to application.yml:
spring-commons:
web-client:
logging:
enabled: trueThen simply create a WebClient bean using the builder:
@Configuration
class TestWebClientConfig {
fun webClient(
@Qualifier("loggingWebClientBuilder") webClientBuilder: WebClient.Builder
) =
webClientBuilder
.baseUrl("http://localhost:8080")
.defaultHeader(
HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE
)
.build()
}In order for Netty to write logs, its logger much be configured at level DEBUG
in application.yml:
logging:
level:
reactor.netty.http.client.HttpClient: DEBUGAny WebClient requests and responses used by the bean will be logged similar
to as follows:
Request:
15:27:02.060 [qtp1001320766-66] DEBUG org.eclipse.jetty.server.HttpChannel MDC= - REQUEST for //localhost:11729/greeting?firstName=John on HttpChannelOverHttp@34998901{s=HttpChannelState@22052be6{s=IDLE rs=BLOCKING os=OPEN is=IDLE awp=false se=false i=true al=0},r=1,c=false/false,a=IDLE,uri=//localhost:11729/greeting?firstName=John,age=0}
GET //localhost:11729/greeting?firstName=John HTTP/1.1
User-Agent: ReactorNetty/1.0.14
Host: localhost:11729
Accept: */*
Content-Type: application/jsonResponse:
15:27:02.152 [reactor-http-nio-2] DEBUG i.o.c.s.w.LoggingWebClientConfig$$EnhancerBySpringCGLIB$$498409ee MDC= - [affd975a-1, L:/127.0.0.1:63371 - R:localhost/127.0.0.1:11729] READ: 252B HTTP/1.1 200 OK
Content-Type: application/json
Matched-Stub-Id: 62600bcd-4064-40a7-8547-bdc3af2577b5
Vary: Accept-Encoding, User-Agent
Transfer-Encoding: chunked
Server: Jetty(9.4.44.v20210927)
27
{"firstName":"John","lastName":"Smith"}
0Note: This data should only be used for diagnostic purposes and should be disabled in production.
Spring supports turning YAML configuration files into properties beans but only
for application*.yml files. Custom YAML files are not supported. To enable
this, use the YamlPropertySourceFactory on @ConfigurtionProperties bean
classes:
Example:
import io.opengood.commons.spring.property.YamlPropertySourceFactory
@Configuration
@ConfigurationProperties(prefix = "app")
@ConstructorBinding
@PropertySource(
value = ["classpath:app-properties.yml"],
factory = YamlPropertySourceFactory::class
)
data class AppProperties(
val properties: Map<String, String> = HashMap()
)YAML file app-properties.yml:
app:
properties:
foo: bar
baz: paz