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实现解耦的详细内容,更多请关注php中文网其它相关文章!
已抢65475个
抢已抢29869个
抢已抢16346个
抢已抢8283个
抢已抢10722个
抢已抢21902个
抢