Archive Redo Blog

DBエンジニアのあれこれ備忘録


テーマ:
サーブレットで日本語のリクエストパラメータを受ける際、文字コードを意識しておかなければ、文字化けなどのトラブルに見舞われます。

サーブレット & JSP で Web アプリケーションを開発している人にとっては常識的なことかもしれませんが、この手の問題は初期の段階で対策を打ってしまえばあとはあまり意識することのないものなので、再び同じ問題にぶち当たったときのために備忘録としてまとめておきます。

サーブレットで日本語のリクエストパラメータを受け取った際、それを適切に処理するには以下のような方法が考えられます。

1.getBytees()を利用してリクエストパラメータの文字コード変換を行う。
サーブレットでは、リクエストパラメータをデコードする際の文字コードがデフォルトで ISO8859-1 と決められていますので、その他の文字コードでデコードする場合は、取得したリクエストパラメータを getBytes() を利用して一旦 ISO8859-1 のバイト列に変換し、改めて文字コードを指定してデコードします。

サーブレットクラス
.
(省略)
.
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  // パラメータの取得
  String keyword = request.getParameter("keyword");
  // 文字コードを変換
  keyword = new String(keyword.getBytes("8859_1"),"utf-8");
  request.getParameter("keyword");
  .
  (省略)
  .
}
.
(省略)
.

2.setCharactorEncoding()を利用して文字コードを指定する。
リクエストパラメータを取得する前に setCharactorEncoding() を利用してデコードする際の文字コードを指定します。

こうすることによって、getParameter() した時に指定した文字コードで自動的にデコードされるようになります。

※setCharactorEncoding()は必ずgetParameter()の前に実行する必要があります。

サーブレットクラス
.
(省略)
.
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  // エンコードの指定
  request.setCharactorEncoding("utf-8");
  // パラメータの取得
  String keyword = request.getParameter("keyword");
  .
  (省略)
  .
}

3.フィルタクラスで setCharactorEncoding() を利用して文字コードを指定する。
2.と同じことをフィルタクラスの中で実行します。

フィルタクラス
.
(省略)
.
public class CharacterEncodingFilter implements Filter {

  protected String encoding;

  public void init(FilterConfig config) throws ServletException {

    encoding = config.getInitParameter("encoding");
    if (encoding == null || encoding.length() == 0) {
      encoding = "8859_1";
    }

  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

    request.setCharacterEncoding(encoding);

  }

  public void destroy() {}

}

web.xml
.
(省略)
.
<filter>
  <filter-name>SetCharacterEncodingFilter</filter-name>
  <filter-class>CharacterEncodingFilter</filter-class>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>utf-8</param-value>
  </init-param>
 </filter>
 .
 (省略)
 .


上記3つの方法のうち、1.は個々のパラメータごとに対応しなければならない、2.は個々のサーブレットごとに対応しなければならない、ということで、3.の方法がベターかと思います。

なお、上記の方法は文字コードが固定であることを前提にしていますが、呼び出し元によって文字コードが異なるようなケースでは、
  • 1.の場合、文字コードを示すパラメータを追加し、その値に応じたデコードを行う。(または文字コードごとにサーブレットを分ける)
  • 2.の場合、文字コードごとにサーブレットを分ける。
  • 3.の場合、文字コードごとにサーブレットもしくはそのURLを分け、適用するフィルタクラスを分ける。
といった対処がそれぞれ必要になります。

AD
いいね!した人  |  コメント(0)  |  リブログ(0)
最近の画像つき記事  もっと見る >>

テーマ:
数値のフォーマット処理についてメモしておきます。


数値(10進数)のフォーマット処理には Java.text パッケージの NumberFormat クラスを使用します。


NumberFormat クラスには汎用的なフォーマットのインスタンスを生成する数種類のファクトリメソッドが用意されており、これらを使用してフォーマット処理を行うのが簡単です。


汎用数値フォーマットによるフォーマット処理

getNumberInstance() を使用すると、汎用数値フォーマットによるフォーマット処理が行えます。

String value = "123456.789";
NumberFormat df = NumberFormat.getNumberInstance();
System.out.println(value + " --> " + df.format(new BigDecimal(value)));

123456.789 --> 123,456.789


整数型数値フォーマットによるフォーマット処理

getIntegerInstance() を使用すると、整数型数値フォーマットによるフォーマット処理が行えます。

String value = "123456.789";
NumberFormat df = NumberFormat.getIntegerInstance();
System.out.println(value + " --> " + df.format(new BigDecimal(value)));

123456.789 --> 123,457


通貨フォーマットによるフォーマット処理

getCurrencyInstance() を使用すると、通貨フォーマットによるフォーマット処理が行えます。

String value = "123456.789";
NumberFormat df = NumberFormat.getCurrencyInstance();
System.out.println(value + " --> " + df.format(new BigDecimal(value)));

123456.789 --> ¥123,457


パーセントフォーマットによるフォーマット処理

getPercentInstance() を使用すると、パーセントフォーマットによるフォーマット処理が行えます。

