20190909のJavaに関する記事は11件です。

ユーザの入力値をServiceクラスに渡すときに考えたこと

ユーザの入力値をServiceクラスに渡すときに考えたこと

結論

入力値を受け取るFormクラスとEntityの変換がしんどいので、解決策を4つ考えました。

  1. 入力値の一つ一つをServiceのメソッドの引数にする
  2. 意味のある単位でオブジェクトにする
  3. ServiceのメソッドをFormクラスにしてしまう
  4. Serviceの引数をインターフェースにする

使えそうな解決策は2と4かなと思っています。今回は社内の文化も踏まえて4で解決しました。

環境

  • Java 11
  • SpringBoot
  • Gradle

前提:入力値を受け取るFormクラスとEntityの変換がしんどい

以下のようにFormクラス内で永続化するためのEntityとの変換をしていました。

PurchaseForm.java
@Data
public class PurchaseForm {
  @NotNull
  private Integer commodityId;
  @NotNull
  private Integer count;

  /**
   * 入力値を元にEntityを生成をする
   */
  public Purchase toEntity() {
    // 省略
  }
}

シンプルな変換であれば全く問題がないのですが、以下のようなパターンだと死ねる。

変換対象のクラスが複雑なクラスの構造のとき

Entityに変換するためのプログラムのコード量が増えると、ソースコードの見通しが悪くなる。特に生成対象のクラスの子クラスまで生成しようとするとかなり苦しいです。わたしの関わるプロジェクトでは主キーの発番をAUTO INCREMENTに頼ることが多いので、必然的に主キーと外部キーをServiceクラスなどで補う必要があります。Formで一部を初期化して残りをServiceクラスで初期化するなど、一つの関心事が複数のクラスに分断され、追いづらいソースコードになってしまいます。

PurchaseForm.java
@Data
public class PurchaseForm {
  // 省略
  /**
   * 入力値を元にEntityを生成をする
   */
  public Purchase toEntity() {
    // こ
    // こ
    // が
    // め
    // っ
    // ち
    // ゃ
    // 長
    // い
    // と
    // 結
    // 構
    // つ
    // ら
    // い
  }
}

Entity生成に必要な引数が多いと苦しい

ここまで引数に永続化されたオブジェクトが必要なら、もはやFormクラスで変換をすべきでないのでは。

PurchaseForm.java
@Data
public class PurchaseForm {
  // 省略
  /**
   * 入力値を元にEntityを生成をする
   * 大量の永続化されたオブジェクトを引数にとるなら、ここでEntityを生成する必要があるのか疑問である。
   */
  public Purchase toEntity(Hoge hoge, Fuga Futa, ... etc) {
    // 省略
  }
}

考えうる解決策

1. 入力値の一つ一つをServiceのメソッドの引数にする

これ試したけど苦しい。なぜならFormの入力値が増えれば増えるほど引数が増えてしまうから。

PurchaseController.java
@Controller
@RequireArgsConstructor
public class PurchaseController {
  public final PurchaseService purchaseService;
  public ModelAndView create(@ModelAttribute @Valid PurchaseForm form, BindingResult result) {
    // check

    // Ah...!
    purchaseService.purchase(form.input1, form.input2, form.input3, form.input4, form.input5,...);
  }
}

2. 意味のある単位でオブジェクトにする

PurchaseController.java
@Controller
@RequireArgsConstructor
public class PurchaseController {
  public final PurchaseService purchaseService;
  public ModelAndView create(@ModelAttribute @Valid PurchaseForm form, BindingResult result) {
    // check

    purchaseService.purchase(new Hoge(form.input1, form.input2), new Fuga(form.input3, form.input4, form.input5),...);
  }
}

これの利点は引数のオブジェクトに対して事前条件を設定して値の整合性を保証できることです。事前条件はコンストラクタでチェックします。

3. ServiceのメソッドをFormクラスにしてしまう

めっちゃシンプルなんだけどめっちゃダメパターンきた!依存の方向がForm←Serviceになってしまうのが苦しい。Formは画面の仕様と密結合なので業務ロジック側がFormに依存するのは避けたいです。業務ロジックが変わっていないのに画面仕様が変わったせいで、Serviceクラスを変更しなくてはならないのはおかしいです。

PurchaseController.java
@Controller
@RequireArgsConstructor
public class PurchaseController {
  public final PurchaseService purchaseService;
  public ModelAndView create(@ModelAttribute @Valid PurchaseForm form, BindingResult result) {
    // check

    // Fuck...!
    purchaseService.purchase(form);
  }
}

