Micronaut #Refreshable not working as expected - java

I'm trying to set up a micronaut service with a Consul configuration. When the service starts it successfully initializes the property of the bean with the value provided from the key-value store. However it doesn't reload when the value changes, even if the refresh is triggered manually.
Here's the bean:
#Refreshable
public class ConfigBean {
#Property(name = "welcome-message")
private String message = "doe!";
#PostConstruct
public void init() {
String text = this + "Scattered Clouds " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MMM/yy HH:mm.ss.SSS"));
System.out.println(text + "\r\n" + this.message);
}
}
And here is the simple controller endpoint:
#Controller("/p")
public class Controller {
#Inject
ApplicationContext applicationContext;
#Inject
private ConfigBean config;
#Get("/r")
#Produces(MediaType.TEXT_PLAIN)
public String refresh() {
applicationContext.publishEvent(new RefreshEvent());
return "Hello World";
}
#Get("/m")
#Produces(MediaType.TEXT_PLAIN)
public String message() {
return config.getMessage();
}
}
With this code what happens is
value is successfully resolved from config store and shown when navigating to localhost:8080/p/m
when getting localhost:8080/p/r for manual refresh interestingly enough the init() method of the config bean is called but the value in the message attribute is not updated
What is needed to re-trigger the update of the bean? Is there maybe an implicit call that can be used to purge a config-cache so it is fetched again?
The documentation is a bit sparse about this (Version 2.2.0 was used), thanks in advance!

Related

start() method of QuarkusTestResourceLifecycleManager is not updating the system properties

In a setup as described in the docs:
public class MyWireMockResource implements QuarkusTestResourceLifecycleManager {
WireMockServer wireMockServer;
#Override
public Map<String, String> start() {
wireMockServer = new WireMockServer(8090);
wireMockServer.start();
// create some stubs
return Map.of("some.service.url", "localhost:" + wireMockServer.port());
}
//...
}
How can I access the value returned in in the start() method.
The javadoc says:
A map of system properties that should be set for the running test
Why "should be"?
I would like to set some values that I can later used in my productive code.
I have tried: System.getProperty("some.service.url")
I have also tried:
#ConfigProperty(name = "some.service.url")
String serviceUrl;
But the value is not set.
Any help is appreciated.
The system properties mentioned in the javadoc is confusing. The returned map will as expected override the configurations.
It was not working for me to access it with the #ConfigProperty annotation because I was reading the value in the constructor and not in the method annotated with #PostConstruct.
#ApplicationScoped
public class SomeBean {
#ConfigProperty(name = "some.service.url")
String serviceUrl;
public SomeBean() {
// we are too early here, value is null:
System.out.println("value in constructor: " + serviceUrl);
}
#PostConstruct
void init() {
System.out.println("value in init: " + serviceUrl);
}
}

Bean failiing to initialize in a not deterministic way

I have a Spring Boot application that has the following bean:
#Configuration
public class ConfigStatus {
#Value("${olt.id}")
private int olt_id;
#Bean
Status createStatus() {
return new Status(olt_id);
}
}
I launch several containers with this application (20 to be exact) and in some of them the bean fails to be initialized and I don't understand why. The bean is successfuly initialized consistently in 18 of 20 containers. Memory is not an issued as the system I am using has 125gb of ram and at the time of this issue is has 90gb of free memory.
This is the class that makes use of the bean:
#DependsOn("createStatus")
#Service
public class ReceiveMessageHandler {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
private static final Gson converter = new Gson();
#Autowired private Status currentStatus;
public void handleMessage(String body) {
OltRequest request = converter.fromJson(body, OltRequest.class);
request.setStartedBeingProcessedAtOlt(new Date().getTime());
Response r = new Response(request.getId(), 200, new Date().getTime());
r.setRequestEnqueuedAtOlt(currentStatus.getEnqueuedAtWorkerTimes().get(request.getId())); // line that fails due to the bean being equal to null
r.setRequestDequeuedAtOlt(new Date().getTime());
log.info("Processing request: " + request.getId());
try {
Thread.sleep(request.getDuration());
} catch(InterruptedException e) {
e.printStackTrace();
}
request.setEndedBeingProcessedAtOlt(new Date().getTime());
log.info("Finished processing request: " + request.getId());
r.setEndedHandling(new Date().getTime());
Worker.send_response(r, request.getOriginMessage().getWorker());
}
}
The error messages sais that when the bean was accessed it was null.
In the environment I have another Spring Boot app with 30 running docker containers in which this problem does not occurr, ence my disbelief :D.
I believe you should do:
#Configuration
public class ConfigStatus {
#Bean
Status currentStatus(#Value("${olt.id}") int olt_id) {
return new Status(olt_id);
}
}
I'm not 100% confident if Spring prioritises #Value over #Bean in its instantiation order, so doing this guarantees the ordering. It may be that some of your instances are trying to load the bean before the value is there, so bails out on some NPE.
Also, you should be able to drop the line: #DependsOn("createStatus") in ReceiveMessageHandler.java. Spring runs #Configuration classes before #Service classes, and #Autowired matches on type.

