Spring におけるアノテーションと設定ファイルの使い分け - SpEL, util:properties, AOP

Spring ではアプリケーションの構成(Bean 定義)を設定するための方法がいくつか用意されており、どれを使えばよいのか悩ましいところですが、個人的には以下のような使い分けが良さそうだと考えています。

  • DBの接続設定等のパラメータ系はプロパティファイルに記述
  • Bean定義はアノテーションを使ってコードに記述
  • コンポーネントのスキャン、AOP、プロパティファイルの読み込み等のセッティング系は XML ファイルに記述

というわけで、以下のような環境を使って MongoDB を使うサンプルを書いてみました。

  • Maven 3.0.2
  • Spring 3.0.5
  • MongoDB 1.7.5

サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20110205/

パラメータ系

MongoDB の接続設定をプロパティファイルに記述します。

src/main/resources/fits/sample/mongodb.properties
uri=mongodb://localhost/
db=sample1

Bean定義

MongoDB にデータを保存・取得するクラスを作成します。

また、サンプルを単純化するため、JSONIC を使って任意のオブジェクトを JSON と相互変換するようにしたり(MongoDB には JSON が扱いやすいので)、
MongoDB のコレクションを KVS 風に使ったりしてます。(本来、MongoDB のコレクションは RDB のテーブルのようなものなので、普通以下のような使い方はしないと思いますが)

src/main/java/fits/sample/MongoDbOperations.java
package fits.sample;

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;

//MongoDB用のDB操作クラス
@Component
public class MongoDbOperations implements DbOperations {

    @Autowired
    private DB db;

    public <T> T get(String key, Class<T> cls) {
        T result = null;

        DBObject obj = db.getCollection(key).findOne();

        if (obj != null) {
            //JSON文字列化
            String json = JSON.serialize(obj);
            //JSONを任意のオブジェクトへ変換
            result = net.arnx.jsonic.JSON.decode(json, cls);
        }

        return result;
    }

    public <T> void put(String key, T obj) {
        DBCollection col = db.getCollection(key);
        //既存コレクションを削除
        col.drop();

        //オブジェクトをJSONへ変換
        String json = net.arnx.jsonic.JSON.encode(obj);
        col.insert((DBObject)JSON.parse(json));
    }
}

次に、@Configuration アノテーションを使った設定クラスを作成します。

  • @Configuration で Bean 定義を Java クラスに記述
  • @Value アノテーションで SpEL(Spring Expression Language)を使って、mongodb.properties ファイルの値を変数の初期値として設定
  • @Bean アノテーションで mongoDb() メソッドの結果が、上記 MongoDbOperations クラスの @Autowired した db 変数にインジェクションされるように設定

ところで、@Value を使った初期値設定は、使い方によって依存関係を増やす事になるので、乱用はなるべく避けて @Configuration の中など限定的に使うのが安全だと思っています。

src/main/java/fits/sample/AppConfig.java
package fits.sample;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;

import com.mongodb.Mongo;
import com.mongodb.DB;
import com.mongodb.MongoURI;

//設定クラス
@Configuration
public class AppConfig {
    //mongodbProperties の uri の値で初期値設定
    private @Value("#{mongodbProperties.uri}") String dbUri;
    //mongodbProperties の db の値で初期値設定
    private @Value("#{mongodbProperties.db}") String dbName;

    @Bean
    public Mongo mongo() throws Exception {
        return new Mongo(new MongoURI(dbUri));
    }

    @Bean
    public DB mongoDb() throws Exception {
        return mongo().getDB(dbName);
    }
}

セッティング系

コンポーネントのスキャン設定とプロパティファイルの設定を XML に記述します。

  • context:component-scan 要素で fits.sample パッケージをコンポーネントのスキャン対象に設定
  • util:properties 要素で mongodb.properties プロパティファイルの内容を mongodbProperties に割り当てるように設定

context:component-scan 要素で fits.sample パッケージの @Component や @Configuration がスキャンされ、@Autowired へのインジェクションが自動的に実施されます。

また、context:component-scan 要素を使う代わりに、Java で ClassPathBeanDefinitionScanner クラス等を使う手もありますが、こういうのは外部ファイルで定義しておいた方が後の拡張がし易くなると思います。

