Springを書く-ゼロからIoC(1)コンテナの前奏を作る-コンポーネントの定義


文書ディレクトリ
  • 概要
  • コンポーネント定義Definition
  • 1パケットスキャンを実現
  • 概要
    そもそも、どうしてこんなことになったのでしょうか.実は私は最初はspringBootのパッケージスキャンに興味があっただけでしたが、最終的にはIOCの容器を書いたので、ここで特別に記録しておきましょう.容器は今料理のように見えますが、AOPはサポートされていません.意外なBugもあるかもしれません.
    まず何がIoC、IoCは の略で、制御反転の意味は、いくつかのjavaのオブジェクト私たち、これはオブジェクトの初期化と作成を処理しますそして 、私たちはこれに私たちが使いたいjavaオブジェクトを要求するだけでいいです.これは多くのjavaオブジェクトを維持しているので、そこでこれを と呼び、逆転を制御するための容器がIoC です.
    では どうやってやったのでしょうか?まず各エンティティを初期化してコンテナに入れ、ある構成に基づいてエンティティ間の関係を構築するという一般的な手法があります.例えば、あるオブジェクトの内部に必要なオブジェクトを追加します.これが (DI)です.
    簡単に言えば 通過 できたIoCの効果です.
    まず、容器などにClassが入っていなければなりません.正確に言えば、classを記述するものです.これらによって、私はClassのオブジェクトを作成し、必要な他のオブジェクトを注入することができます.だから、IOCになります.このClassを記述する実体は欠かせません.私はそれをDefinitionと呼んでいます.
    コンポーネント定義Definition
    このような定義には、どのようなものが含まれているのでしょうか.まずこのクラスそのもの、次にこのクラスの の記述、クラス 、および単例かどうか、工場が必要かどうか、静的工場が必要か動的工場が必要かなど一連のものです.
    ただし、ここでは、構築方法とフィールドに個別のデータ構造が必要であるという問題があり、ExecutableParamDefinitonメソッドを記述するためのパラメータおよびパラメータのタイプとデフォルト値、FieldAwaredDefinitionフィールドのタイプ、注入方法およびデフォルト値を記録するために使用される.
    完了すると、これらのdefinitionは次のようになります.
    public class Definition {
    	/**
    	 *         class
    	 */
    	private Class<?> clazz;
    	
    	/**
    	 *    
    	 */
    	private String name;
    	
    	/**
    	 *        
    	 */
    	private List<ExecutableParamDefinition> initMethod;
    	
    	/**
    	 *       
    	 */
    	private List<ExecutableParamDefinition> destoryMethod;
    	
    	/**
    	 *     
    	 */
    	private Scope scope;
    	
    	/**
    	 *        
    	 */
    	private List<Class<?>> dependsOn;
    	
    	/**
    	 *             
    	 */
    	private boolean isFactoryInjectDefinition;
    	
    	/**
    	 *               
    	 */
    	private boolean isStaticFactory;
    	
    	/**
    	 *       
    	 */
    	private Class<?> factoryComponentClass;
    	
    	/**
    	 *      
    	 */
    	private String factoryMethodName;
    	
    	/**
    	 *              map
    	 */
    	private List<ExecutableParamDefinition> constructorArgsList;
    	
    	/**
    	 *          map
    	 */
    	private Map<String, FieldAwareDefinition> propClassesMap;
    	
    	public Definition() {
    		dependsOn = new LinkedList<>();
    		constructorArgsList = new LinkedList<>();
    		propClassesMap = new LinkedHashMap<>();
    		destoryMethod = new LinkedList<>();
    		initMethod = new LinkedList<>();
    	}
    	//   get/set
    }
    

    説明方法のDefinition:
    public class ExecutableParamDefinition {
    	
    	/**
    	 *        
    	 */
    	private int paramCount;
    	
    	/**
    	 *    
    	 */
    	private String name;
    	
    	/**
    	 *         -   map
    	 */
    	private Map<String, Class<?>> paramNameTypeMap;
    	
    	/**
    	 *         -  map(value       )
    	 */
    	private Map<String, Object> paramNameValueMap;
    	
    	/**
    	*        (name/type)
    	*/
    	private AwareType awareType;
    
    	public ExecutableParamDefinition() {
    		this.paramNameTypeMap = new HashMap<>();
    		this.paramNameValueMap = new HashMap<>();
    	}
    	//   get/set
    }
    

    フィールド注入の説明:
    public class FieldAwareDefinition {
    
    	private String name;
    	private AwareType type;
    	private Class<?> clazz;
    	private Object value;
    	//   get/set
    }
    

    定義されたデータ構造はありますが、具体的な定義データはどこから来ますか?実はSpringのxmlは書くのが面倒だと感じたので、springBootのスキャン方式に倣って定義を取得することにしました.
    パッケージスキャンを実現
    だから、実はスキャンする時環境は2種類に分けられて、1つはjarパッケージの中で、1つはファイルシステムです.
    どういう意味ですか.私が開発した時、すべてのclassは直接フォルダの中にあったので、もちろんjarパッケージもいくつかありましたが、リリースすればすべてのclassがjarパッケージの中に入るので、classファイルは2つの場所にいることができて、直接フォルダかjarパッケージの中にいることができます.
    しかし,どこにいてもスキャンの目的は類似しており,環境によっては1つ以上のスキャン方式が必要であるため,インタフェースを用いてスキャナの挙動を規範化すべきである.
    public interface IPackageScanner {
    
    	/**
    	 *          ,   class
    	 * @return
    	 */
    	List<Class<?>> scanPackage();
    
    	/**
    	 *          ,   class      
    	 * @param parentClazz
    	 * @return
    	 */
    	List<Class<?>> scanSubClazz(Class<?> parentClazz);
    	
    	/**
    	 *          
    	 * @param annotationClazz
    	 * @return
    	 */
    	List<Class<?>> scanAnnotation(Class<?> annotationClazz);
    
    }
    

    では,スキャンの目的は,注釈を含むクラス,あるいはあるクラスのサブクラス,あるいはいっそすべてのクラスを得るためであり,簡単明瞭である.
    まずフォルダのclassをスキャンしましょう.扱いやすいと思います.
    大まかな考え方は、まずclassを格納するフォルダを見つけて、それからフォルダから遍歴して、再帰サブフォルダはclassファイルを探して、それからファイルのアドレスの中でフォルダのアドレスとclass接尾辞を取り除いて、最後にスラッシュを点に置き換えて、Class.forNameclassをロードすることです.
    実は私のところにclassLoaderがあれば、今すぐclassLoaderを使ってロードすることができますが、今は書いていないので、直接forNameもできないわけではありませんが、springbootのような再起動効果を実現するにはclassLoaderが必要です.
    public class FileSystemScanner implements IPackageScanner {
    
    	private File baseDir;
    	private String base;
    	private List<Class<?>> result;
    	
    	public FileSystemScanner(Class<?> baseClass) {
    		String packagePath = baseClass.getPackageName().replace('.', File.separatorChar);
    		baseDir = new File(baseClass.getResource("").getFile());
    		base = baseDir.getAbsolutePath().replace(packagePath, ""); 
    	}
    	
    	public FileSystemScanner(String path) {
    		baseDir = new File(path);
    		base = path;
    	}
    }
    

    これがベースのスキャナーで、アドレスを置き換えるためのbaseDirが保持されています.このbaseDirはファイルの絶対パスでclassファイルのパケットパスを削除して得られたもので、スキャンしたclassファイルの絶対パスで、このbaseを削除するとclass接尾辞を含むパケットパスになります.
    例:C:projectbincomtestHello.class
    これを元にスキャンするとC:projectbincomtestHelloが得られる.classは絶対パス、comtestはパッケージパス、サブフォルダの中にこのようなclassがあることを発見したら:C:projectbincomtestWorld.class、C:projectbinを取り除くだけでcomtestWorldを得ることができます.class、classを削除すると、クラスをロードするためにクラスの全限定名を得ることができます.このC:projectbinはbaseパスです.
    次にclassを再帰的に検索します.
    private void scanClasses(WhenClassFound founded,String base,File file,List<Class<?>> container, Class<?> reference) throws ClassNotFoundException {
    		if (file.isDirectory()) {
    			List<File> files = Arrays.asList(file.listFiles());
    			for (File elem : files) {
    				scanClasses(founded, base, elem,container,reference);
    			}
    		} else {
    			String className = file.getAbsolutePath().replace(base, "");
    			if (!className.toLowerCase().endsWith("class") || className.contains("module-info")) {
    				return;
    			}
    			className = className.replace(".class", "");
    			if (className.startsWith(File.separator)) {
    				className = className.substring(1);
    			}
    			className = className.replace(File.separatorChar, '.');
    			try {
    				Class<?> clazz = Class.forName(className);
    				founded.accept(clazz, container, reference);
    			} catch (Throwable e) {
    			}
    		}
    	}
    
    @FunctionalInterface
    public interface WhenClassFound {
    	
    	void accept(Class<?> clazz, List<Class<?>> container, Class<?> reference);
    	
    }
    

    classを探してもいいですが、このインタフェースは何ですか?実はこのようにして、私はこの3つのスキャン方法を実現するならば、彼らの再帰検索のステップは実はあまり悪くなくて、classを得た後に行う操作だけが違いがあることを発見して、この違いは再帰と循環の内部に包まれて、あまり処理しにくくて、結局1つの差の少ないコードが3部コピーするのは少しひどいです.
    では、どうすればいいのでしょうか.Java 8にはStreamAPIがあります.中にはfilterという方法があります.集合も遍歴しています.内部に違いがあります.では、filterはどうやってやってやったのでしょうか.彼は関数式インタフェースを通じて、ユーザーにフィルタ条件をlambda式の形式でパラメータに体現させ、filterの時にこのlambdaをコールバックするだけで、ユーザーの条件に従ってフィルタリングを行うことができます.
    この考え方に倣って,classを見つけた後に呼び出し,classを格納するListと参照class,および発見したばかりのclassを転送するインタフェースも作成した.
    その後、このようなlambdaを与えるだけで、1つの再帰またはループ内でclassを異なる方法で検索することができる.
    このとき、jarパッケージの中のclassをスキャンすると、classをスキャンした後に行われる動作と、現在classをスキャンする動作には大きな違いがないはずなので、ここではさらに簡単にすることができます.
    Java 8の特性は、インタフェースでdefaultを使用して、直接インタフェースに良い方法を追加することができます.Java 8の特性は,メソッド参照に二重コロンを用いることができ,メソッドのパラメータがlambdaインタフェースパラメータと一致すれば,メソッドはこのlambdaインタフェースの実装方法として直接機能することができる.
    したがって、すべてのclassの判定方法をスキャンし、指定された注釈を含むclassをスキャンする判定方法および指定されたclassサブクラスをスキャンする判定方法は、defaultメソッドとしてPackagesScannerインタフェースに入れることができ、PackagesScannerの実装クラスは、直接メソッドによってそれらを多重化し、スキャンを実行するメソッドをインタフェースに書き込むことを実装仕様とする.
    PackageScannerインタフェースに追加する4つの方法.
    /**
    	 *   lambda  ,    Class,        
    	 * @param clazz    class
    	 * @param container        
    	 * @param reference    
    	 */
    	default void justAdded(Class<?> clazz, List<Class<?>> container, Class<?> reference) {
    		if (isValidClass(clazz)) {
    			container.add(clazz);
    		}
    	}
    	
    	/**
    	 *   lambda  ,    class,            ,     
    	 * @param clazz    class
    	 * @param container        
    	 * @param reference    
    	 */
    	default void assignableAdded(Class<?> clazz, List<Class<?>> container, Class<?> reference) {
    		if (isValidClass(clazz) && reference.isAssignableFrom(clazz) ) {
    			container.add(clazz);
    		}
    	}
    	
    	/**
    	 *   lambda  ,    class,          ,     
    	 * @param clazz    class
    	 * @param container        
    	 * @param reference    
    	 */
    	default void annotationAdded(Class<?> clazz, List<Class<?>> container, Class<?> reference) {
    		if (isValidClass(clazz) && AnnotationUtil.getAnnotation(reference, clazz) != null) {
    			container.add(clazz);
    		}
    	}
    	
    	/**
    	 *        
    	 * @param found        
    	 * @param container        
    	 * @param reference    (    )
    	 */
    	void scanClasses(WhenClassFound found, List<Class<?>> container, Class<?> reference);
    

    次に、3つのスキャン方法の実装について説明します.
    @Override
    	public List<Class<?>> scanPackage() {
    		if (baseDir == null || !baseDir.exists() || baseDir.isFile()) {
    			throw new RuntimeException("     。");
    		}
    		LinkedList<Class<?>> container = new LinkedList<>();
    		this.scanClasses(this::justAdded , container, null);
    		result = container;
    		return new LinkedList<>(container);
    	}
    	
    	@Override
    	public List<Class<?>> scanSubClazz(Class<?> parentClazz) {
    		if (this.result != null) {
    			return this.result.stream()
    					.filter(clazz -> parentClazz.isAssignableFrom(clazz))
    					.collect(Collectors.toList());
    		}
    		if (baseDir == null || !baseDir.exists() || baseDir.isFile()) {
    			throw new RuntimeException("     。");
    		}
    		LinkedList<Class<?>> container = new LinkedList<>();
    		this.scanClasses(this::assignableAdded ,container, parentClazz);
    		return new LinkedList<>(container);
    	}
    	
    	@Override
    	public List<Class<?>> scanAnnotation(Class<?> annotationClazz) {
    		if (this.result != null) {
    			return  this.result.stream()
    					.filter(clazz -> AnnotationUtil.getAnnotation(annotationClazz, clazz) != null)
    					.collect(Collectors.toList());
    		}
    		if (baseDir == null || !baseDir.exists() || baseDir.isFile()) {
    			throw new RuntimeException("     。");
    		}
    		try {
    			LinkedList<Class<?>> container = new LinkedList<>();
    			this.scanClasses(this::annotationAdded ,base, baseDir, container, annotationClazz);
    			return new LinkedList<>(container);
    		} catch (ClassNotFoundException e) {
    			throw new RuntimeException(e);
    		}
    	}
    

    書き続ける時間がある.(to be continue)