■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2009年03月29日

    Java総合講座 - 初心者から達人へのパスポート
                  vol.146

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.SOAPのアプリケーション(Webサービス)
========================================================


シングルトンについて、もう少し説明を続けます。
下記に、RunModePropertiesクラスのソース・コードを再提示して
おきます。

--------------------------------------------------------
package jp.co.flsi.lecture.webservice.hotel;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.log4j.Logger;

public class RunModeProperties extends Properties {

   private static RunModeProperties instance = null;
   private static Logger logger = Logger.getLogger(RunModeProperties.class);

   private RunModeProperties() {
      InputStream inputStream = getClass().getResourceAsStream("/runmode.properties");
      if(inputStream != null) {
         try {
            super.load(inputStream);
         } catch (IOException e1) {
            logger.error(e1);
         }
      }
      logger.info("インスタンスを生成しました。 ...............");
   }
  
   public static RunModeProperties getInstance() {
      if (instance != null) {
         logger.info("インスタンスは既に存在します。 ...............");
      }
      else {
         logger.info("インスタンスが存在しないので生成します。 ...............");
         instance = new RunModeProperties();
      }
      return instance;
   }
}
--------------------------------------------------------

このうち、

private static RunModeProperties instance = null;

というフィールドの修飾子を確認してください。
まずこれは、外部から呼び出されることがない(同じクラス内でしか呼び出されない)
ので、privateを指定しています。
(フィールドは特に必要が無い限りprivateにするのが基本です。)

そして、これはstaticフィールドにしています。つまり、staticが指定されています。

以前お話したように、普通のフィールドがインスタンスのフィールドという意味を
持つのに対し、staticフィールドはクラスのフィールドという意味を持ちます。
つまり、普通のフィールドは生成されたインスタンスごとに別々に保持されるのに
対し、staticフィールドは一つのクラスについてただ一つだけしか保持されません。

そして、上記のようにフィールドの宣言文において、値の代入の式が書かれている場合
(上記の場合はnullが代入されているが)、普通のフィールドはインスタンスの生成時
に(インスタンスを生成するたびにインスタンスごとに)初期化が行われるのに対し、
staticフィールドは、最初のクラスのロード時に1回だけ初期化が行われます。

そうすると、もしこのinstanceフィールドの宣言文において

private static RunModeProperties instance = new RunModeProperties();

というようにインスタンスを生成して代入する式を書いておけば、最初のクラスのロード時
にインスタンスの代入が行われることになります。

そうすると、最初にクラスが呼び出されたあとは、常にinstance != nullの状態に
なるので、getInstance()メソッドの中では条件判断をする必要はなくなります。
つまり、getInstance()メソッドは常に

return instance;

だけを実行すればいいことになり、ソース・コードが簡単になります。

というわけで、以上を踏まえて、RunModePropertiesクラスのソース・コードを
下記のように変更してみましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.webservice.hotel;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.log4j.Logger;

public class RunModeProperties extends Properties {

   private static Logger logger = Logger.getLogger(RunModeProperties.class);
   private static RunModeProperties instance = new RunModeProperties();
  
   private RunModeProperties() {
      InputStream inputStream = getClass().getResourceAsStream("/runmode.properties");
      if(inputStream != null) {
         try {
            super.load(inputStream);
         } catch (IOException e1) {
            logger.error(e1);
         }
      }
      logger.info("インスタンスを生成しました。 ...............");
   }
  
   public static RunModeProperties getInstance() {
      return instance;
   }
}
--------------------------------------------------------

このとき、loggerフィールドの宣言をinstanceフィールドの宣言の前に移動して
いることに注意して下さい。
RunModeProperties()のコンストラクターの中でloggerフィールドを呼び出して
いるため、new RunModeProperties()を実行する前にloggerフィールドの初期化
が終わっている必要があるためです。(初期化はフィールドの宣言の上から下の
順番に行われます。)

ソース・コードの変更が終わったら、再度、デプロイをして、HotelClientを何度か
実行してみて下さい。
ログを見てみると、"インスタンスを生成しました。 ..............."というメッ
セージは1回出力されるだけですね。やはり、シングルトンとして機能していること
がわかります。



但し、特殊な場合にはgetInstance()メソッドの中で条件判断をさせることもあります。

private static Logger logger = Logger.getLogger(RunModeProperties.class);

の行に注目してください。
このLoggerのgetLogger()メソッドは先ほどのgetInstance()メソッドとほぼ同じ機能を
持つメソッドで、Loggerのただ一つのインスタンスを取得するためのメソッドです。
ただし、引数に指定した値が同じもの(既存のもの)に対しては既存のインスタンスを
返しますが、引数に指定した値がこれまでにないものの場合は、新規にインスタンスを
生成して、それを返すことになります。
このように条件付でただ一つのインスタンスだけを生成する場合は、getInstance()メ
ソッド(Loggerクラスの場合はgetLogger()メソッド)の中に条件判断のためのロジック
を入れることになります。


ところで、

private static Logger logger = Logger.getLogger(RunModeProperties.class);

の行もやはりstatic指定していますが、これは習慣的にstaticにしているものです。

