記事ディレクトリ
前回の記事 「手動開発 - シンプルな Spring XML 構成ベースのプログラム - ソース コード分析」 では、XML 構成ファイルから Bean オブジェクト情報を読み取り、設計したコンテナーでそれを初期化し、属性を注入して、最後に getBean ( ) メソッドが戻ります。この記事では、アノテーションの観点に基づいて単純な Spring コンテナを実装します。ここでもいくつかの変更を加えます。以前は、xml ファイル名を使用して値を渡すコンテナーを初期化していましたが、ここでは、値を渡すインターフェイス タイプを使用してコンテナーを初期化しました。したがって、この記事には次の 2 つの特徴があります。
- アノテーションに基づいた Spring コンテナ シミュレーションの実装
- インターフェースタイプを使用してiocコンテナを初期化する
@デザインノート@
Spring には多くのアノテーションがありますが、ここでは使用するアノテーションを設計します。では、注釈はどのようにデザインすればよいのでしょうか? Spring のアノテーション設計はメタ アノテーションに基づいています。メタ アノテーションは Java の基礎です。メタ アノテーションは次のとおりです。
-
@目標
アノテーションの使用範囲を指定するために使用されます
- ElementType.TYPE: クラス、インターフェース、アノテーション、列挙
- ElementType.FIELD: フィールド、列挙定数
- ElementType.METHOD:方法
- ElementType.PARAMETER: 仮パラメータ
- ElementType.CONSTRUCTOR:コンストラクター
- ElementType.LOCAL_VARIABLE: ローカル変数
- ElementType.ANNOTATION_TYPE:注解
- ElementType.PACKAGE:包
- ElementType.TYPE_PARAMETER: タイプパラメータ
- ElementType.TYPE_USE: タイプの使用
-
@保持
注釈を指定するための保持ポリシー
- RetentionPolicy.SOURCE: 注釈はソース コード内にのみ保持され、コンパイル中にコンパイラーによって破棄されます。
- RetentionPolicy.CLASS: (デフォルトの保持ポリシー) 注釈は Class ファイルに保持されますが、仮想マシンにはロードされず、実行時に取得できません。
- RetentionPolicy.RUNTIME: 注釈はクラス ファイルに保持され、仮想マシンにロードされ、実行時に取得できます。
-
@文書化されました
- Javadocに注釈を含めるために使用されます
- デフォルトでは、javadoc には注釈が含まれませんが、@Documented 注釈が使用されている場合は、関連する注釈タイプの情報が生成されたドキュメントに含まれます。
-
@遺伝性の
親クラスのアノテーションがサブクラスに継承されることを示すために使用されます。
-
@Repeatable
タグの宣言に使用されるアノテーションは反復可能なタイプのアノテーションであり、同じ場所で複数回使用できます。
これらのメタアノテーションは最も基本的なコンポーネントであり、アノテーションを設計するときに使用する必要があります。次に、独自の ComponentScan アノテーションを設計します。
/**
* @author linghu
* @date 2023/8/30 13:56
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
この注釈が実行時に有効になることを示します。@Target(ElementType.TYPE)
注釈が変更できる型を示します。Type
ソース コードを入力すると、注釈を通じて、TYPE
それにクラス、インターフェイス (注釈の型を含む)、または enum 宣言が含まれていることがわかります。クラスまたはインターフェイスにすることができます。 …:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
このとき、設計した新しいアノテーション ComponentScan を検証するために、新しい LingHuSpringConfig 構成クラスを作成します。実際、この構成クラスは特に何も実装しません。単にクラス名にアノテーション ComponentScan を配置し、値は次のようになります。
/**
* @author linghu
* @date 2023/8/30 14:09
* 这个配置文件作用类似于beans.xml文件,用于对spring容器指定配置信息
*/
@ComponentScan(value = "com.linghu.spring.component")
public class LingHuSpringConfig {
}
LingHuSpringConfig.class
この構成クラスを設定する目的は、コンテナを初期化するときに、インターフェイスを直接渡し、インターフェイス タイプを介して ioc コンテナを初期化し、コンテナが完全なクラス パスcom.linghu.spring.componentをスキャンすることです。私たちがデザインした注釈。
$デザインコンテナ$
実際、このコンテナの設計は、「マニュアル開発 - シンプルな Spring XML 構成ベースのプログラム - ソース コード分析」で説明されているものと似ており、どちらも次のものが必要です。
- 1つは
ConcurrentHashMap
コンテナとして - コンテナを初期化するコンストラクタ。
getBean
ioc コンテナを返すメソッドを提供します。
ここでの作業のほとんどはコンストラクターで行われ、完成した作業は次のようになります。
- 構成クラスを見つけて
@ComponentScan
値を読み取り、クラスパスを取得します。 - 前の手順のクラス パスを通じて、対応するディレクトリのパスに移動して、すべてのファイル (実際には .class ファイル) にインデックスを付ける必要があります。
target
これらのファイルをフィルタリングします。フィルタリング プロセス中に、注釈が追加されているかどうかを判断します。これらのファイルのクラスパスは、ioc コンテナに保存されます。 - ファイルを取得してフィルタリングするときは、コンポーネント ファイルに保存されている .class ファイルの名前を抽出し、これらの名前をコンテナに保存する必要があります。
- 完全なクラスパスを取得し、これらのクラスに @compoment、@controller、@Service などの注釈があるかどうかを確認します。容器に注射する必要がありますか?
- コンポーネント アノテーションの値を取得します。この値は Bean オブジェクトの ID 名として使用され、ioc コンテナーに保存されます。
ついにそれを実現しました。定義したアノテーションを通じて、アノテーションが付けられたクラスのクラスパスをスキャンし、それを作成したコンテナ ioc に追加しました。最終的に、自分たちで設計した ioc コンテナを通じて、必要なオブジェクトを取得しました。IOC はオブジェクトの作成にどのように役立ちますか? リフレクションによって作成された、リフレクションによって必要なクラスパスは、アノテーションから読み取ったものです。
#完全なコード#
LingSpringApplicationContext.java:
package com.linghu.spring.annotation;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author linghu
* @date 2023/8/30 14:13
* 这个类充当spring原生的容器ApplicationContext
*/
public class LingSpringApplicationContext {
private Class configClass;
//ioc里存放的是通过反射创建的对象(基于注解形式)
private final ConcurrentHashMap<String,Object> ioc=
new ConcurrentHashMap<>();
public LingSpringApplicationContext(Class configClass) {
this.configClass = configClass;
// System.out.println("this.configClass="+this.configClass);
//获取到配置类的@ComponentScan(value = "com.linghu.spring.component")
ComponentScan componentScan =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
//取出注解的value值:com.linghu.spring.component。得到类路径,要扫描的包
String path = componentScan.value();
// System.out.println("value="+value);
//得到要扫描包下的资源(.class文件)
//1、得到类的加载器
ClassLoader classLoader =
LingSpringApplicationContext.class.getClassLoader();
path = path.replace(".", "/");
URL resource = classLoader.getResource(path);
// System.out.println("resource="+resource);
//将要加载的资源(.class)路径下的文件进行遍历=》io
File file = new File(resource.getFile());
if (file.isDirectory()){
File[] files = file.listFiles();
for (File f :files) {
//获取"com.linghu.spring.component"下的所有class文件
System.out.println("===========");
//D:\Java\JavaProjects\spring\target\classes\com\linghu\spring\component\UserDAO.class
System.out.println(f.getAbsolutePath());
String fileAbsolutePath = f.getAbsolutePath();
//只处理.class文件
if (fileAbsolutePath.endsWith(".class")){
//1、获取到类名=》字符串截取
String className =
fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
// System.out.println("className="+className);
//2、获取类的完整的路径
String classFullName = path.replace("/", ".") + "." + className;
System.out.println("classFullName="+classFullName);
//3、判断该类是不是需要注入容器,就看该类是不是有注解@compoment,@controller,@Service...
try {
//得到指定类的类对象,相当于Class.forName("com.xxx")
Class<?> aClass = classLoader.loadClass(classFullName);
if (aClass.isAnnotationPresent(Component.class)||
aClass.isAnnotationPresent(Service.class)||
aClass.isAnnotationPresent(Repository.class)||
aClass.isAnnotationPresent(Controller.class)){
//演示一个component注解指定value,分配id
if (aClass.isAnnotationPresent(Component.class)){
Component component = aClass.getDeclaredAnnotation(Component.class);
String id = component.value();
if (!"".endsWith(id)){
className=id;//用户自定义的bean id 替换掉类名
}
}
//这时就可以反射对象,放入到ioc容器中了
Class<?> clazz = Class.forName(classFullName);
Object instance = clazz.newInstance();//反射完成
//放入到容器中,将类的首字母变成小写,这里用了Stringutils
ioc.put(StringUtils.uncapitalize(className),instance);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
//返回容器中的对象
public Object getBean(String name){
return ioc.get(name);
}
}
Gitee: 「Spring コンテナメカニズムの実装」