これを4番目の案で解決しました。

4. Serviceの引数をインターフェースにする

まずドメインに近いパッケージでServiceと引数のinterfaceを定義しました。

PurchaseService.java
@Service
public class PurchaseService {
  public void purchase(PurchaseRequest request) {}
}
PurchaseRequest.java
public interface PurchaseRequest {
  String getInput1();
  Integer getInput2();
  // etc...
}

その上で値のバリデーションは別の実装クラスに書きました。

PurchaseForm.java
public class PurchaseForm implements PurchaseRequest {
  @NotEmpty
  private String input1;
  @NotNull
  private Integer input2;
  public String getInput1() {
    return input1;
  }
  public Integer getInput2() {
    return input2;
  }
}

この実装方法の嬉しいポイントは、ドメインに近い層でServiceクラスで使う値を宣言するので画面側の変更に強いです。もちろん業務の概念としてInputとOutputが変わるのであれば見直す必要はありますが、ある程度の違いは画面側でコントロールすることが可能です。

補足

いっそのことドメイン側で定義した型に対して画面からの入力値をバインドするというのも良いと思います。それが最適解として語られることが多いような気がします。上記は弊社のプロジェクトでドメイン側のエンティティにバリデーションを実装したり、値の型を意味的に捉えて実装したりする文化がない中で行った解決策です。

思わぬ副次効果

なんとその後、購入商品と個数の情報を元に購入金額を計算するAPIを作成することになりました。PurchaseService#calculatePrice(CalculatePriceRequest)というメソッドを定義し、PurchaseRequestCalculatePriceRequestを継承させれば、PurchaseRequest型の引数でも購入金額の計算を容易に行えます。そりゃ商品購入のリクエストには商品と個数の情報があるので、これらをもとに購入金額を計算できるのは至極当然なわけですけどね。

最後に

色々試行錯誤をしているところですが、上手くいったパターンとか上手くいかなかったパターンを分析して、実装方法の手札を増やせればなって感じです。特に私はWEBアプリケーションを開発することが多いので、画面とのIOは色々考えていきたいところです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot で @ControllerAdvice, @ExceptionHandler, HandlerExceptionResolver を利用して例外を捕捉する

概要

  • Spring Boot アプリケーションにて、コントローラークラスで発生した例外を捕捉する
  • @ControllerAdvice を付与したクラスにて、@ExceptionHandler を付与したメソッドで例外クラスごとに捕捉する
  • @ExceptionHandler を付与したメソッドで処理しない例外は、HandlerExceptionResolver を implements したクラスで捕捉する

今回の動作確認環境

  • OpenJDK 11.0.2
  • Spring Boot 2.1.7
  • Spring Web MVC 5.1.9

ソースコード一覧

├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── my
        │               ├── MyApplication.java
        │               ├── MyController.java
        │               ├── MyControllerAdvice.java
        │               ├── MyException.java
        │               └── MyHandlerExceptionResolver.java
        └── resources
            └── templates
                └── myview.html

MyApplication.java

Spring Boot 起動クラス。

package com.example.my;

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

@SpringBootApplication
public class MyApplication {

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

MyException.java

今回の動作確認用に用意したシンプルな例外クラス。

package com.example.my;

public class MyException extends Exception {
}

MyController.java

ルーティングを処理するコントローラークラス。
http://localhost:8080/myexception へアクセスが来たら MyException 例外を発生させる。
http://localhost:8080/exception へアクセスが来たら Exception 例外を発生させる。

package com.example.my;

import org.springframework.boot.SpringApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MyController {

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

  @RequestMapping("/")
  public ModelAndView handleTop(ModelAndView mav) {
    mav.addObject("mymessage", "Hello, world.");
    mav.setViewName("myview");
    return mav;
  }

  @RequestMapping("/myexception")
  public ModelAndView handleMyException(ModelAndView mav) throws MyException {
    throw new MyException();
  }

  @RequestMapping("/exception")
  public ModelAndView handleException(ModelAndView mav) throws Exception {
    throw new Exception();
  }
}

MyControllerAdvice.java

MyException 例外を捕捉するためのクラス。
クラスには @ControllerAdvice アノテーションを付与する。
例外を捕捉するためのメソッドに @ExceptionHandler アノテーションを付与し、 MyException.class を指定している。

package com.example.my;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class MyControllerAdvice {

  @ExceptionHandler({MyException.class})
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ModelAndView handleMyException(Exception e, WebRequest req) {
    System.out.println("MyControllerAdvice#handleMyException");
    ModelAndView mav = new ModelAndView();
    mav.addObject("myerror", "MyControllerAdvice#handleMyException");
    mav.setViewName("myview");
    return mav;
  }
}

MyHandlerExceptionResolver.java

@ExceptionHandler で処理しない例外を捕捉するためのクラス。
HandlerExceptionResolver インターフェースを implements する。
Bean として DI コンテナに登録するため @Component アノテーションをクラスに付与する。

package com.example.my;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

