afnf.net

Spring Bootとthymeleafを触ってみた

Spring Boot thymeleaf 2015/01/04 19:33

良い感じでした。ソースは以下。

https://github.com/af-not-found/spring-boot-example1

Spring Boot 導入

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を指定。

Hot swapping

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して、もう一回起動を試みるという方法です。

thymeleaf 導入

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は書けるので、まあいいかな。


で、こんな感じになりました。

20150104_spring_boot

イイネ!

Integration Test

ほぼ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となりました。それなりに大きいですね。

TODO

  • thymeleafのlayout(テンプレート化)を試す →やった
  • logback.xmlがうまく適用されないので調査 →諦め気味
  • SeleniumとJaCoCoを試す →やった
  • Spring Dataを触ってみる →後回し
  • blog-java1を移植する →やった
Spring Boot thymeleaf 2015/01/04 19:33
comments (0)

blog-java2 engine (build:2017-09-13 21:46 JST)