良い感じでした。ソースは以下。
https://github.com/af-not-found/spring-boot-example1
spring-boot-sample-tomcat-archetypeでプロジェクト生成して、起動とHello worldを確認しました。素の状態だと2秒程度で起動しましたが、色々追加したので5秒近くかかるようになってしまいました。
Started SpringBootApplication in 4.115 seconds (JVM running for 4.558)
archetypeのバージョンが1.0.2だったので、1.2.0.RELEASEに変更。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.0.RELEASE</version>
</parent>
あと、maven-compiler-pluginを追加して1.8を指定。
EclipseからDebugで起動すればHot swappingされます。これは便利です。普通のRunだと駄目でした。
二重起動すると、"Address already in use: bind"で怒られます。そりゃそうですね。うっとうしいので改善します。
まず、actuatorとsecurityを追加。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
application.propertiesに設定を追加して・・・
management.port=8081
management.security.enabled=false
endpoints.sensitive=false
endpoints.shutdown.enabled=true
# これを入れないと、アプリ全体にBASIC認証がかかってしまう
security.basic.path=/_admin/**
エントリポイントとなるApplicationクラスを以下のようにしました。
public static void main(String[] args) throws Exception {
shutdownAndRun(SpringBootApplication.class, args); //{
}
protected static ConfigurableApplicationContext
shutdownAndRun(Class<?> clazz, String[] args) {
try {
return SpringApplication.run(clazz, args);
}
catch (Exception e) {
// java.net.BindException: Address already in use: bind
if ("Tomcat connector in failed state".equals(e.getMessage())) {
HttpURLConnection connection = null;
try {
connection = (HttpURLConnection)
new URL("http://localhost:8081/shutdown").openConnection();
connection.setRequestMethod("POST");
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
logger.info("Shutdown OK");
// launch again
return SpringApplication.run(clazz, args);
}
else {
throw new IllegalStateException("Invalid response code : " + responseCode);
}
}
catch (Exception e2) {
throw new IllegalStateException("Shutdown failed", e2);
}
finally {
connection.disconnect();
}
}
else {
throw e;
}
}
}
"Address already in use: bind"な例外で失敗したら、actuatorでshutdownして、もう一回起動を試みるという方法です。
Springイチオシ?のテンプレートエンジンであるthymeleafを使ってみます。例によってpom.xml追加。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
次にControllerを修正。
@Controller
public class TopController {
@Autowired
private HelloWorldService helloWorldService;
@RequestMapping("/")
public ModelAndView top(
@RequestParam(value = "param1", required = false) String param1) {
Map<String, String> model = new HashMap<String, String>();
model.put("message", helloWorldService.getHelloMessage());
model.put("param1", "param1 is " + param1);
return new ModelAndView("top", model);
}
}
ModelAndViewがキモ。top.htmlにMapが渡ります。
最後にsrc/main/resources/templates/top.htmlを追加。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout">
<head>
<title th:text="${@environment.getProperty('spring.application.name')}">title</title>
<link rel="stylesheet" type="text/css" href="/main.css" th:href="@{/main.css}" />
</head>
<body>
<div class="header" th:text="${@environment.getProperty('spring.application.name')}">header</div>
<div>
message : <span th:text="${message}">message content</span>
</div>
<div>
<div th:if="${#strings.isEmpty(param1)}">param1 is empty</div>
<div th:if="${not #strings.isEmpty(param1)}">
<span th:text="${param1}">param1 content</span>
</div>
</div>
<div>
<div th:if="${@environment.getActiveProfiles().length == 0}">
environment is empty
</div>
<div th:if="${@environment.getActiveProfiles().length > 0}">
environment :
<span th:text="${#strings.arrayJoin(@environment.getActiveProfiles(),',')}">
environment
</span>
</div>
</div>
<div>
os name : <span th:text="${@systemProperties['os.name']}">os.name</span>
</div>
</body>
</html>
Strutsでいう"html:rewrite"は、th:href="@{/xxx}"
みたいに書くようです。
application.propertiesの値は、${@environment.getProperty('xxx')}
で取れました。便利。
システムプロパティーは${@systemProperties['xxx']}
です。
elseが書けないのはちょっとびっくりですね。switchは書けるので、まあいいかな。
で、こんな感じになりました。
イイネ!
ほぼarchetypeのままですが。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:0")
@DirtiesContext
public class TopControllerTests {
@Value("${local.server.port}")
private int port;
@Test
public void testTop() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity("http://localhost:" + this.port, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertThat(entity.getBody(), containsString("Hello World"));
}
}
良いと思います。
mvn package
で終わり。
作成されたsboot1-0.0.1-SNAPSHOT.jarは、15.1MBとなりました。それなりに大きいですね。