Any way to declare happen-before relatioship in Spring Boot?

I have some validation code that should run on server startup and make sure various conditions are met so whoever deploys the server don't messes up the DB or start the server with bad security configurations etc. To do that I created a bean
#Component
public class ApplicationStartupConditionsValidationBean {
static class ServerInitializationError extends Error{
public ServerInitializationError(String msg){
super(msg);
}
}
private Environment springEnv;
private String datasourceURL;
private String ddlAuto;
private static final Logger logger = LogManager.getLogger();
#Autowired
public ApplicationStartupConditionsValidationBean(Environment springEnv) throws Exception {
this.springEnv = springEnv;
this.datasourceURL = springEnv.getProperty("spring.datasource.url");
this.ddlAuto = springEnv.getProperty("spring.jpa.hibernate.ddl-auto");
validateStartupConditions();
}
public boolean isDBLocal(){
return datasourceURL.startsWith("jdbc:postgresql://localhost:");
}
private String disallowedParamMsg(String optionName, String optionValue){
return "option " + optionName + "=" + optionValue + " not allowed in production";
}
private void reject(String msg) throws ServerInitializationError{
String rejectionMsg = "startup conditions validation failed with msg: " + msg;
logger.error(rejectionMsg);
throw new ServerInitializationError(rejectionMsg);
}
private void reject(String paramName, String paramValue) throws ServerInitializationError{
reject(disallowedParamMsg(paramName, paramValue));
}
private void validateDatasourceParams(){
if(!isDBLocal() &&
!ddlAuto.equals("validate")){
reject("ddl-auto", ddlAuto);
}
}
public void validateStartupConditions() throws Exception{
logger.info("validating startup conditions");
validateDatasourceParams();
// more validation logic...
logger.info("startup conditions validation succeeded, proceeding with boot");
}
}
The way I would have wanted to use this class is to define what beans this must come before. In the example here I would have wanted to make sure this bean would be created before the DataSource bean is created, so that "ddl-auto=create" doesn't slip in production. I Know about the #DependsOn annotation but I would have wanted to do the reverse, and declare that this bean #HappensBefore a list of other beans. Is there any way to do this?
Thanks!
To run code before "normal" beans get created, you can use a BeanFactoryPostProcessor.
To declaratively add additional dependencies among beans, you could also use a BeanPostProcessor, but that sounds needlessly cumbersome for your use case.

Caching lookups on application startup doesn't work