  @Override
  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    System.out.println("MyHandlerExceptionResolver#resolveException");
    System.out.println(handler.getClass());
    System.out.println(handler);
    ModelAndView mav = new ModelAndView();
    mav.addObject("myerror", "MyHandlerExceptionResolver#resolveException");
    mav.setViewName("myview");
    mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
    return mav;
  }
}

myview.html

HTML 出力用 Thymeleaf テンプレートファイル。
エラー等の情報を表示する。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:if="${myerror}">
    <div>Error: <span th:text="${myerror}"></span></div>
</div>
<div th:if="${mymessage}">
    <div>Message: <span th:text="${mymessage}"></span></div>
</div>
</body>
</html>

pom.xml

Maven でビルドするための設定ファイル。

<?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 https://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.7.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <groupId>com.example</groupId>
  <artifactId>my</artifactId>
  <version>0.0.1</version>
  <name>my</name>
  <description>My project for Spring Boot</description>

  <properties>
    <java.version>11</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

JAR ファイルの作成と Spring Boot の起動

Maven の mvn package コマンドで JAR ファイルを生成する。

$ mvn package

生成された JAR ファイルを指定して java コマンドで Spring Boot による Web サーバを起動する。

$ java -jar target/my-0.0.1.jar

curl でアクセスして挙動を確認する

http://localhost:8080/myexception

curl でアクセスする。
MyControllerAdvice クラスの handleMyException メソッドで処理されていることがわかる。

$ curl http://localhost:8080/myexception
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <div>Error: <span>MyControllerAdvice#handleMyException</span></div>
</div>

</body>
</html>

Spring Boot サーバ側の出力。

MyControllerAdvice#handleMyException

http://localhost:8080/exception へアクセスして挙動を見る

curl でアクセスする。
MyHandlerExceptionResolver クラスの resolveException メソッドで処理されていることがわかる。

$ curl http://localhost:8080/exception
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <div>Error: <span>MyHandlerExceptionResolver#resolveException</span></div>
</div>

</body>
</html>

Spring Boot サーバ側の出力。

MyHandlerExceptionResolver#resolveException
class org.springframework.web.method.HandlerMethod
public org.springframework.web.servlet.ModelAndView com.example.my.MyController.handleException(org.springframework.web.servlet.ModelAndView) throws java.lang.Exception

Spring Web MVC 内での例外処理の流れ

DispatcherServlet クラスが複数の HandlerExceptionResolver オブジェクトを処理している。

ExceptionHandlerExceptionResolver クラス、ResponseStatusExceptionResolver クラス、DefaultHandlerExceptionResolver クラスが用意されており、これらのクラスがそれぞれの役割を持って例外を処理している。

その中でも ExceptionHandlerExceptionResolver クラスは @ExceptionHandler アノテーションの付与されたメソッドを呼び出す処理をしている。

DispatcherServlet (Spring Framework 5.1.9.RELEASE API)

The dispatcher's exception resolution strategy can be specified via a HandlerExceptionResolver, for example mapping certain exceptions to error pages. Default are ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver, and DefaultHandlerExceptionResolver. These HandlerExceptionResolvers can be overridden through the application context. HandlerExceptionResolver can be given any bean name (they are tested by type).

Spring Web MVC の DispatcherServlet クラスのソースコードを見る。

spring-framework/DispatcherServlet.java at v5.1.9.RELEASE · spring-projects/spring-framework

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    @Nullable Object handler, Exception ex) throws Exception {

  // Success and error responses may use different content types
  request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

  // Check registered HandlerExceptionResolvers...
  ModelAndView exMv = null;
  if (this.handlerExceptionResolvers != null) {
    for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
      exMv = resolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
        break;
      }
    }
  }

processHandlerException メソッドの中で、this.handlerExceptionResolvers からひとつずつ HandlerExceptionResolver オブジェクトを取得し resolveException メソッドを呼び出している。
それぞれのオブジェクトの resolveException メソッドが例外に対して処理を行い ModelAndView オブジェクトを返している。

