可参考上一篇文章:
https://blog.csdn.net/shang_xs/article/details/86560469
就是可以选中不同的实现来执行具体的代码,定义SPI接口,以及SPI的使用姿势(前提) 一个生成代理类的FactoryBean (核心)
public interface ISpi<T> {
boolean verify(T condition);
}
看到上面的实现之后,就会有一个疑问,如果有多个子类都满足这个条件怎么办?因此可以加一个排序的接口,返回优先级最高的匹配者
public interface ISpi<T> {
boolean verify(T condition);
/**
* 排序,数字越小,优先级越高
* @return
*/
default int order() {
return 10;
}
接口定义之后,使用者应该怎么用呢?
基于JDK的代理模式,一个最大的前提就是,只能根据接口来生成代理类,因此在使用SPI的时候,我们希望使用者先定义一个接口来继承ISpi,然后具体的SPI实现这个接口即可
其次就是在Spring的生态下,要求所有的SPI实现都是Bean,需要自动扫描或者配置注解方式声明,否者代理类就不太好获取所有的SPI实现了
在使用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动态代理类生成
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])) {
// 第一个参数作为条件选择
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用于文本输出,并给两个实现,一个控制台输出,一个日志输出
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);
具体的实现类如下,外部使用者通过execute方法实现调用,其中level<=0时选择控制台输出;否则选则日志文件方式输出
@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);
}
看上面的Application的构造方法,要求传入一个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