Spring Boot Devtools: Detect automatic restart and apply configuration - spring

I want to set properties that apply only during the automatic restarts provided by Spring Boot developer tools. Is there a way to achieve this?
In other words, is there a way for some part of my code (maybe a configuration bean or a listener) to detect that a restart is under way?
In my specific use case I want to run some SQL scripts during the regular Spring Boot application startup, but not after Devtools has triggered a restart (so that my database state doesn't change during restarts).

Here's an idea:
It's confusing to explain, but you'll see with the code below. When Spring-Boot starts with devtools in its dependencies, it first starts and then immediately restarts a first time trough devtools. You could dynamically add command line argument to track the restarts and change the Spring profile being used when devtools is restarting:
#SpringBootApplication
public class App {
public static void main(String[] args)
throws ParseException, IOException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
String profile = "";
//(1) Very first time - Spring Boot doesn't really load, it only kick start devtool then restarts.
if (args.length == 0) {
args = new String[] { "spring-boot-loaded" };
profile = "no-devtools-yet";
}
//(2) The first time the application loads with devtools
else if (args.length == 1 && args[0].equals("spring-boot-loaded")) {
args = new String[] { "spring-boot-loaded", "devtools-loaded" };
profile = "devtools";
Field argsField = Restarter.class.getDeclaredField("args");
argsField.setAccessible(true);
argsField.set(Restarter.getInstance(), args);
}
//(3) This is the first restart - You don't want to re-initialized the database here
else {
profile = "devtools-reloaded";
}
new SpringApplicationBuilder() //
.sources(App.class)//
.profiles(profile) //
.run(args);
}
}
The sketchy part is that the Restarter keeps the original arguments (which would be "no-devtools-yet" in this example). So when devtools first starts, you need to replace the Restarter's internal args

Related

StreamsException when running #QuarkusTest with kafka streams in quarkus development mode