IntelliJ IDEA のデバッガで処理中のオブジェクトを見てみる。

dispatcherservlet-1.png

dispatcherservlet-2.png

HandlerExceptionResolverComposite オブジェクトが ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver を管理しているのが見える。

ExceptionHandlerExceptionResolver オブジェクトは exceptionHandlerAdviceCache というインスタンス変数を持っており、ここに @ControllerAdvice アノテーションを付与したクラスのオブジェクトがある。

ExceptionHandlerExceptionResolver のソースコードを見ると、exceptionHandlerAdviceCache に登録している様子がわかる。

spring-framework/ExceptionHandlerExceptionResolver.java at v5.1.9.RELEASE · spring-projects/spring-framework

private void initExceptionHandlerAdviceCache() {
  if (getApplicationContext() == null) {
    return;
  }

  List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
  AnnotationAwareOrderComparator.sort(adviceBeans);

  for (ControllerAdviceBean adviceBean : adviceBeans) {
    Class<?> beanType = adviceBean.getBeanType();
    if (beanType == null) {
      throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
    }
    ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
    if (resolver.hasExceptionMappings()) {
      this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
    }
    if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
      this.responseBodyAdvice.add(adviceBean);
    }
  }

spring-framework/ExceptionHandlerMethodResolver.java at v5.1.9.RELEASE · spring-projects/spring-framework

private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
  ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
  Assert.state(ann != null, "No ExceptionHandler annotation");
  result.addAll(Arrays.asList(ann.value()));
}

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JAVAでHTML解析(スクレイピング)

はじめに

サイトリニューアルのとき、データ移行作業がよくあります。
手動作業はできますが、コストかかりますので、
バッチでHTML取得⇒解析⇒新システムに投入 というような機能が役に立つ場合があります。

Javaでは、jsoupというライブラリは有名です。
Pythonでは、beautifulsoup4 というライブラリは有名です。

jsoup: https://jsoup.org/
beautifulsoup4: https://pypi.org/project/beautifulsoup4/

1 jsoup

jsoupはHTML解析のJAVAライブラリです。jquery-likeのセレクターで簡単にHTML解析できます。
WHATWG HTML5 仕様を対応しております。

1-1 JAVAプロジェクト作成し、ライブラリ導入

Gradle例:

// https://mvnrepository.com/artifact/org.jsoup/jsoup
compile group: 'org.jsoup', name: 'jsoup', version: '1.12.1'

1-2 Yahooニュースのタイトルの取得例

1-2-1 HTML構造

1-2-2 タイトルとURLを抽出するシンプルな解析コード

package com.test.jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class JsoupHtmlParser {

    public static void main(String[] args) throws IOException {
        Document doc = Jsoup.connect("https://news.yahoo.co.jp").get();
        // 各記事のaタグを取得。jQueryのセレクターと同じ感じで記載
        Elements newsHeadlines = doc.select(".topicsList li.topicsListItem a");
        for (Element headline : newsHeadlines) {
            System.out.println("title: " + headline.ownText() + ",  href: " + headline.absUrl("href"));
        }
    }
}

1-2-3 解析結果

title: 台風で記録的暴風、2人死亡,  href: https://news.yahoo.co.jp/pickup/6336014
title: 成田空港 1万人でごった返す,  href: https://news.yahoo.co.jp/pickup/6336017
title: 警備会社の3.6億円盗む 手配,  href: https://news.yahoo.co.jp/pickup/6336018
title: 計画運休 ダイヤの正常化課題,  href: https://news.yahoo.co.jp/pickup/6336013
title: バズる女子大生 炎上で50回涙,  href: https://news.yahoo.co.jp/pickup/6335993
title: バスケW杯5敗目 3P全部外す,  href: https://news.yahoo.co.jp/pickup/6336020
title: NPB プロスポーツ協会を脱退,  href: https://news.yahoo.co.jp/pickup/6336015
title: 吉沢亮「重圧尋常じゃない」,  href: https://news.yahoo.co.jp/pickup/6336022

1-3 HTML文字列の解析

1-3-1 解析サンプル

package com.test.jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class JsoupHtmlParser {

    public static void main(String[] args) throws IOException {
        String html = "<h1>HTML fragmentの解析</h1><div><p>P1</p>";
        Document doc = Jsoup.parseBodyFragment(html);

        // docのそのまま出力すると、html,bodyタグが付加されましたので、fragment解析の場合は要注意
        System.out.println(doc.html());

        System.out.println("==========================");

        // bodyの要素を出力
        Element body = doc.body();
        System.out.println(body.html());
    }
}

