IoC(制御の反転)の概念と実装
IoC(Inversion of Control)は、オブジェクトの生成および管理の制御を開発者からフレームワーク(コンテナ)に移譲する設計思想です。従来の開発では、呼び出し元が依存するオブジェクトを直接インスタンス化(new)していましたが、Springではこれをコンテナが行います。
まず、基本的な依存関係を定義します。ここでは「ユーザー情報のログ出力」を例にします。
// データアクセス層のインターフェース
public interface Logger {
void log(String message);
}
// 実装クラス
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
次に、ビジネスロジック層でこのLoggerを使用します。従来の方法では、サービスクラス内でnew ConsoleLogger()としていましたが、SpringではXML設定でBeanを定義し、コンテナから取得します。
<!-- applicationContext.xml -->
<bean id="consoleLogger" class="com.example.demo.ConsoleLogger"/>
実行コードは以下のようになります。
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Logger logger = (Logger) context.getBean("consoleLogger");
logger.log("System started");
}
}
DI(依存性注入)の実装パターン
DI(Dependency Injection)は、オブジェクト間の依存関係をコンテナが設定する仕組みです。主要な注入方法には以下があります。
1. Setter注入
Setterメソッドを経由して依存オブジェクトを注入します。最も一般的な方法の一つです。
public class UserActionService {
private Logger logger;
// Setterメソッド
public void setLogger(Logger logger) {
this.logger = logger;
}
public void executeAction(String user) {
logger.log("User action performed by: " + user);
}
}
<bean id="userActionService" class="com.example.demo.UserActionService">
<!-- nameはプロパティ名、refは注入するBeanのID -->
<property name="logger" ref="consoleLogger"/>
</bean>
また、単純型(String, intなど)の注入も可能です。
public class ConsoleLogger implements Logger {
private String prefix;
public void setPrefix(String prefix) {
this.prefix = prefix;
}
// ...
}
<bean id="consoleLogger" class="com.example.demo.ConsoleLogger">
<property name="prefix" value="DEBUG"/>
</bean>
2. コンストラクタ注入
コンストラクタの引数経由で依存関係を解決します。依存関係が必須である場合に推奨されます。
public class UserActionService {
private final Logger logger;
public UserActionService(Logger logger) {
this.logger = logger;
}
// ...
}
<bean id="userActionService" class="com.example.demo.UserActionService">
<constructor-arg ref="consoleLogger"/>
</bean>
引数の型が曖昧な場合は、indexやtype属性を使用して明示的に指定できます。
3. 自動ワイヤリング(Autowiring)
autowire属性を使用することで、XMLの記述を省略できます。型(byType)または名前(byName)による自動結合が可能です。
<bean id="userActionService" class="com.example.demo.UserActionService" autowire="byType"/>
Beanのスコープとライフサイクル
Beanのデフォルトスコープは「Singleton(単例)」であり、コンテナから取得するたびに同じインスタンスが返されます。一方、「Prototype(プロトタイプ)」を指定すると、取得のたびに新しいインスタンスが生成されます。
<bean id="prototypeService" class="com.example.demo.UserActionService" scope="prototype"/>
Singletonはステートレスなサービス(Service, Dao, Util)に適し、Prototypeは状態を持つオブジェクトに適しています。
Beanのライフサイクル(初期化処理と破棄処理)を管理するには、init-methodおよびdestroy-methodを使用します。破棄メソッドを実行するには、コンテナを正常に閉じる必要があります。
<bean id="lifecycleBean" class="com.example.demo.LifecycleBean"
init-method="initialize" destroy-method="cleanup"/>
// コンテナのクローズ処理
((ClassPathXmlApplicationContext) context).close();
または、Spring標準のインターフェースInitializingBeanおよびDisposableBeanを実装する方法もあります。
外部ライブラリのBean管理
開発者が作成したクラスだけでなく、Druidなどのサードパーティ製ライブラリのクラスもBeanとして管理できます。通常、propertyタグを使用して接続設定を注入します。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="admin"/>
<property name="password" value="secret"/>
</bean>
設定値を外部ファイル(jdbc.properties)に分離する場合は、context名前空間を使用してプロパティファイルをロードし、${...}プレースホルダーで参照します。
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<!-- 他のプロパティも同様にプレースホルダーを使用 -->
</bean>
アノテーションによる開発
XML設定に代わり、アノテーションを使用することでより簡潔に記述できます。
コンポーネントスキャンとBean定義
@Component、@Service、@Repository、@Controllerをクラスに付与することで、Beanとして登録されます。以下は設定クラスの例です。
@Configuration
@ComponentScan("com.example") // スキャン対象のパッケージ
public class AppConfig {
// 設定クラス本体は空で可
}
@Service
public class UserActionService {
// ...
}
依存性注入のアノテーション
@Autowiredを使用して、型に基づき自動的に依存オブジェクトが注入されます。
@Service
public class UserActionService {
@Autowired
private Logger logger;
// ...
}
同一型のBeanが複数ある場合、@QualifierでBean名を指定して解決します。また、単純な値は@Valueで注入可能です。
@Value("${app.name}")
private String appName;
サードパーティ製Beanのアノテーション設定
@Beanアノテーションをメソッドに付与し、設定クラス内でインスタンスを生成・返却します。
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/mydb");
ds.setUsername("admin");
ds.setPassword("secret");
return ds;
}
}
設定を複数のクラスに分割する場合は、@Importを使用してインポートします。
@Configuration
@Import(DatabaseConfig.class)
@ComponentScan("com.example")
public class AppConfig {
}