Quarkus 2.15.3,
Problem
When running my test case via gradle build or IDE (IntelliJ), everything is fine, test OK.
When starting application in Quarkus Dev Mode, test fails with following exception:
Caused by: org.apache.kafka.streams.errors.StreamsException: Unable to initialize state, this can happen if multiple instances of Kafka Streams are running in the same state directory (current state directory is [C:\Users\username\AppData\Local\Temp\kafka-streams\my-service]
at org.apache.kafka.streams.processor.internals.StateDirectory.initializeProcessId(StateDirectory.java:191)
TopologyProducer, builds a simple topology, creating only a global state store
#ApplicationScoped
public class TopologyProducer {
#ConfigProperty(name = "sometopic")
String SOME_TOPIC;
#Produces
public Topology buildTopology() {
final var builder = new StreamsBuilder();
var keySerDe = new Serdes.StringSerde();
var valueSerDe = new ObjectMapperSerde<>(MyValue.class);
builder.globalTable(SOME_TOPIC, Consumed.with(keySerDe, valueSerDe), Materialized.as(MyStateStoreService.STORE_NAME));
return builder.build();
}
}
MyStateStoreService, reads a value from the global state store by key
#ApplicationScoped
public class MyStateStoreService {
public static final String STORE_NAME = "MyStore";
#Inject
KafkaStreams streams;
public MyValue valueById(String id) {
// Taken this from official documentation but tried with try-catch and streams.start() in catch-block too. Same result.
while (true) {
try {
return (MyValue) streams.store(StoreQueryParameters
.fromNameAndType(STORE_NAME, QueryableStoreTypes.keyValueStore())).get(id);
} catch (InvalidStateStoreException e) {
// Ignore, store not ready yet
}
}
}
}
MyStateStoreServiceTest, should test, that MyStateStoreService returns the newest version of a value for a specific key
#QuarkusTest
#QuarkusTestResource(KafkaCompanionResource.class)
public class MyStateStoreServiceTest {
#InjectKafkaCompanion
KafkaCompanion companion;
#ConfigProperty(name = "sometopic")
String SOME_TOPIC;
#Inject
MyStateStoreService myStateStoreService;
#BeforeEach
public void setUp() {
companion.registerSerde(String.class, new Serdes.StringSerde());
companion.registerSerde(MyValue.class, new ObjectMapperSerde<>(MyValue.class));
}
#Test
public void valueUpdatesCorrectly() {
// Given
var key= "valueUpdatesCorrectly";
var value = new MyValue("dummyValue");
var valueUpdated= new MyValue("dummyValueUpdated");
// When
companion
.produce(String.class, MyValue.class)
.fromRecords(
new ProducerRecord<>(SOME_TOPIC, key, value),
new ProducerRecord<>(SOME_TOPIC, key, valueUpdated))
.awaitCompletion();
// Then
assertEquals(valueUpdated, myStateStoreService.valueById(key));
}
}
By now, in my application there was no problem in using Dev Mode, #QuarkusTestResource, KafkaCompanion and a Topology without state stores together. Other tests, using this approach (taken from Testing using a Kafka broker), run fine.
As soon as this global state store is added to my topology, I get above exception ONLY during continuous tests in dev mode (for readability reasons I removed the code for the rest of my topology from TopologyProducer)
Is there something wrong in my test setup, misunderstanding of dev mode and kafka companion from my site, or something else I missed?

Spring Boot auto-configured metrics not arriving to Librato

I am using Spring Boot with auto-configure enabled (#EnableAutoConfiguration) and trying to send my Spring MVC metrics to Librato. Right now only my own created metrics are arriving to Librato but auto-configured metrics (CPU, file descriptors, etc) are not sent to my reporter.
If I access a metric endpoint I can see the info generated there, for instance http://localhost:8081/actuator/metrics/system.cpu.count
I based my code on this post for ConsoleReporter. so I have this:
public static MeterRegistry libratoRegistry() {
MetricRegistry dropwizardRegistry = new MetricRegistry();
String libratoApiAccount = "xx";
String libratoApiKey = "yy";
String libratoPrefix = "zz";
LibratoReporter reporter = Librato
.reporter(dropwizardRegistry, libratoApiAccount, libratoApiKey)
.setPrefix(libratoPrefix)
.build();
reporter.start(60, TimeUnit.SECONDS);
DropwizardConfig dropwizardConfig = new DropwizardConfig() {
#Override
public String prefix() {
return "myprefix";
}
#Override
public String get(String key) {
return null;
}
};
return new DropwizardMeterRegistry(dropwizardConfig, dropwizardRegistry, HierarchicalNameMapper.DEFAULT, Clock.SYSTEM) {
#Override
protected Double nullGaugeValue() {
return null;
}
};
}
and at my main function I added Metrics.addRegistry(SpringReporter.libratoRegistry());
For the Librato library I am using in my compile("com.librato.metrics:metrics-librato:5.1.2") build.gradle. Documentation here. I used this library before without any problem.
If I use the ConsoleReporter as in this post the same thing happens, only my own created metrics are printed to the console.
Any thoughts on what am I doing wrong? or what am I missing?
Also, I enabled debug mode to see the "CONDITIONS EVALUATION REPORT" printed in the console but not sure what to look for in there.
Try to make your MeterRegistry for Librato reporter as a Spring #Bean and let me know whether it works.
UPDATED:
I tested with ConsoleReporter you mentioned and confirmed it's working with a sample. Note that the sample is on the branch console-reporter, not the master branch. See the sample for details.

How to live reload without restart on additional static resources in spring boot?

I'm attempting to set a new resource location on a spring boot project that is enabled for live reloads but not for application restarts. I'm able to add additional resources, but any change made in the new directory causes the application to restart.
The documentation on this seems light. I must be misinterpreting something.
My layout looks like this:
- src
- main
- java
- resources
- static
- web
- dist
And my application class looks like this:
#Bean
WebMvcConfigurer configurer () {
return new WebMvcConfigurerAdapter() {
#Override
public void addResourceHandlers (ResourceHandlerRegistry registry) {
registry.addResourceHandler("/dist/**")
.addResourceLocations(""file:web/dist/"")
.setCachePeriod(0);
}
};
}
public static void main(String[] args)
{
SpringApplication app = new SpringApplication(Application.class);
app.addListeners(new PropertyLogger());
Properties properties = new Properties();
properties.setProperty("spring.devtools.restart.additional-paths", "web/dist/");
properties.setProperty("spring.devtools.restart.additional-exclude", "/dist/**");
app.setDefaultProperties(properties);
app.run(args);
}
I've read through several similar questions and this seems to be the best I can do. Is it possible to enable live reload on dist without a full application restart?
By the way, my IDE is IntelliJ. I'm beginning to wonder if IntelliJ needs to exclude the dist directory. I'll followup if that's the case.
I've beaten this to death and have finally found a working solution.
Properties properties = new Properties();
properties.setProperty("spring.devtools.restart.additional-paths", "web/");
properties.setProperty("spring.devtools.restart.additional-exclude", "dist/**");
app.setDefaultProperties(properties);
Defining the web directory as an additional path combined with the pattern used for additional exclude does the trick.
I won't mark this as accepted unless there are upvotes to back my conclusion.

One Tomcat two spring application (war) two seperate logging configurations

As mentioned in the title I have two applications with two different logging configurations. As soon as I use springs logging.file setting I can not seperate the configurations of both apps.
The problem worsens because one app is using logback.xml and one app is using log4j.properties.
I tried to introduce a new configuration parameter in one application where I can set the path to the logback.xml but I am unable to make the new setting work for all logging in the application.
public static void main(String[] args) {
reconfigureLogging();
SpringApplication.run(IndexerApplication.class, args);
}
private static void reconfigureLogging() {
if (System.getProperty("IndexerLogging") != null && !System.getProperty("IndexerLogging").isEmpty()) {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
System.out.println("SETTING: " + System.getProperty("IndexerLogging"));
System.out.println("SETTING: " + System.getProperty("INDEXER_LOG_FILE"));
context.reset();
configurator.doConfigure(System.getProperty("IndexerLogging"));
} catch (JoranException je) {
System.out.println("FEHLER IN CONFIG");
}
logger.info("Entering application.");
}
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
reconfigureLogging();
return application.sources(applicationClass);
}
The above code works somehow. But the only log entry which is written to the logfile specified in the configuration, which ${IndexerLogging} points to, is the entry from logger.info("Entering application."); :(
I don't really like to attach that code to every class which does some logging in the application.
The application has to be runnable as tomcat deployment but also as spring boot application with integrated tomcat use.
Any idea how I can set the path from ${IndexerLogging} as the path to read the configuration file when first configuring logging in that application?
Take a look at https://github.com/qos-ch/logback-extensions/wiki/Spring you can configure the logback config file to use.

Combined Spring Boot integration & web application won't start

I have created two Spring Boot applications. One uses Spring Integration to read stuff from several feeds and the other combines the retrieved data from the feeds on a simple web page.
Currently these two exist as separate apps but I want to combine the two in a single application. The integration "application" is nothing more than a integration.xml and the other one is a couple of RestControllers.
In my Application class the integration application has the following in the main method:
ConfigurableApplicationContext ctx = new SpringApplication("/feed/integration.xml").run(args);
System.out.println("Hit Enter to terminate");
final int read = System.in.read();
System.out.println("Closing! (" + read + ")");
ctx.close();
The web application has
SpringApplication.run(MyWebApplication.class, args);
I've tried to combine the two resulting in:
try {
ConfigurableApplicationContext ctx = new SpringApplication("/feed/integration.xml").run(TrailerListApplication.class, args);
System.out.println("Hit Enter to terminate");
System.in.read();
ctx.close();
} catch (IOException e) {
e.printStackTrace();
}
but that only starts the web application. The feeds don't get initialized. How can I make sure both components start and keep running?
Add #ImportResource("classpath:/feed/integration.xml") to MyWebApplication and just use
SpringApplication.run(MyWebApplication.class, args);

Resources