1-3-2 解析結果

<html>
 <head></head>
 <body>
  <h1>HTML fragmentの解析</h1>
  <div>
   <p>P1</p>
  </div>
 </body>
</html>
==========================
<h1>HTML fragmentの解析</h1>
<div>
 <p>P1</p>
</div>

そのほか、ファイルからHTML解析、データ抽出、データ修正などサイトにわかりやすいサンプルコードがあります。
https://jsoup.org/cookbook/input/load-document-from-file

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JAVAでHTML解析

はじめに

サイトリニューアルのとき、よくあるデータ移行があります。
手動作業はできますが、コストかかりますので、
バッチでHTML取得⇒解析⇒新システムに投入 というような機能が役に立つ場合があります。

Javaでは、jsoupというライブラリは有名です。
Pythonでは、beautifulsoup4 というライブラリは有名です。

jsoup: https://jsoup.org/
beautifulsoup4: https://pypi.org/project/beautifulsoup4/

1 jsoup

jsoupはHTML解析のJAVAライブラリです。jquery-likeのセレクターで簡単にHTML解析できます。
WHATWG HTML5 仕様を対応しております。

1-1 JAVAプロジェクト作成し、ライブラリ導入

Gradle例:

// https://mvnrepository.com/artifact/org.jsoup/jsoup
compile group: 'org.jsoup', name: 'jsoup', version: '1.12.1'

1-2 Yahooニュースのタイトルの取得例

1-2-1 HTML構造

image.png

1-2-2 タイトルとURLを抽出するシンプルな解析コード

package com.test.jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class JsoupHtmlParser {

    public static void main(String[] args) throws IOException {
        Document doc = Jsoup.connect("https://news.yahoo.co.jp").get();
        // 各記事のaタグを取得。jQueryのセレクターと同じ感じで記載
        Elements newsHeadlines = doc.select(".topicsList li.topicsListItem a");
        for (Element headline : newsHeadlines) {
            System.out.println("title: " + headline.ownText() + ",  href: " + headline.absUrl("href"));
        }
    }
}

1-2-3 解析結果

title: 台風で記録的暴風、2人死亡,  href: https://news.yahoo.co.jp/pickup/6336014
title: 成田空港 1万人でごった返す,  href: https://news.yahoo.co.jp/pickup/6336017
title: 警備会社の3.6億円盗む 手配,  href: https://news.yahoo.co.jp/pickup/6336018
title: 計画運休 ダイヤの正常化課題,  href: https://news.yahoo.co.jp/pickup/6336013
title: バズる女子大生 炎上で50回涙,  href: https://news.yahoo.co.jp/pickup/6335993
title: バスケW杯5敗目 3P全部外す,  href: https://news.yahoo.co.jp/pickup/6336020
title: NPB プロスポーツ協会を脱退,  href: https://news.yahoo.co.jp/pickup/6336015
title: 吉沢亮「重圧尋常じゃない」,  href: https://news.yahoo.co.jp/pickup/6336022

1-3 HTML文字列の解析

1-3-1 解析サンプル

package com.test.jsoup;

import java.io.IOException;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class JsoupHtmlParser {

    public static void main(String[] args) throws IOException {
        String html = "<h1>HTML fragmentの解析</h1><div><p>P1</p>";
        Document doc = Jsoup.parseBodyFragment(html);

        // docのそのまま出力すると、html,bodyタグが付加されましたので、fragment解析の場合は要注意
        System.out.println(doc.html());

        System.out.println("==========================");

        // bodyの要素を出力
        Element body = doc.body();
        System.out.println(body.html());
    }
}

1-3-2 解析結果

<html>
 <head></head>
 <body>
  <h1>HTML fragmentの解析</h1>
  <div>
   <p>P1</p>
  </div>
 </body>
</html>
==========================
<h1>HTML fragmentの解析</h1>
<div>
 <p>P1</p>
</div>

そのほか、ファイルからHTML解析、データ抽出、データ修正などサイトにわかりやすいサンプルコードがあります。
https://jsoup.org/cookbook/input/load-document-from-file

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバサイドKotlin用Docker, docker-compose

サーバサイドと書いてありますが、Android関係ないってことを表したかっただけです。
内容は最低限のものになってます。

ローカルでApache Beam SDK動かす用に雑に作ったものです。

とりあえずミニマムのexampleが一つ動いたので載せておきます。

Dockerfile

