Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring boot hot reload #607

Merged
merged 1 commit into from
Mar 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/spring-boot-hot-reload/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>spring-boot-sample-devtools</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1
45 changes: 45 additions & 0 deletions examples/spring-boot-hot-reload/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Spring Boot example project

This example demonstrates Spring Boot running inside a Docker container (managed by a local Kubernetes cluster) with Spring Boot's dev tools enabled. We'll walk through live-restarting a Spring Boot service on compilation via Garden's hot reload functionality.

## Prerequisites

You'll need to have Java and [Maven](https://maven.apache.org/install.html) installed (follow the appropriate installation instructions for your platform).

## Overview

This project consists of a single module (in the `devtools` directory), which is a minimally modified version of the [Spring Boot devtools sample project found here](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-devtools).

We've changed the parent pom from `spring-boot-samples` to `spring-boot-starter-parent`, and added a dependency on `spring-boot-starter-actuator` (to enable the health check endpoint for k8s readiness and liveness probes). We've also removed the artificial 5 second delay that was added by the `slowRestart` method in `MyController`.

## Usage

First, run `mvn compile` in the `devtools` directory. This will download the service's dependencies and compile it. Afterwards, you should see the `target` directory appear (under `devtools`).

Subsequent calls to `mvn compile` will be much faster, since the dependencies will have been cached by Maven.

You can now deploy the service with hot reloading enabled e.g. by running

```
garden deploy --hot=devtools
```

This should produce output that's something like this:

```
Deploy 🚀

✔ devtools → Building devtools:v-c6b9091207... → Done (
took 0.4 sec)
✔ devtools → Deploying version v-c6b9091207... → Done (
took 67 sec)
→ Ingress: http://spring-boot-hot-reload.local.app.garden
🌻 Garden dashboard and API server running on http://localhost:50934

🕑 Waiting for code changes
```
Open the ingress URL (http://spring-boot-hot-reload.local.app.garden above) in a web browser.

Now, change the value of the `MESSAGE` constant in `devtools/src/main/java/sample/devtools/Message.java` to something else than `"Message"`, and run `mvn compile` (again in the `devtools` directory).

This should trigger a hot reload. Refresh the the browser tab opened earlier, and you should see the change you made reflected in the header text.
1 change: 1 addition & 0 deletions examples/spring-boot-hot-reload/devtools/.gardenignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/main/java/**/*
1 change: 1 addition & 0 deletions examples/spring-boot-hot-reload/devtools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
16 changes: 16 additions & 0 deletions examples/spring-boot-hot-reload/devtools/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM maven:3.6.0-jdk-11-slim
WORKDIR /app

COPY pom.xml /app/pom.xml
RUN mvn dependency:resolve

RUN mkdir -p /app/target
COPY src /app/src
# RUN mvn install

ENV JVM_OPTS "-XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1"
RUN mvn install
# RUN mvn compile
CMD ["mvn", "spring-boot:run"]

EXPOSE 8080
21 changes: 21 additions & 0 deletions examples/spring-boot-hot-reload/devtools/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module:
description: Spring Boot devtools-sample module
type: container
name: devtools
hotReload:
sync:
- target: /app/target
source: target
services:
- name: devtools
ports:
- name: http
containerPort: 8080
servicePort: 80
healthCheck:
httpGet:
path: /actuator/health
port: http
ingresses:
- path: /
port: http
49 changes: 49 additions & 0 deletions examples/spring-boot-hot-reload/devtools/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<artifactId>spring-boot-sample-devtools</artifactId>
<name>Spring Boot Developer Tools Sample</name>
<description>Spring Boot Developer Tools Sample</description>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency> <!-- Added for Garden example: For Kubernetes health checks -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.devtools;

public final class Message {

/**
* Sample message.
*/
public static String MESSAGE = "Message";

private Message() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.devtools;

import java.util.Date;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MyController {

@GetMapping("/")
public ModelAndView get(HttpSession session) {
Object sessionVar = session.getAttribute("var");
if (sessionVar == null) {
sessionVar = new Date();
session.setAttribute("var", sessionVar);
}
ModelMap model = new ModelMap("message", Message.MESSAGE)
.addAttribute("sessionVar", sessionVar);
return new ModelAndView("hello", model);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.devtools;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SampleDevToolsApplication {

public static void main(String[] args) {
SpringApplication.run(SampleDevToolsApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Enable remote support, for local development you don't need this line
spring.devtools.remote.secret=secret
management.endpoint.health.enabled=true
management.endpoints.web.exposure.include=health
# management.endpoints.web.exposure.include=health,info
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public file
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
h1 {
color: green;
}

.content {
font-family: sans-serif;
border-top: 3px solid red;
padding-top: 30px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
</head>
<body>
<h1 th:text="${message}">Header</h1>
<div class="content">
<h2 th:text="${sessionVar}">Session Var</h2>
Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Cras ut fringilla augue, quis dictum
turpis. Sed tincidunt mi vel euismod viverra. Nulla facilisi.
Suspendisse mauris dolor, egestas ac leo at, porttitor ullamcorper
leo. Suspendisse consequat, justo ut rutrum interdum, nibh massa
semper dui, id sagittis tellus lectus at nibh. Etiam at scelerisque
nisi. Quisque vel eros tempor, fermentum sapien sed, gravida neque.
Fusce interdum sed dolor a semper. Morbi porta mauris a velit laoreet
viverra. Praesent et tellus vehicula, sagittis mi quis, faucibus urna.
Ut diam tortor, vehicula eget aliquam eget, elementum a odio. Fusce at
nisl sapien. Suspendisse potenti.
</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.devtools;

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Integration tests for {@link SampleDevToolsApplication}.
*
* @author Andy Wilkinson
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SampleDevToolsApplicationIntegrationTests {

@Autowired
private TestRestTemplate restTemplate;

@Test
public void testStaticResource() {
ResponseEntity<String> entity = this.restTemplate
.getForEntity("/css/application.css", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("color: green;");
}

@Test
public void testPublicResource() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/public.txt",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("public file");
}

@Test
public void testClassResource() {
ResponseEntity<String> entity = this.restTemplate
.getForEntity("/application.properties", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}

}
6 changes: 6 additions & 0 deletions examples/spring-boot-hot-reload/garden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
project:
name: spring-boot-hot-reload
environments:
- name: local
providers:
- name: local-kubernetes