1.はじめに
設計パターンは、ソフトウェア開発の重要な部分です。これらのソリューションは、繰り返し発生する問題を解決するだけでなく、開発者が一般的なパターンを特定することでフレームワークの設計を理解するのにも役立ちます。
このチュートリアルでは、Springフレームワークで使用される最も一般的な4つのデザインパターンについて学習します。
- シングルトンパターン
- ファクトリーメソッドパターン
- プロキシモード
- テンプレートパターン
また、Springがこれらのパターンを使用して開発者の負担を軽減し、ユーザーが煩わしいタスクをすばやく実行できるようにする方法についても検討します。
2.シングルトンモード
シングルトンモードは、アプリケーションごとに1つのオブジェクトインスタンスのみが存在することを保証するメカニズムです。このモードは、共有リソースを管理する場合や、ロギングなどのクロスドメインサービスを提供する場合に役立ちます。
2.1シングルトンBean
通常、シングルトンはアプリケーションに対してグローバルに一意ですが、Springでは、この制約はより広くなります。Springによって定義されたシングルトンは、Spring IOCコンテナで唯一のものです。実際には、これは、Springがアプリケーションコンテキストのタイプごとに1つのBeanしか作成しないことを意味します。
アプリケーションは複数のSpringコンテナーを持つことができるため、Springのアプローチは厳密なシングルトン定義とは異なります。したがって、複数のコンテナがある場合、同じクラスの複数のオブジェクトが単一のアプリケーションに存在する可能性があります。
デフォルトでは、SpringはすべてのBeanをシングルトンとして作成します。
2.2シングルトンオブジェクトの自動アセンブリ
たとえば、アプリケーションコンテキストで2つのコントローラーを作成し、同じタイプのBeanを各コントローラーに挿入できます。
まず、BookRepositoryを作成して、Bookドメインオブジェクトを管理します。
次に、BookRepositoryを使用してライブラリ内の書籍の数を返すLibraryControllerを作成します。
@RestController
public class LibraryController {
@Autowired
private BookRepository repository;
@GetMapping("/count")
public Long findCount() {
System.out.println(repository);
return repository.count();
}
}
最後に、IDで本を見つけるなど、本固有の操作に焦点を当てたBookControllerを作成します。
@RestController
public class BookController {
@Autowired
private BookRepository repository;
@GetMapping("/book/{id}")
public Book findById(@PathVariable long id) {
System.out.println(repository);
return repository.findById(id).get();
}
}
次に、このアプリケーションを起動して、/ countおよび/ book / 1に対してGETを実行します。
curl -X GET http://localhost:8080/count
curl -X GET http://localhost:8080/book/1
アプリケーションの出力では、2つのBookRepositoryオブジェクトが同じオブジェクトIDを持っていることがわかります。
com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f
com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f
LibraryControllerとBookControllerのBookRepositoryオブジェクトIDは同じです。これは、Springが両方のコントローラーに同じBeanを注入することを証明しています。
@scope(ConfigurableBeanFactory.scope_prototype)アノテーションを使用して、Beanスコープをシングルトンからプロトタイプに変更し、BookRepository Beanの個別のインスタンスを作成できます。
これにより、Springは作成するBookRepository Beanごとに個別のオブジェクトを作成します。したがって、各コントローラでBookRepositoryのオブジェクトIDを再度確認すると、同じではないことがわかります。
3.ファクトリーメソッドパターン
ファクトリー・メソッド・パターンでは、必要なオブジェクトを作成するための抽象メソッドがファクトリー・クラスにあることが必要です。通常、特定のコンテキストに基づいて異なるオブジェクトを作成します。
たとえば、アプリケーションで車両オブジェクトが必要になる場合があります。航海環境では船を作りたいが、航空宇宙環境では航空機を作りたい:
このため、必要なオブジェクトごとにファクトリ実装を作成し、具象ファクトリメソッドから必要なオブジェクトを返すことができます。
3.1アプリケーションコンテキスト
Springは、依存性注入(DI)フレームワークに基づいてこの手法を使用します。Springは基本的に、BeanコンテナーをBeanを生成するためのファクトリーとして扱います。したがって、SpringはBeanFactoryインターフェースをBeanコンテナーの抽象概念として定義します。
public interface BeanFactory {
getBean(Class<T> requiredType);
getBean(Class<T> requiredType, Object... args);
getBean(String name);
// ...
}
各getBeanメソッドはファクトリメソッドと見なされ、Beanのタイプや名前など、メソッドに提供された条件に一致するBeanを返します。
次に、SpringはBeanFactoryをApplicationContextインターフェースで拡張し、他のアプリケーション構成を導入しました。Springはこの構成を使用して、いくつかの外部構成(XMLファイルやJava注釈など)に基づいてBeanコンテナーを開始します。
次に、AnnotationConfigApplicationContextなどのApplicationContextクラスを使用して、BeanFactoryインターフェースから継承されたさまざまなファクトリメソッドを通じてBeanを作成できます。
まず、簡単なアプリケーション構成を作成します。
@Configuration
@ComponentScan(basePackageClasses = ApplicationConfig.class)
public class ApplicationConfig {
}
次に、コンストラクターパラメーターを受け入れない単純なクラスFooを作成します。
@Component
public class Foo {
}
次に、単一のコンストラクタパラメータを受け入れる別のクラスBarを作成します。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bar {
private String name;
public Bar(String name) {
this.name = name;
}
// Getter ...
}
最後に、ApplicationContextのAnnotationConfigApplicationContextを通じてBeanを作成します。
@Test
public void whenGetSimpleBean_thenReturnConstructedBean() {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Foo foo = context.getBean(Foo.class);
assertNotNull(foo);
}
@Test
public void whenGetPrototypeBean_thenReturnConstructedBean() {
String expectedName = "Some name";
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
Bar bar = context.getBean(Bar.class, expectedName);
assertNotNull(bar);
assertThat(bar.getName(), is(expectedName));
}
getBeanファクトリメソッドを使用すると、クラスタイプとコンストラクタパラメータ(Bar用)のみを使用して、構成済みBeanを作成できます。
3.2外部配置
外部構成に基づいてアプリケーションの動作を完全に変更できるため、このモードはユニバーサルです。
アプリケーションの自動配線オブジェクトの実装を変更したい場合は、使用するApplicationContext実装を調整できます。
たとえば、AnnotationConfigApplicationContextをClassPathXmlApplicationContextなどのXMLベースの構成クラスに変更できます。
@Test
public void givenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean() {
String expectedName = "Some name";
ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
// Same test as before ...
}
4.プロキシモード
エージェントは私たちのデジタルの世界では便利なツールであり、ソフトウェア(ネットワークエージェントなど)の外部で使用することがよくあります。コードでは、プロキシパターンは、あるオブジェクト(プロキシ)が別のオブジェクト(テーマまたはサービス)へのアクセスを制御できるようにするテクノロジです。
4.1事務
プロキシを作成するには、サブジェクトと同じインターフェイスを実装し、サブジェクトへの参照を含むオブジェクトを作成します。
その後、エージェントの代わりにエージェントを使用できます。
Springでは、プロキシBeanを使用して、基本Beanへのアクセスを制御します。トランザクションを使用するときにこのメソッドが表示されます。
@Service
public class BookManager {
@Autowired
private BookRepository repository;
@Transactional
public Book create(String author) {
System.out.println(repository.getClass().getName());
return repository.create(author);
}
}
BookManagerクラスでは、作成メソッドに@Transactionalアノテーションを付けています。このコメントは、createメソッドを自動的に実行するようにSpringに指示します。プロキシがないと、SpringはBookRepository Beanへのアクセスを制御できず、トランザクションの一貫性を確保できません。
4.2 CGLibエージェント
その代わり、SpringはBookRepository Beanをラップするプロキシを作成し、そのBeanを検出してcreateメソッドを自動的に実行しました。
BookManager#createメソッドを呼び出すと、出力を確認できます。
com.baeldung.patterns.proxy.BookRepository$$EnhancerBySpringCGLIB$$3dc2b55c
通常、標準のBookRepositoryオブジェクトIDを確認します。代わりに、EnhancerBySpringCGLIBオブジェクトIDを見ました。
バックグラウンドで、SpringはBookRepositoryオブジェクトをEnhancerBySpringCGLIBオブジェクトとしてラップします。したがって、SpringはBookRepositoryオブジェクトへのアクセスを制御します(トランザクションの一貫性を保証します)。
通常、Springは2種類のプロキシを使用します。
CGLib Proxies – Used when proxying classes
JDK Dynamic Proxies – Used when proxying interfaces
トランザクションを使用して基になるプロキシを公開する場合、Beanへのアクセスを制御する必要があるあらゆる状況で、Springはプロキシを使用します。
5.テンプレートモード
多くのフレームワークでは、コードのほとんどが定型コードです。
たとえば、データベースでクエリを実行する場合、同じ一連の手順を完了する必要があります。
- 接続を確立する
- クエリを実行する
- クリーンアップを実行する
- 接続を閉じる
これらの手順は、テンプレートメソッドパターンの理想的なシナリオです。
5.1テンプレートとコールバック
テンプレートメソッドパターンは、特定の操作に必要なステップを定義し、サンプルステップを実装し、カスタマイズ可能なステップを抽象化して保持する手法です。サブクラスは、この抽象クラスを実装して、欠落しているステップの具体的な実装を提供できます。
データベースクエリの場合、テンプレートを作成できます。
public abstract DatabaseQuery {
public void execute() {
Connection connection = createConnection();
executeQuery(connection);
closeConnection(connection);
}
protected Connection createConnection() {
// Connect to database...
}
protected void closeConnection(Connection connection) {
// Close connection...
}
protected abstract void executeQuery(Connection connection);
}
さらに、コールバックメソッドを提供することで、不足しているステップを提供できます。
コールバックメソッドは、特定の必要な操作が完了したことをサブジェクトがクライアントに通知できるようにするメソッドです。
場合によっては、サブジェクトはこのコールバックを使用して、マッピング結果などの操作を実行できます。
たとえば、executeQueryメソッドを使用する代わりに、executeメソッドが結果を処理するためのクエリ文字列とコールバックメソッドを提供できます。
最初に、Resultsオブジェクトを受け取り、T型のオブジェクトにマッピングするコールバックメソッドを作成します。
public interface ResultsMapper<T> {
public T map(Results results);
}
次に、このコールバックを利用するようにDatabaseQueryクラスを変更します。
public abstract DatabaseQuery {
public <T> T execute(String query, ResultsMapper<T> mapper) {
Connection connection = createConnection();
Results results = executeQuery(connection, query);
closeConnection(connection);
return mapper.map(results);
}
protected Results executeQuery(Connection connection, String query) {
// Perform query...
}
}
このコールバックメカニズムは、SpringがJdbcTemplateクラスで使用するものとまったく同じです。
5.2 JdbcTemplate
JdbcTemplateクラスは、クエリ文字列とResultSetExtractorオブジェクトを受け入れるクエリメソッドを提供します。
public class JdbcTemplate {
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
// Execute query...
}
// Other methods...
}
ResultSetExtractorは、クエリ結果を表すResultSetオブジェクトをTタイプドメインオブジェクトに変換します。
@FunctionalInterface
public interface ResultSetExtractor<T> {
T extractData(ResultSet rs) throws SQLException, DataAccessException;
}
より具体的なコールバックインターフェイスを作成することで、Springは定型コードをさらに削減します。
たとえば、RowMapperインターフェースは、SQLデータの単一行をタイプTのドメインオブジェクトに変換するために使用されます。
public class JdbcTemplate {
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
// Other methods...
}
ResultSetオブジェクト全体(行の反復を含む)を変換するロジックを提供することに加えて、単一の行を変換する方法のロジックを提供できます。
public class BookRowMapper implements RowMapper<Book> {
@Override
public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
Book book = new Book();
book.setId(rs.getLong("id"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
return book;
}
}
このコンバーターを使用すると、JdbcTemplateを使用してデータベースを照会し、各結果行をマップできます。
JdbcTemplate template = // create template...
template.query("SELECT * FROM books", new BookRowMapper());
Springは、JDBCデータベース管理に加えて、次のテンプレートも使用します。
6.まとめ
このチュートリアルでは、Springフレームワークに適用される最も一般的な4つのデザインパターンについて検討しました。
また、Springがこれらのパターンを使用して豊富な機能を提供し、開発者の負担を軽減する方法についても調査しました。