FROM gradle:5.4-jdk-alpine

ENV APP_ROOT /usr/src/app

WORKDIR $APP_ROOT

USER root

RUN apk add --no-cache curl \
    zip \
    libc6-compat \
    bash

RUN ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2

RUN curl -s https://get.sdkman.io | bash

RUN bash -c " \
    source "$HOME/.sdkman/bin/sdkman-init.sh" && \
    sdk install kotlin 1.3.50 \
"

docker-compose.yml

docker-compose.yml
version: '2'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/usr/src/app:cached

動かす時

$ docker-compose run --rm app bash
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kotlinを使いこなす 〜Convert Java File to Kotlin File 卒業への道〜 その1

はじめに

Android等でKotlinを使用している場合、Javaから『Convert Java File to Kotlin File』で自動変換している人は多い。実際にそれでも動くのだが、そこをもう1歩先に進みKotlinをより使いこなす為のメモ。

今回はスマートキャストを習得する。
Kotlinバージョン:1.3

Kotlin変換前のサンプルコード

もっと良いサンプルあっただろうと思われるかもしれないが(汗)、以下のような Java と Kotlin が混ざったコードがあるとする。

ResponseModle.java
public class ResponseModle {

    public void receiveResponseBody(ItemData data) {
        if (data == null) {
            return;
        }

        if (data.getBody() != null) {
            ResponseLog.INSTANCE.showLength(data.getBody());
        }

        // android.text.TextUtils.isEmpty(CharSequence str)
        // null or empty の両方をチェックできる
        if (!TextUtils.isEmpty(data.subBody)) {
            ResponseLog.INSTANCE.showSubTitle(data.getSubBody());
        }
    }
ItemData.java
public class ItemData {

    private String body = null;

    private String subBody = null;

    public ItemData(String body, String subBody) {
        this.body = body;
        this.subBody = subBody;
    }

    public String getBody() {
        return body;
    }

    public String getSubBody() {
        return subBody;
    }
}
ResponseLog.kt
object ResponseLog {

    public fun showLength(title: String) {
        System.out.print(title.length)
    }

    public fun showSubTitle(subTitle: String) {
        System.out.print(subTitle)
    }
}

Kotlin変換後のサンプルコード

これを自動変換して元の形に合わせるときっとこんな感じに・・・。

ResponseModle.kt
class ResponseModle {

    fun receiveResponceBody(data: ItemData?) {
        if (data == null) {
            return
        }

        if (data?.body != null) {
            ResponseLog.showLength(data.body)
        }

        if (!TextUtils.isEmpty(data?.subBody)) {
            ResponseLog.showSubTitle(data.subBody!!)
        }
    }
}
ItemData.kt
data class ItemData(val body: String? = null, val subBody: String? = null)
ResponseLog.kt
object ResponseLog {

    public fun showLength(title: String) {
        System.out.print(title.length)
    }

    public fun showSubTitle(subTitle: String) {
        System.out.print(subTitle)
    }
}

何が問題か

ResponseModleクラスの実装がやや強引な実装になってしまった。
ItemData のnullチェック完了しているのに判定でNullableのアンラップ(data?・・・)してしまっている。
ResponseLogクラスのメソッドの引数がNonNullしか渡せない。コンパイルエラーを回避する為に強制アンラップ(・・・!!)している。既にnullチェック済みであるので強制アンラップしたくない。

スマートキャストで解決

スマートキャストを使用すると強制アンラップしなくてもコンパイルが通る。

ResponseModle.kt
class ResponseModle {

    fun receiveResponceBody(data: ItemData?) {
        // スマートキャスト
        if (data !is ItemData) return
        // スマートキャスト
        if (data.body is String) ResponseLog.showLength(data.body)
        // Kotlin1.3からisNullOrEmptyでスマートキャストが効くようになった
        if (!data.subBody.isNullOrEmpty()) ResponseLog.showSubTitle(data.subBody)
    }
}

良くない例

強制アンラップを回避するためのエルビス演算子を使用してしまう
ResponseLog.showSubTitle(data.subBody ?: "")

おまけ

値代入時のletを使用したシンプルな実装方法

こんな実装があったとして

ResponseModle.java
public class ResponseModle {

    public void receiveResponseBody(ItemData data) {
        String body;
        if (data.getBody() == null) {
            return;
        } else {
            body = "OK";
        }
        ResponseLog.INSTANCE.showLength(body);
    }
}

Convert Java File to Kotlin File で変換すると

ResponseModle.kt
class ResponseModle {