というのは、(このRunModePropertiesクラスはシングルトンなので例外になり
ますが)通常のクラスの場合はインスタンスが何度も生成される可能性がありま
すので、このloggerフィールドも(static指定していない場合は)何度も初期化
される可能性があります。そうすると、同じクラスに対してはgetLogger()メソッド
は同一のLoggerインスタンスを返すにもかかわらず、getLogger()メソッドを何度も
呼び出すことになり、無駄な呼び出しをしていることになります。
その点、static指定にしておくと、このloggerフィールドの初期化はクラスの最初
のロード時に1回行われるだけになり、したがって、getLogger()メソッドは1回しか
呼び出されません。
これは、ささやかではありますがコンピューターの負荷軽減にもなります。

というわけで、一般にLoggerオブジェクトを設定するときには

private static Logger logger = Logger.getLogger(クラス名.class);

というようにstatic指定を行うことを習慣づけておきます。



さて、前回はログのファイル名をstdout.logにしていましたが、ログが出力される
たびにこのファイルのサイズが大きくなってきて、そのうちにパンクしないかと
心配になった人もいることでしょう。
パンクしないまでも、そのままほうっておくと、そのうちに大きくなりすぎて
普通のテキスト・エディターでは開けないという問題が生じる恐れもあります。

このような場合には、いったんTomcatを停止させた後、ログ・ファイルを削除して
再度Tomcatを起動し直すと、ログ・ファイルが新しく作られるのでサイズ0から
再度出発することになります。(ログ・ファイルを削除する代わりに、ファイル名
を変えてもよい。)
しかし、いちいちファイルを削除するのは面倒だし、また、場合によっては以前の
ログ・ファイルが今後発生する問題の何らかの兆候を残していることもあるので、
即座に削除するのは望ましくない。


これに対し、ログのデフォルトのファイル名には、ログを出力したときの日付が
含まれていたことを覚えているでしょう。
つまり、ログ・ファイルはデフォルトでは、日が変わるとファイル名が変わって
いたのです。
これだと、ファイルがいつまでも大きくなり続ける心配はないし、古いログも
残ります。

しかし、このやり方でも、ある日、たまたま何らかの障害が発生して、やたらに
多量のログが書き出され、滅茶苦茶大きなファイルになってしまった、という
事件も起こるかも知れないと心配な人もいるかもしれません。

そのような人のために、ログ・ファイルのサイズには制限(最大ファイル・サイ
ズ)を設けることができ、最大ファイル・サイズを超えるときにはファイル名を
変えて保存し、新たなファイルにログ出力を続行する、という機能も用意されて
います。

この機能を利用するためには、log4j.propertiesファイルを例えば次のような
内容にします。

--------------------------------------------------------
log4j.rootLogger=INFO, A1
log4j.appender.A1=org.apache.log4j.RollingFileAppender
log4j.appender.A1.file=logs/stdout.log
log4j.appender.A1.MaxFileSize=10MB
log4j.appender.A1.MaxBackupIndex=7
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=*%-5p [%d] %c.%M: %m%n
--------------------------------------------------------

ここで、以前FileAppenderを指定していたものがRollingFileAppenderに
代わっていることに注目してください。
このRollingFileAppenderクラスを使うと、ファイル名を順繰り(rolling)して
保存(backup)してくれます。(順繰りの意味は後で説明します。)

また、上で新たに出てきたMaxFileSizeは最大ファイル・サイズを意味し、
MaxBackupIndexは、保存(backup)するログ・ファイルのファイル名の末尾に
つける番号の最大値を意味します。
先ほど「ファイル名を変えて保存」という表現をしたのは、この
「ファイル名の末尾に番号をつける」ことを意味します。

例えば上記の指定の場合は、stdout.logというファイル名の末尾に番号をつけ、
stdout.log.1
stdout.log.2
stdout.log.3
     .
     .
stdout.log.7
というファイル名にして保存することになります。
そしてMaxBackupIndexを7に指定してあると、7が最大の番号になるので、これ
以上のファイルは作られません。そして、stdout.log.7が一番古く、stdout.log.1
が一番新しい保存(backup)ファイルとなって、stdout.log.7より古いファイルは
削除されてしまいます。
先ほど「ファイル名を順繰り(rolling)」と言ったのは、ファイル名が

stdout.log.1
    ↓
stdout.log.2
    ↓
stdout.log.3
    ↓
     .
     .
    ↓
stdout.log.7

と順繰りに押し出されていくことを意味します。

なお、現行のファイル名は、番号のつかないstdout.logになります。


詳しくは、Log4jのAPIリファレンス

http://logging.apache.org/log4j/1.2/apidocs/index.html

でRollingFileAppenderクラスを参照して下さい。


では、実際にlog4j.propertiesファイルを変更して試してみましょう。但し、
MaxFileSizeの値が大きいと、なかなか保存(backup)ファイルが作られないので
下記のように小さい値にしてみましょう。

--------------------------------------------------------
log4j.rootLogger=INFO, A1
log4j.appender.A1=org.apache.log4j.RollingFileAppender
log4j.appender.A1.file=logs/stdout.log
log4j.appender.A1.MaxFileSize=1KB
log4j.appender.A1.MaxBackupIndex=7
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=*%-5p [%d] %c.%M: %m%n
--------------------------------------------------------

そして、Tomacatを起動し直して、HotelClientを何度も実行してみて下さい。

stdout.log.1
stdout.log.2
stdout.log.3
     .
     .
     .
stdout.log.7

ができますね。



(次回に続く)


では、今日はここまでにします。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2009 Future Lifestyle Inc. 不許無断複製