I am using Spring Boot 1.5.9 on Tomcat 9.0.2 and I am trying to cache lookups using spring #Cacheable scheduling a cache refresh job that runs on application startup and repeats every 24 hours as follows:
#Component
public class RefreshCacheJob {
private static final Logger logger = LoggerFactory.getLogger(RefreshCacheJob.class);
#Autowired
private CacheService cacheService;
#Scheduled(fixedRate = 3600000 * 24, initialDelay = 0)
public void refreshCache() {
try {
cacheService.refreshAllCaches();
} catch (Exception e) {
logger.error("Exception in RefreshCacheJob", e);
}
}
}
and the cache service is as follows:
#Service
public class CacheService {
private static final Logger logger = LoggerFactory.getLogger(CacheService.class);
#Autowired
private CouponTypeRepository couponTypeRepository;
#CacheEvict(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES, allEntries = true)
public void clearCouponsTypesCache() {}
public void refreshAllCaches() {
clearCouponsTypesCache();
List<CouponType> couponTypeList = couponTypeRepository.getCoupons();
logger.info("######### couponTypeList: " + couponTypeList.size());
}
}
the repository code:
public interface CouponTypeRepository extends JpaRepository<CouponType, BigInteger> {
#Query("from CouponType where active=true and expiryDate > CURRENT_DATE order by priority")
#Cacheable(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES)
List<CouponType> getCoupons();
}
later in my webservice, when trying to get the lookup as follows:
#GET
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
#Path("/getCoupons")
#ApiOperation(value = "")
public ServiceResponse getCoupons(#HeaderParam("token") String token, #HeaderParam("lang") String lang) throws Exception {
try {
List<CouponType> couponsList = couponRepository.getCoupons();
logger.info("###### couponsList: " + couponsList.size());
return new ServiceResponse(ErrorCodeEnum.SUCCESS_CODE, resultList, errorCodeRepository, lang);
} catch (Exception e) {
logger.error("Exception in getCoupons webservice: ", e);
return new ServiceResponse(ErrorCodeEnum.SYSTEM_ERROR_CODE, errorCodeRepository, lang);
}
}
The first call it gets the lookup from the database and the subsequent calls it gets it from the cache, while it should get it from the cache in the first call in the web service?
Why am I having this behavior, and how can I fix it?
The issue was fixed after upgrading to Tomcat 9.0.4
While it's not affecting the scheduled task per se, when refreshAllCaches() is invoked in the CacheService, #CacheEvict on clearCouponsTypesCache() is bypassed since it's invoked from the same class (see this answer). It will lead to cache not being purged before
List<CouponType> couponTypeList = couponTypeRepository.getCoupons();
is invoked. This means that the #Cacheable getCoupons() method will not query the database, but will instead return values from the cache.
This makes the scheduled cache refresh action to do its work properly only once, when the cache is empty. After that it's useless.
The #CacheEvict annotation should be moved to refreshAllCaches() method and add beforeInvocation=true parameter to it, so the cache is purged before being populated, not after.
Also, when using Spring 4 / Spring Boot 1.X, these bugs should be taken into consideration:
https://github.com/spring-projects/spring-boot/issues/8331
https://jira.spring.io/browse/SPR-15271
While this bug doesn't seem to affect this specific program, it might be a good idea to separate #Cacheable annotation from JpaRepository interface until migration to Spring 5 / Spring Boot 2.X.
#CacheEvict won't be invoked when called within the same service. This is because Spring creates a proxy around the service and only calls from "outside" go through the cache proxy.
The solution is to either add
#CacheEvict(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES, allEntries = true)
to refreshAllCaches too, or to move refreshAllCaches into a new service that calls ICacheService.clearCouponsTypeCache.

Java Spring Boot Configurations & Components

I'm trying to use a component to load up my configuration yml file.
However, it throws a Null Pointer exception and System.out shows 'null' for application.
However, when the same pattern is used to code up a #RestController, everything works fine. Why is #component not seeing my configurations??
#Component
#ConfigurationProperties(prefix = "myconf")
public class AppConfigJSON {
private String application;
private final String applicationConfigJSON;
Logger logger = LoggerFactory.getLogger(this.getClass());
private final ReadContext acRcJson;
public AppConfigJSON(){
String json = "";
try {
System.out.println("Application: " + this.application);
json = IOUtils.toString(this.getClass().getResourceAsStream("/myconfs/"+this.application+".json"));
} catch (IOException e) {
logger.error("Error reading JSON or app YML {}", e.getStackTrace());
}
this.applicationConfigJSON = json;
this.acRcJson = JsonPath.parse(this.getApplicationConfigJSON());
}
// These functions below set by configuration
public String getApplication()
{
return application;
}
public void setApplication(String application)
{
this.application = application;
}
}
In your constructor the application variable hasn't been initialized yet, in other words, Spring needs an instance first so it can apply it's magic. You need to move your constructor logic to a method annotated with #PostContruct so the application variable is set with the property value at that point.

Categories