    fun receiveResponceBody(data: ItemData?) {
        val body: String
        if (data?.body == null) {
            return
        } else {
            body = "OK"
        }
        ResponseLog.showLength(body);
}

となるが、Javaっぽい実装になってしまう。せっかくKotlinを使用するのであればletを使用してシンプルに書く

ResponseModle.kt
class ResponseModle {

    fun receiveResponceBody(data: ItemData?) {
        val body = data?.body?.let { "OK" } ?: return
        ResponseLog.showLength(body);
}

とてもシンプル!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】スタック領域、static領域について

Javaのスタック領域、static領域について質問です。

【質問】
メモリのスタック領域と、static領域とは、同じものを指しているのでしょうか?

【経緯】
先日Javaの参考書にて、「static領域」という言葉が出てきました。
<参考書>
徹底攻略 Java SE 8 Silver 問題集
https://www.amazon.co.jp/%E5%BE%B9%E5%BA%95%E6%94%BB%E7%95%A5-Java-Silver-%E5%95%8F%E9%A1%8C%E9%9B%86-1Z0-808/dp/4844339931/ref=asc_df_4844339931/?tag=jpgo-22&linkCode=df0&hvadid=295658314765&hvpos=1o1&hvnetw=g&hvrand=2232083536107640732&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009343&hvtargid=pla-526914835838&psc=1&th=1&psc=1

以前から知っているスタック領域とは別物なのかと思い、「static領域」で調べてみると、
「スタック領域」に関するサイトばかりがヒットします。

この2つは同じなのでしょうか?
それとも別物でしょうか?

もし別物でしたら解説もいただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sdkmanを使ったKotlinの開発環境の構築

sdkmanとは

JVM系のrbenvみたいなものです。

rbenvのようなenv系と同様のメリット(複数バージョンのインストール、切り替え)を享受できます。

UNIX系のみで使えるようです。

sdkmanのインストール

bashの部分はお使いのシェルに変更してください。

$ curl -s https://get.sdkman.io | bash
$ exec $SHELL -l

Kotlinのインストール

$ sdk install kotlin

動作確認

ファイルを準備

hello.kt
fun main(args: Array<String>) {
    println("Hello, World!")
}

コンパイル

$ kotlinc hello.kt -include-runtime -d hello.jar

実行

$ java -jar hello.jar

おまけ(自分がよく使いそうなコマンド)

バージョン指定インストール

$ sdk install kotlin 1.2.71

バージョン指定アンインストール

$ sdk uninstall kotlin 1.2.71

切り替え

sdk default kotlin 1.2.71

一覧表示

$ sdk list kotlin

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SDKMANを使ったKotlinの開発環境の構築

Kotlinで開発する必要がでたので備忘録としてまとめました。

SDKMAN, Kotlin, Gradleのインストール等について記載してます。

SDKMANとは

JVM系のrbenvみたいなものです。

rbenvのようなenv系と同様のメリット(複数バージョンのインストール、切り替え)を享受できます。

UNIX系のみで使えるようです。

SDKMANのインストール

bashの部分はお使いのシェルに変更してください。

$ curl -s https://get.sdkman.io | bash
$ exec $SHELL -l

Kotlinのインストール

$ sdk install kotlin

動作確認

ファイルを準備

hello.kt
fun main(args: Array<String>) {
    println("Hello, World!")
}

コンパイル

$ kotlinc hello.kt -include-runtime -d hello.jar

実行

$ java -jar hello.jar

Gradleのインストール

$ sdk install gradle

Gradleを使ってプロジェクト作成

$ gradle init

色々聞かれるのでお好みで適宜選択。

おまけ(自分がよく使いそうなコマンド)

バージョン指定インストール

$ sdk install kotlin 1.2.71

バージョン指定アンインストール

$ sdk uninstall kotlin 1.2.71

切り替え

$ sdk default kotlin 1.2.71

一覧表示

$ sdk list kotlin

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ArAutoValueConverterの使用例(フィールドタイプの変換)

目次 ⇒ Javaアルゴリズムライブラリ-Artery-サンプル

package jp.avaj.lib.algo;

import jp.avaj.lib.test.L;

/**
ArAutoValueConverterの使用例(フィールドタイプの変換)

・以下のタイプを相互変換する.
  ・String
  ・Boolean
  ・Integer
  ・Long
  ・BigDecimal
  ・ArYoubi ⇒ Enumだが特別扱いしている.
  ・Enum
  ・Date
  ・Calendar
  ・ArDate

・当然なから変換できない場合がある.変換できない時はnullを戻す.例外にはしない.

 */
public class Q06_00 {

