Java利用SPI实现解耦的示例详解
作者:少年的我
概述
SPI的全称是服务提供接口,可以用其来启动框架的扩展和替换组件。
其本质是利用 接口实现+策略模式+配置文件来实现对实现类的动态加载。
在具体的使用中,存在一些约定:
(1)规定在 classPath 的 META-INF/services/ 下,创建该接口的全名称文件
(2)在该文件中,写入该接口实现类全称(路径+文件名),多个实现类的话,分行写。
(3)用的2时候,使用 java.util.ServiceLoader 的 load(Interface.class),获取到实现类,就可以使用了。
值得注意的是,接口实现类必须有一个不带参数的构造方法。
实现案例
在本应用中,存在两个模块,分别为A模块和B模块,这两个模块中,A模块是主模块,B是从模块,B模块是依赖A模块的。但是在目前有一个类,该类中实现在B模块中,A模块需要调用这个类的函数,而模块不能再依赖B模块,此时需要进行解耦。在本实现中,利用SPI的方式进行解耦实现。具体实现方案为:
(1)在A模块新建一个接口:MyLogAppender,具体实现为:
/** * @author Huang gen(kenfeng) * @description 自定义的appender接口 * @Since 2021/02/21 **/ public interface MyLogAppender { /** * 获取实现的appender * @return 返回新建的appender对象 * */ Appender getAppender(); }
这个接口很简单,只是返回一个appender的对象。对于对象的实际操作,在接口的实现中进行操作。
(2)在B模块添加对这个接口的实现,具体的操作为:
/** * @author Huang gen(kenfeng) * @description 自定义的appender * @Since 2021/02/21 **/ @Component public class MeshLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements MyLogAppender,ApplicationContextAware { private ApplicationContext applicationContext; public MeshLogAppender(){ } @Override public Appender getAppender() { MeshLogAppender meshLogAppender = new MeshLogAppender(); return meshLogAppender; } @Override protected void append(ILoggingEvent iLoggingEvent) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String std = simpleDateFormat.format(new Date(Long.parseLong(String.valueOf(iLoggingEvent.getTimeStamp())))); String log = std + "\t" + iLoggingEvent.getLevel() +"\t"+"--- ["+ iLoggingEvent.getThreadName()+"]\t"+iLoggingEvent.getCallerData()[0]+":\t "+iLoggingEvent.getMessage(); FlowMessage input = new FlowMessage(); MeshFlowService meshFlowService = SandboxSystemServiceFactory.getService(MeshFlowService.class); Map<String, Object> body = new HashMap<>(2); body.put("log",log); input.setTenantCode(DefaultTenant.get()); input.setAppCode("epoch"); input.setFlowCode("log_broadcast"); input.setBody(body); FlowMessage output = meshFlowService.process(input); if(!StringUtils.isEmpty(output.getErrorMessage())){ throw new RuntimeException("发布日志时,广播失败"); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
在该接口的申明和接口的实现中,存在一些小的技巧的实现。在接口中,只声明一个类的获取,并没有实现具体的方法。在实现类中,对这个类进行实例化,new一个新的类并返回,此时用户根据这个get方法就可以拿到这个实现类,然后进行实现类的一些操作。这样写可以带来两个好处: i. 代码更简洁,接口的代码简单易懂 ii. 可以在实现类的构造方法中注入一些参数,当用户使用时,直接在get方法里面注入即可。
(3) 在实现类所在文件夹下,也就是sandbox-app-epoch-starter中添加一个配置文件,其配置文件的路径默认为: resources/META-INF/services/,在这个文件夹下新建一个问题,文件名为接口的路径,内容是实现类的路径。由此可以实现接口-->实现类的映射。
如上图中,文件名为:com.alibaba.halo.sandbox.app.util.MyLogAppender
其文件中的内容为:com.alibaba.lattice2.epoch.util.MeshLogAppender
其原理是,当用户使用接口时,会扫描项目下的所有文件,查找文件名为com.alibaba.halo.sandbox.app.util.MyLogAppender,然后根据其内容来查找到相关的实现类
(4)在A,可以直接使用接口来进行调用,具体实现如下:
ServiceLoader<MyLogAppender> myLoaderInterfaceServiceLoader = ServiceLoader.load(MyLogAppender.class); Iterator<MyLogAppender> myLoaderInterfaceIterator = myLoaderInterfaceServiceLoader.iterator(); while (myLoaderInterfaceIterator.hasNext()){ MyLogAppender myLoaderInterface = myLoaderInterfaceIterator.next(); Appender newAppender = myLoaderInterface.getAppender(); newAppender.setName("application"); newAppender.setContext(loggerContext); newAppender.start(); rootLogger.addAppender(newAppender); }
从上面可以看到,其可以直接调用MyLogAppender接口,利用这个接口获取的Appender,之后直接赋值即可。
优势和不足
优点:可以实现代码的解耦
缺点:存在多个实现类的话,无法根据某个参数或者标志位获取实例,只能通过遍历获取,没有实现所谓的懒加载
到此这篇关于Java利用SPI实现解耦的示例详解的文章就介绍到这了,更多相关Java SPI解耦内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!