src/main/resources/applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd
      http://www.springframework.org/schema/util
      http://www.springframework.org/schema/util/spring-util-3.0.xsd">

    <context:component-scan base-package="fits.sample" />

    <util:properties id="mongodbProperties" location="classpath:fits/sample/mongodb.properties" />
</beans>

ビルドと実行

動作確認用のクラスを以下のように作成し Maven で実行してみます。(事前に MongoDB を起動しておきます)

src/main/java/fits/sample/App.java
package fits.sample;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//アプリケーションクラス
public class App {
    public static void main( String[] args ) {
        //アプリケーション構成ファイルの読み込み
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        DbOperations dbo = ctx.getBean(DbOperations.class);

        dbo.put("no1", new Data("test1", 5));

        Data d = dbo.get("no1", Data.class);
        System.out.printf("data = %s, %d \n", d.getName(), d.getPoint());
    }
}
MongoDb の実行例
> mongod -dbpath db
サンプルの実行例
> mvn compile
> mvn exec:java
・・・
情報: Loading properties file from class path resource [fits/sample/mongodb.properties]
data = test1, 5
・・・

ちなみに、pom.xml ファイルの内容は以下の通りです。
@Configuration アノテーションを使うには cglib が必要になるようなので注意。(cglib が無いと実行時に例外が発生)

pom.xml
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>fits.sample</groupId>
  <artifactId>spring_mongo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>Spring mongodb sample</name>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <!-- Java クラスの実行プラグイン -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <configuration>
          <executable>java</executable>
          <mainClass>fits.sample.App</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
     <!-- Spring 用のリポジトリ設定 -->
     <repository>
        <id>com.springsource.repository.maven.release</id>
        <url>http://maven.springframework.org/release/</url>
     </repository>
     <!-- JSONIC 用のリポジトリ設定 -->
     <repository>
        <id>seasar.repository</id>
        <url>http://maven.seasar.org/maven2/</url>
     </repository>
  </repositories>
  <dependencies>
    <!-- Spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.0.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2</version>
    </dependency>
    <!-- MongoDB -->
    <dependency>
      <groupId>org.mongodb</groupId>
      <artifactId>mongo-java-driver</artifactId>
      <version>2.4</version>
    </dependency>
    <!-- JSONIC -->
    <dependency>
      <groupId>net.arnx.jsonic</groupId>
      <artifactId>jsonic</artifactId>
      <version>1.2.0</version>
    </dependency>
  </dependencies>
</project>

AOP の追加

上記のサンプルに AOP の機能を追加してみます。

XML のアプリケーション構成ファイルに設定を追加し、@Aspect アノテーションを付与したクラスを追加すれば AOP が適用できます。

まず、applicationContext.xml に以下を設定します。

aop:aspectj-autoproxy 要素に関しても、org.springframework.aop.config.AopConfigUtils クラスの registerAspectJAutoProxyCreatorIfNecessary() 等を使えば、Java クラス内で定義できますが、これも外部ファイルで設定しておいた方が良さそうに思います。

src/main/resources/applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  ・・・
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
      ・・・
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <context:component-scan base-package="fits.sample">
    <!-- @Aspect アノテーションをスキャン対象とするための設定 -->
    <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect" />
  </context:component-scan>
  ・・・
  <aop:aspectj-autoproxy />
</beans>

あとは、@Aspect アノテーションを使ったクラスを用意するだけです。
今回は MongoDbOperations の put メソッドの実行前後にログ出力するような簡単なアドバイスを用意しました。

src/main/java/fits/sample/SampleAspect.java
package fits.sample;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class SampleAspect {
	//DbOperations 実装クラスの put メソッド実行にアドバイスを適用
    @Around("execution(void DbOperations+.put(..))")
    public Object aroundPut(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("*** before: " + pjp);

        Object result = pjp.proceed();

        System.out.println("*** after: " + pjp);

        return result;
    }
}

最後に、pom.xmlAspectJ の設定を追加し、実行すれば AOP の適用が確認できます。

pom.xml
<project・・・>
  ・・・
  <dependencies>
    ・・・
    <!-- AspectJ -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjtools</artifactId>
      <version>1.6.10</version>
    </dependency>
  </dependencies>
</project>
実行例
> mvn compile exec:java
・・・
情報: Loading properties file from class path resource [fits/sample/mongodb.properties]
*** before: execution(void fits.sample.DbOperations.put(String,Object))
*** after: execution(void fits.sample.DbOperations.put(String,Object))
data = test1, 5
・・・