  public static void main(String[] args) {
    // すべての組合せを示すと長くなるので、一部のみを提示する.

    Object result;

    L.p("\n==== String⇒Boolean");
    // この変換はArAutoValueConverter.stringTrueValues,ArAutoValueConverter.stringFalseValuesを書き換えることで変更できる.
    {
      result = ArAutoValueConverter.convert("TRUE",Boolean.class);
      L.p(result.getClass().getSimpleName());
      L.p(result+"");
    }
    L.p("\n==== String⇒ArYoubi");
    {
      result = ArAutoValueConverter.convert("Sun",ArYoubi.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== String⇒Enum:当該Enumにstatic Enum fromString(String)があることが必要");
    {
      result = ArAutoValueConverter.convert("チョキ",Jyanken.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== Boolean⇒String");
    // この変換はArAutoValueConverter.stringTrueValues,ArAutoValueConverter.stringFalseValuesを書き換えることで変更できる.
    {
      result = ArAutoValueConverter.convert(new Boolean(false),String.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== Boolean⇒Integer");
    // この変換はArAutoValueConverter.integerTrueValues,AutoValueConverter.integerFalseValuesを書き換えることで変更できる.
    {
      result = ArAutoValueConverter.convert(new Boolean(false),Integer.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== Integer⇒ArYoubi");
    // この変換はArYoubi.intConversionを書き換えることで変更できる
    {
      result = ArAutoValueConverter.convert(new Integer(1),ArYoubi.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== Integer⇒Enum:当該Enumにstatic Enum fromInt(Integer)があることが必要");
    {
      result = ArAutoValueConverter.convert(new Integer(2),Jyanken.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== ArYoubi⇒String");
    // この変換はYoubi.defaultLang,Youbi.defaultLength,Youbi.defaultBracketをを書き換えることで変更できる.
    {
      result = ArAutoValueConverter.convert(ArYoubi.TUE,String.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
    L.p("\n==== Enum⇒Integer:当該EnumにInteger toInt()があることが必要);");
    {
      result = ArAutoValueConverter.convert(Jyanken.Paa,Integer.class);
      L.p(result.getClass().getSimpleName());
      L.p(result.toString());
    }
  }
  static enum Jyanken {
    Guu("グー",1),Choki("チョキ",2),Paa("パー",3);
    private Jyanken(String name,int val) {
      this.name = name;
      this.val = val;
    }
    public String toString() {
      return this.name();
    }
    public int toInt() {
      return this.val;
    }
    public static Jyanken fromString(String str) {
      if ("グー".equals(str)) { return Guu; }
      if ("チョキ".equals(str)) { return Choki; }
      if ("パー".equals(str)) { return Paa; }
      return null;
    }
    public static Jyanken fromInt(Integer v) {
      if (v == 1) { return Guu; }
      if (v == 2) { return Choki; }
      if (v == 3) { return Paa; }
      return null;
    }
    private String name;
    private int val;
  }
}

結果は次のとおり。

result.ext
==== String⇒Boolean
Boolean
true

==== String⇒ArYoubi
ArYoubi
(日)

==== String⇒Enum:当該Enumにstatic Enum fromString(String)があることが必要
Jyanken
Choki

==== Boolean⇒String
String
FALSE

==== Boolean⇒Integer
Integer
0

==== Integer⇒ArYoubi
ArYoubi
(月)

==== Integer⇒Enum:当該Enumにstatic Enum fromInt(Integer)があることが必要
Jyanken
Choki

==== ArYoubi⇒String
String
(火)

==== Enum⇒Integer:当該EnumにInteger toInt()があることが必要);
Integer
3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jarファイルを比較する

はじめに

ソースは変えずにビルドツールを変更したので、jarの中身まで確認したときのメモです。
もしビルドツールに関わらず、jarの中身を見たいときに参考になると思います。

1. jar の解凍

jar -xvf xxx.jar

2. jad でデコンパイル

こちら からjadをインストールします。
以下コマンドでデコンパイルします。

find . -name "*.class" -exec echo jad -s java -r {} \; > jad.sh
sh jad.sh

ちなみに jad のオプションは jad --help で確認しました。
-s で拡張子をjavaに指定し -r で元の階層を維持しています

3. 比較(WinMargeやCompareMerge)

作成されたディレクトリごと比較する
ただし、対象は*.javaを指定してください。
※ classファイルも残りママであるため

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む