String value = "0.123";
NumberFormat df = NumberFormat.getPercentInstance();
System.out.println(value + " --> " + df.format(new BigDecimal(value)));

0.123 --> 12%


カスタムフォーマットによるフォーマット処理

NumberFormat クラスのサブクラス DecimalFormat を使用すると、カスタムフォーマットによるフォーマット処理が行えます。

String value = "123456789";
DecimalFormat df = new DecimalFormat("#,##0.0");
System.out.println(value + " -- ( " + df.toPattern() + " ) --> " + df.format(new BigDecimal(value)));

123456789 --> 123,456,789.0

フォーマットに指定できるパターンには、

パターン 説明
# 数字。値が0の時は表示しない。
0 数字。値が0の時も表示する。
. 小数点
, 桁区切り文字
% パーセント表示
\u00A4 通貨記号

などがあります。


桁数を指定したフォーマット処理

NumberFormat クラスは整数部、小数部の最小桁数、最大桁数を保持しており、これらの設定に基づいてフォーマット処理が行われます。

これらの変数の既定値はNumberFormatクラスのインスタンスの作成方法によって異なりますが、以下のメソッドを使用すれば、後から変更することができます。

メソッド 説明
setMinimumIntegerDigits(int newValue) 整数部の最小桁数を指定する。
setMaximumIntegerDigits(int newValue) 整数部の最大桁数を指定する。
setMinimumFractionDigits(int newValue) 小数部の最小桁数を指定する。
setMaximumFractionDigits(int newValue) 小数部の最大桁数を指定する。


例えば、最大桁数を指定することによって、溢れた桁を丸めたり、

String value = "123456.789";
DecimalFormat df = new DecimalFormat("#.#");
df.setMaximumIntegerDigits(5);
df.setMaximumFractionDigits(2);
System.out.println(value + "  --> " + df.format(new BigDecimal(value)));

123456.789 --> 23456.79

最小桁数を指定することによって、ゼロ埋めしたりすることができます。

String value = "123456.789";
DecimalFormat df = new DecimalFormat("#.#");
df.setMinimumIntegerDigits(10);
df.setMaximumIntegerDigits(10);
df.setMinimumFractionDigits(5);
df.setMaximumFractionDigits(5);
System.out.println(value + " --> " + df.format(new BigDecimal(value)));

123456.789 --> 0000123456.78900


ロケールを指定したフォーマット処理

通貨記号をつける場合など、ロケールを明示的に指定しなければ既定のロケールに基づいてフォーマット処理が行われますが、以下のようにロケールを明示的に指定すると、指定したロケールに基づいてフォーマット処理が行われます。


NumberFormat クラスのファクトリメソッドを使用する場合

ファクトリメソッドの引数としてロケールを指定します。

String value = "123456.789";
NumberFormat df = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println(value + " --> " + df.format(new BigDecimal(value)));

123456.789 --> $123,456.79

DecimalFormat クラスを使用する場合

デフォルトロケールを変更してからフォーマット処理を行います。

Locale.setDefault(Locale.US);
DecimalFormat df = new DecimalFormat("\u00A4#,##0");
System.out.println(value + " --> " + df.format(new BigDecimal(value)));
Locale.setDefault(Locale.JAPAN);

123456789 --> $123,456,789
AD
いいね!した人  |  コメント(0)  |  リブログ(0)

テーマ:
よく使うわりに、よく使い方を忘れるのでメモしておきます。


正規表現によるパターンマッチングを行うには、java.util.regex パッケージの Pattern クラスと Matcher クラスを利用します。

Pattern クラスは正規表現パターンを表すクラス、Matcher クラスはマッチングを行うクラスで、それぞれ、以下のように準備します。

String value = "This is a pen.";
String pattern = "\\w+";
Pattern p = Pattern.compile(value);
Matcher m = p.matcher(pattern);

最終的に使用したいのは Matcher クラスなので、以下のようにしてもいいかもしれません。

Matcher m = Pattern.compile(pattern).matcher(value);


Matcher クラスが準備できたら、マッチングを実行します。

マッチングには matches() または find() を使います。


matches()

matches() は文字列全体がパターンに完全にマッチするかどうかを返します。


以下の例では、パターンに完全にマッチするため、matches() は true を返します。

String value = "This is a pen.";
String pattern = "This is a \\w+.";
Matcher m = Pattern.compile(pattern).matcher(value);
if (m.matches()){
    System.out.println("\"" + pattern + "\" matches \"" + value + "\".");
} else {
    System.out.println("\"" + pattern + "\" does not match \"" + value + "\".");
}

以下の例では、パターンに部分的にしかマッチしないため、matches() は false を返します。

String value = "This is a pen.";
String pattern = "\\w+";
Matcher m = Pattern.compile(pattern).matcher(value);
if (m.matches()){
    System.out.println("\"" + pattern + "\" matches \"" + value + "\".");
} else {
    System.out.println("\"" + pattern + "\" does not match \"" + value + "\".");
}


find()

find() は文字列を前から検索し、パターンにマッチする部分文字列があるかどうかを返します。


