春のIOCと工場パターン
PS:この記事の内容は比較的ハードコアであり、Javaオブジェクト指向、リフレクション、クラスローダー、ジェネリックス、プロパティ、XML、およびその他の基本的な知識を深く理解する必要があります。
(1)簡単な紹介
Spring IOCについて話す前に、ファクトリパターン(Factory Pattern)について話す必要があります。ファクトリパターンでは、Javaオブジェクトの呼び出し元を呼び出し先の実装ロジックから分離できます。ファクトリパターンは、Javaで最も一般的に使用されるデザインパターンの1つです。このタイプのデザインパターンは、オブジェクトを作成する最良の方法を提供する作成パターンに属します。ファクトリモードでは、オブジェクトを作成するときにクライアントに作成ロジックを公開せず、新しく作成されたオブジェクトを指すために共通のインターフェイスを使用します。
これは少し抽象的かもしれません。簡単に言えば、将来、独自の新しいオブジェクトを作成する必要はありません。オブジェクトのインスタンス化は、ファクトリによって行われます。オブジェクトが必要な場合は、ファクトリに直接要求して取得できます。後で例を見ていきます。ここで注意すべき点は、スプリングIOCとファクトリモードは完全に同じではないということです。最大の違いは、通常のファクトリモードでもnewを使用してオブジェクトを作成しますが、spring IOCはリフレクションを使用してオブジェクトを作成することです。そうすることの利点は何ですか? ?
(2)「新しい」対「リフレクション」
ファクトリーパターンの例を見てみましょう。デモの便宜上、すべてのクラスとインターフェイスを一緒に記述します。
public interface Shape {
void draw();
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public class ShapeFactory {
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
このコードの機能を見てみましょう。このShapeFactoryを調べます。その中にgetShapeメソッドがあります。形状の名前を入力すると、対応する形状のオブジェクトを取得できます。これはいわゆるファクトリであり、このファクトリを取得した後、getShapeメソッドを直接呼び出して、使用するグラフィックオブジェクトを取得できます。このようにして、これらのオブジェクトを使用するクラスを、これらのグラフィッククラスから分離できます。しかし、ファクトリーが3つの異なるオブジェクトを生成できるようになったことは簡単にわかります。ファクトリーに新しいオブジェクトを追加する場合、それは非常に面倒です。実際のファクトリーが突然欲しいのと同じように、コードを変更して再コンパイルする必要があります。新しい生産ラインの追加は非常に面倒です。したがって、春のIOCを生み出した改善を求めなければなりません。
Spring IOCの考え方は、オブジェクトの作成方法が「新規」からリフレクションに変更されたことを除いて、基本的にファクトリーモデルと同じです。これにより、柔軟性が大幅に向上します。ただし、Springのソースコードを読むのは時期尚早なので、Spring IOCの基本原理をシミュレートする簡単な例を書きました。
まず、リフレクションを使用してオブジェクトを作成する場合は、完全なクラス名が不可欠です(リフレクションを覚えていない場合は、リフレクションを確認してください)。次に、必要なオブジェクトをファクトリに伝えるためのクラス名が必要です(ちょうど上記のgetShapeメソッドで渡されたパラメーターshapeTypeと同様に、名前は自由に取得できますが、繰り返すことはできません。このように、オブジェクトを作成するための2つの要素があり、その2つを関連付けるためのキーと値のペアが必要です。次に、パターンが形成されます。クラス名を渡し、ファクトリはキーと値のペアをクエリし、対応する完全なクラス名を見つけ、リフレクションを使用して完全なクラス名でオブジェクトを作成し、それを返します。とても簡単ですか?
言うまでもありませんが、最初にこのキーと値のペアを作成します。これは、いわゆる構成ファイルです。SpringはXMLを使用します。ここでは、簡単にするためにプロパティを使用します。原理は同じです。
//文件名:bean.properties
circle=com.demo.Circle
rectangle=com.demo.Rectangle
square=com.demo.Square
設定ファイルが利用可能になったら、BeanFactoryを作成しましょう。
//文件名:BeanFactory.java
package com.demo;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BeanFactory {
//配置对象(类比spring IOC容器中的Bean定义注册表)
private static final Properties props;
//保存创建好的对象的容器,与类名组成key-value对(类比spring IOC容器中的Bean缓存池)
private static Map<String, Object> beans;
static {
props = new Properties();
//通过类加载器读入配置文件
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
//加载配置文件到配置对象
props.load(in);
//初始化容器
beans = new HashMap<>();
Enumeration<Object> keys = props.keys();
//循环遍历配置对象中的所有的类名(key)
while (keys.hasMoreElements()){
String key = keys.nextElement().toString();
//通过类名拿到全类名(value)
String beanPath = props.getProperty(key);
//利用全类名反射创建对象
Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
//将对象放入容器中
beans.put(key, value);
}
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化properties失败!程序终止!");
} catch (Exception e){
e.printStackTrace();
}
}
public static Object getBean(String beanName){
//从容器中获取对象
return beans.get(beanName);
}
}
他の3つのクラスと1つのインターフェイスは、上記のファクトリパターンの例を使用していますが、この場合、すべてのJavaファイルはcom.demoパッケージにあります。このBeanFactoryを詳しく見てみましょう(筆者が自分で作成したcopycatバージョンで、Spring IOCの基本機能を模倣しています)。最初に、コアを把握します。コアは、Map <String、Object> Beanです。これは、Spring IOCコンテナを直接ターゲットにしています。 Beanバッファプールは、作成されたオブジェクトを格納するために使用され、Mapは、クラス名を介して対応するオブジェクトを直接取得するために使用されます。次に、これらのオブジェクトがどのように生成されるかを確認します。
Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
明らかに、リフレクションはこの文にあります。クラスの完全なクラス名を使用してオブジェクトを作成します。完全なクラス名は、構成ファイルを読み取り、Spring IOCコンテナーでベンチマークすることによって生成されたオブジェクトである、Propertiesオブジェクトに由来します。 Beanはレジストリを定義します。これで、この構成ファイルの機能を理解できたはずです。これは、製造に必要なオブジェクトが書き込まれる、工場に与える製造オーダーのようなものです。Beanバッファープールは、ファクトリーのウェアハウスに相当し、生成されたオブジェクトを格納するために使用され、取り出されるのを待機しています。そして、私たちが定義したBean実装クラス(上記のCircle、Squareなど)は、これらのオブジェクトが何であり、どのように生成されるかをファクトリーに伝える描画と同等です。要約しましょう:
モジュール | 特徴 |
---|---|
Bean実装クラス | 作り方を説明する |
Bean定義レジストリー | 何を作成する必要があるかを説明する |
Beanバッファープール(HashMap実装) | 生成されたオブジェクトを保存する |
(3)実際のSpring IOC
私が書いた「コテージ」のIOCを読んだ後、絵を描いて実際の春のIOC構造の実行プロセスを見てみましょう、実際、それは基本的に私が書いたものと同じです。
実行プロセスを見てみましょう:
- Bean構成情報をBean定義レジストリーに読み込みます。
- Bean Registryに従ってBeanをインスタンス化します
- インスタンス化されたBeanインスタンスをBeanバッファープールに入れます(HashMap実装)
- アプリケーションは、クラス名によってBeanキャッシュプールからBeanインスタンスを取得します。
よく読んだ後、Spring IOCコンテナを使用してオブジェクトを作成する方法を見てみましょう。まず、上記のコピーIOCと同様に、XMLファイルを最初に書き込む必要があります。XMLはプロパティよりも複雑ですが、幸い、そのような複雑な部分はまだ必要ありません。まず、公式Webサイトにアクセスして、テンプレートを見つけてコピーします。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
これは、Spring構成ファイルのテンプレートです。いくつかのXMLファイルの制約は上記で定義されています。つまり、これらのXMLタグに書き込むことができます。今後の開発は、このテンプレートに基づいて行われます。次に、クラス名と完全なクラス名で構成されるキーと値のペアです。これらのプロセスは上記とまったく同じですが、書き込みが異なります。書き込むべきものはすべて書き込みます(私の書き込みに注意してください)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="circle" class="com.demo.Circle"/>
<bean id="square" class="com.demo.Square"/>
<bean id="rectangle" class="com.demo.Rectangle"/>
</beans>
明らかに、このIDはクラス名に相当し、このクラスは完全なクラス名に相当します。これは基本的に私のコピーと同じですか?
もちろん、フレームワークを使用しているので、面倒な作業を行う必要はありません。つまり、オブジェクトの作成方法と管理方法を気にする必要はありません。これらはすべて、Springによって行われます。私たちがしなければならないのは、春のIOCコンテナに直接オブジェクトを取り出すよう依頼することです。では、このコンテナはどこにありますか?もちろん、このコンテナは手動でしか作成できません。そうでない場合、薄い空気からは作成されません。
このインターフェース継承図を見てみましょう。2つに注目する必要があります。1つは内部の最上位のインターフェースBeanFactoryで、もう1つは下部のApplicationContextインターフェースです。BeanFactoryは単純なコンテナであり、コンテナの基本機能、getBean、containsBeanなどの一般的なメソッドを実装します。ApplicationContextはアプリケーションコンテキストであり、単純なコンテナに基づいてコンテキストの特性を追加します。その機能はBeanFactoryよりも強力であるため、開発時には通常ApplicationContextインターフェースを使用します。もちろん、「アプリケーションコンテキスト」という名前は少し変わっているかもしれませんが、春のIOCコンテナーインターフェイスであることを覚えておく必要があります。インターフェースの次のステップは、実装クラスを見つけることです。
ApplicationContextには多くの実装クラスがあります。最も一般的に使用される1つのClassPathXmlApplicationContextを選択しましょう。
彼の名前を見てみましょう:ClassPathXmlApplicationContext。クラスパスXMLアプリケーションコンテキストとして翻訳されています。まあ、名前はもっと奇妙です。実際、これはアプリケーションコンテキスト実装クラスであり、クラスパスの下のXMLファイルを構成ファイルとしてのみ読み取ることができます。次に、別の例を示します:FileSystemXmlApplicationContext、彼は何をしますか?まあ、それはファイルシステムアプリケーションコンテキストです。つまり、XMLをディスク上のどこにでも(読み取り権限が必要です)構成ファイルとして読み取ることができます。
そのため、IOCコンテナーをインスタンス化できます。
package com.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//构造函数参数为配置文件名称
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
Shape circle = (Shape) applicationContext.getBean("circle");
circle.draw();
}
}
コンテナ内のオブジェクトを取得します。基本的には私のコピーと同じですか?これを読んで、春のIOCの原理を理解できたはずですが、春のIOCの詳細やその他の機能を分析するために、新しい記事を更新します。最後に、スクリーンショットを貼ります。プロジェクトの構造がわからない場合は、確認してください。
2020年5月6日