JavaSPIメカニズムの実装からSpringBootSPI拡張まで



From Java Spi Mechanism Implementation Spring Boot Spi Extension



JavaSPIの実装

前の記事を参照してください:
https://blog.csdn.net/shang_xs/article/details/86560469

Spring BootSPIの実装

1.SPI機能

さまざまな実装を選択して特定のコードを実行し、SPIインターフェイスを定義し、SPIの使用状況を定義することができます(前提条件)。プロキシクラスを生成するFactoryBean(コア)。



public interface ISpi<T> { boolean verify(T condition) }

上記の実装を見た後、質問があります。この条件を満たすサブクラスが複数ある場合はどうなりますか?したがって、ソートされたインターフェースを追加して、最も優先度の高い一致を返すことができます。

public interface ISpi<T> { boolean verify(T condition) /** * Sort, the smaller the number, the higher the priority * @return */ default int order() { return 10 }

インターフェイスを定義した後、ユーザーはそれをどのように使用する必要がありますか?



2.制約spiを使用して実装された制約

JDKプロキシモードに基づくと、最大の前提の1つは、プロキシクラスはインターフェイスに従ってのみ生成できるということです。したがって、SPIを使用する場合、ユーザーがISpiを継承するインターフェイスを定義する必要があります。そうすれば、特定のSPIがこのインターフェイスを実装できます。

2つ目は、Springエコシステムでは、すべてのSPI実装がBeanである必要があり、自動的にスキャンするか、アノテーションを使用して構成する必要があるということです。そうでなければ、プロキシクラスはすべてのSPI実装を取得するのに十分ではありません。

3.spiによって使用される制約

SPIインターフェースを使用する場合、実際にはプロキシクラスを挿入するため、インターフェースを介して導入されます。具体的な実装クラスは記述しないでください。



4.すべてのSPI実装クラスを取得します

(org.springframework.beans.factory.ListableBeanFactory#getBeansOfType(java.lang.Class))jdkからプロキシクラスを生成します。プロキシクラスでは、すべてのSPI実装を反復処理し、渡された最初のパラメーターに従って照合します。最初にヒットしたSPI実装クラス、特定を達成するための上記の手順の実装は、比較的簡単です。

public class SpiFactoryBean<T> implements FactoryBean<T> { private Class<? extends ISpi> spiClz private List<ISpi> list public SpiFactoryBean(ApplicationContext applicationContext, Class<? extends ISpi> clz) { this.spiClz = clz Map<String, ? extends ISpi> map = applicationContext.getBeansOfType(spiClz) list = new ArrayList<>(map.values()) list.sort(Comparator.comparingInt(ISpi::order)) } @Override @SuppressWarnings('unchecked') public T getObject() throws Exception { // jdk dynamic proxy class generation InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for (ISpi spi : list) { if (spi.verify(args[0])) { // The first parameter is selected as a condition return method.invoke(spi, args) } } throw new NoSpiChooseException('no spi server can execute! spiList: ' + list) } } return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{spiClz}, invocationHandler) } @Override public Class<?> getObjectType() { return spiClz }

シナリオ設計後、実装する必要があります。ただし、実装が単純すぎるため、設計プロセスも手書きされます。これは、動的プロキシクラスを生成するために使用されるインターフェイス定義ISpiおよびSpiFactoryBeanです。
次に、機能デモンストレーションの簡単な例を記述し、テキスト出力用のIPrintを定義して、2つの実装(1つはコンソール出力、もう1つはログ出力)を提供します。

public interface IPrint extends ISpi<Integer> { default void execute(Integer level, Object... msg) { print(msg.length > 0 ? (String) msg[0] : null) } void print(String msg) The specific implementation class is as follows, the external user implements the call through the execute method, where level<=0Select console output otherwise select log file mode output @Component public class ConsolePrint implements IPrint { @Override public void print(String msg) { System.out.println('console print: ' + msg) } @Override public boolean verify(Integer condition) { return condition <= 0 } } @Slf4j @Component public class LogPrint implements IPrint { @Override public void print(String msg) { log.info('log print: {}', msg) } @Override public boolean verify(Integer condition) { return condition > 0 }

前の手順は、一般的な手順と同じです。使用する姿勢は何ですか?

@SpringBootApplication public class Application { public Application(IPrint printProxy) { printProxy.execute(10, ' log print ') printProxy.execute(0, ' console print ') } public static void main(String[] args) { SpringApplication.run(Application.class, args) }

上記のアプリケーションのコンストラクターを見ると、IPrintパラメーターを渡す必要があります。 SpringはコンテナからBeanをパラメータとして検出します。このBeanは生成したプロキシクラスであるため、さまざまなパラメータに従って特定の実装を選択できます。クラス

したがって、問題は、プロキシクラスを宣言し、以下を構成し、FactoryBeanメソッドを介してBeanを宣言し、@ Primaryアノテーションを追加して、注入したプロキシクラスが確実に注入されるようにする方法です。

@Configuration public class PrintAutoConfig { @Bean public SpiFactoryBean printSpiPoxy(ApplicationContext applicationContext) { return new SpiFactoryBean(applicationContext, IPrint.class) } @Bean @Primary public IPrint printProxy(SpiFactoryBean spiFactoryBean) throws Exception { return (IPrint) spiFactoryBean.getObject() } @SpringBootApplication public class SpringBootApplication { public SpringBootApplication (Iprint pringProxy){ pringProxy.execute(10,'log.print') pringProxy.execute(1,'console.print') } public static void main(String[] args) { SpringApplication.run(SpringBootApplication.class, args) } }

具体的な実装については、以下を参照してください。 https://github.com/dwyanewede/project-learn

拡張ポイントの読み込み:com.learn.demo.spi.ISpi