以下の例では、find() は true を返します。

String value = "This is a pencil.";
String pattern = "pen";
Matcher m = Pattern.compile(pattern).matcher(value);
if (m.find()){
    System.out.println("\"" + pattern + "\" is found in \"" + value + "\".");
} else {
    System.out.println("\"" + pattern + "\" is not found in \"" + value + "\".");
}

以下の例では、find() は false を返します。

String value = "This is a pencil.";
String pattern = "\\bpen\\b";
Matcher m = Pattern.compile(pattern).matcher(value);
if (m.find()){
    System.out.println("\"" + pattern + "\" is found in \"" + value + "\".");
} else {
    System.out.println("\"" + pattern + "\" is not found in \"" + value + "\".");
}

find() の場合は、文字列の中にパターンにマッチする部分文字列が複数存在する場合に、繰り返し実行することで前方から順にそれらの部分文字列を順次抽出することができます。


group()

matches() または find() の実行後、マッチした部分文字列を抽出するには、group() を実行します。


以下の例では、"This"、"is"、"a"、"pen" が抽出されます。

String value = "This is a pen.";
String pattern = "(\\w+)";
Matcher m = Pattern.compile(pattern).matcher(value);
while (m.find()){
    System.out.println(m.group());
}

また、group(int group) を使用すれば、後方参照を利用して、"()" でグループ化された各部分文字列を抽出することができます。


以下の例では、年、月、日が別々に抽出されます。

String value = "2009/12/31";
String pattern = "(\\d{4})/(\\d{2})/(\\d{2})";
Matcher m = Pattern.compile(pattern).matcher(value);
if (m.matches()){
    System.out.println(m.group(1)+ "年" + m.group(2) + "月" + m.group(3) + "日");
}
AD
いいね!した人  |  コメント(0)  |  リブログ(0)

テーマ:
何かと利用する機会も多そうなのでメモしておきます。

英数字に関しては、全角半角でコードの並び順が一致しているため、文字コードの加減算で実現できるようです。

つまり、半角→全角の場合は 0xFEE0 を加算、全角→半角の場合は 0xFEE0 を減算すれば OK ということになります。


なお、英数字以外の文字種については、必ずしも全角半角でコードの並び順が一致しているわけではないので、英数字のように単純には変換できません。

したがって、それらの文字種について誤った変換がなされないよう、変換前には変換対象とするコード範囲を絞り込んでおく必要があります。

文字種 コード範囲
数字(半角) 0x30~0x39
英字(半角/大文字) 0x41~0x5A
英字(半角/小文字) 0x61~0x7A
数字(全角) 0xFF10~0xFF19
英字(全角/大文字) 0xFF21~0xFF3A
英字(全角/小文字) 0xFF41~0xFF5A

半角英数字を全角英数字に変換する
private static String hankakuToZenkaku(String value) {
    StringBuilder sb = new StringBuilder(value);
    for (int i = 0; i &lt; sb.length(); i++) {
        int c = (int) sb.charAt(i);
        if ((c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A)) {
            sb.setCharAt(i, (char) (c + 0xFEE0));
        }
    }
    value = sb.toString();
    return value;
}

全角英数字を半角英数字に変換する
private static String zenkakuToHankaku(String value) {
    StringBuilder sb = new StringBuilder(value);
    for (int i = 0; i &lt; sb.length(); i++) {
        int c = (int) sb.charAt(i);
        if ((c >= 0xFF10 && c <= 0xFF19) || (c >= 0xFF21 && c <= 0xFF3A) || (c >= 0xFF41 && c <= 0xFF5A)) {
            sb.setCharAt(i, (char) (c - 0xFEE0));
        }
    }
    value = sb.toString();
    return value;
}

いいね!した人  |  コメント(0)  |  リブログ(0)

テーマ:
Tomcat を 5.5.27 から 5.5.28 にバージョンアップすると 起動時の tld ファイルの処理中にエラーが出るようになりました。

致命的: Error processing TLD files for context path /xxxxxx
javax.servlet.ServletException: コンテキスト /xxxxx のリソースパス /WEB-INF/xxxxxxxxxx.tld のTLDを処理中の例外です

どうやら、web.xml に存在しない tld ファイルを定義していると上記のようなエラーが発生するようです。

5.5.27 では単に無視されていただけなのですが、よりチェックが厳しくなったということでしょうか。
いいね!した人  |  コメント(0)  |  リブログ(0)

AD

Ameba人気のブログ

Amebaトピックス

      ランキング

      • 総合
      • 新登場
      • 急上昇
      • トレンド

      ブログをはじめる

      たくさんの芸能人・有名人が
      書いているAmebaブログを
      無料で簡単にはじめることができます。

      公式トップブロガーへ応募

      多くの方にご紹介したいブログを
      執筆する方を「公式トップブロガー」
      として認定しております。

      芸能人・有名人ブログを開設

      Amebaブログでは、芸能人・有名人ブログを
      ご希望される著名人の方/事務所様を
      随時募集しております。