热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java三点定位_架构师必知必会:Java内置的控制反转机制ServiceProvider

前言Java统治服务器编程领域多年还未有退位趋势,以IoC(控制反转)思想为核心的Spring功不可没。大多数时候,我们都可以使用Spring框架来实现

前言

Java统治服务器编程领域多年还未有退位趋势,以IoC(控制反转)思想为核心的Spring功不可没。大多数时候,我们都可以使用Spring框架来实现我们的依赖注入,但仍有很多场景,我们期望自己的代码有更少的依赖、适应更多的场景,比如跨Android和服务端、跨JVM语言的组件拼装。

其实从Java6开始已经提供了一套依赖注入标准“Service Provider”和相应的工具”ServiceLoader”来实现我们自己的控制反转,且其已经广泛应用在JDK的扩展性设计之中(如:脚本引擎ScriptEngine, 字符集Charset, 文件系统FileSystems, 网络通讯NIO),并越来越多地被其它开源组件所使用(如:Web标准Servlet3.0, 通用日志接口slf4j-api:1.3),Java9进一步对“Service Provider”进行扩展实现了Java的模块化。所以”Service Provider”机制是Java越来越重要的基础知识之一。

容我带领各位,通过JDK文档和源码来一步步了解”Service Provider”,进而掌握通过Java自带能力零依赖实现自己的动态依赖注入的方法,或按Java标准来扩展JDK、日志、Http服务的能力。

“Service Provider“标准

”Service Provider”首先是作为一个标准被Javase所吸纳:

https://docs.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service_Provider

标准中对”Service Provider”的定义可以总结为三句话:

  1. 一个”Service”(服务)就是一组知名的接口或(通常是抽象)类的集合,“Service Provider“(服务提供者)就是对服务的特定实现;
  2. 服务提供者,通过jar包中的“META-INF/services/fully-qualified.name.of.service.Interface“文件包含实现类的完全限定名,将实现类发布为提供者;
  3. 服务查找机制,通过遍历ClassPath中所有的上述文件内容,来查找并创建提供者的实例。

以上是”Service Provider”标准中最重要的三点,请大家务必完全理解并牢记于心,标准中还有其它一些限制条件,只需了解以便问题的快速定位,如:

  1. 提供者必须包含无参构造函数,以便服务查找机制可以通过反射创建其实例;
  2. 提供者发布文件中,可以通过”#”开头定义注释行;
  3. 提供者发布文件中,可以通过换行来分隔多个实现类;
  4. 须在安全上下文中调用服务查找者…

服务加载机制

JDK中内置了“Service Provider“加载工具类”ServiceLoader”,通过静态方法”ServiceLoader.load()”方法,即可创建指定类型的”Service Provider”迭代器(ServiceLoader本身),通过遍历迭代器即可得到所有Provider的实例。”ServiceLoader”的源代码可以在JDK中找到,实现”服务加载”最关键的是下面几段(以Oracle JDK8源码为例):

1、”Service Provider”标准定义的服务发布文件路径前缀:

17358570d216bdf6d5bfcb372ff26029.png

2、使用(系统或用户)ClassLoader找到指定”Service”的”Provider”所有发布文件

eb867c8eb65d43e6e844d75c9e7548d2.png

3、根据发布文件中的类名加载Provider的Class

593781c5bc819f8337b8aa7a7fbbb618.png

4、通过Provider的Class创建Provider实例

849fc9e7b0ed5b6198503bb646349735.png

我们看到ServiceLoader通过调用提供者Class的newInstance方法,创建了服务提供者的实例,这也是为什么服务提供者需要有一个无参构造函数的原因。

出于Web应用安全隔离的需要,Tomcat在实现Servlet3.0标准的” ServletContainerInitializer”应用自启动机制中,使用了遵循”Service Provider”标准的另一套服务查找实现”WebappServiceLoader”,主要区别在于去”WEB-INF/lib”下而不是直接通过类加载器查找”Service Provider”文件,感兴趣的同学可以去看Tomcat的源码:org.apache.catalina.startup.WebappServiceLoader

服务使用者

罗马不是一天建成的,JDK也是。从MessageDigest、Charset、ScriptEngine等诞生于Java不同时期的API来看,”Service Provider”标准和”ServiceLoader”工具类的使用从无到有,从选择之一到唯一选择,我们可以看出”Service Provider”和”ServiceLoader”机制将是扩展JDK已有服务(Charset、ScriptEngine、NIO等)的标准。

让我们一起通过脚本引擎ScriptEngine这一与”Service Provider”一起诞生的API,来学习如何通过”Service Provider”扩展JDK服务,以及玩转可扩展服务的设计。

从Java6开始,JDK内置了一套Javascript脚本引擎”NashornScriptEngine”,可以很方便地在java里面直接解析、运行js脚本,为代码提供动态执行能力,而不用依赖任何第三方库:

public static void callJavascript() { //创建脚本引擎管理器 ScriptEngineManager m = new ScriptEngineManager(); //通过脚本引擎管理器查找并创建Javascript引擎 ScriptEngine jsEngine = m.getEngineByName("Javascript"); try { //绑定预定义参数 Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE); bindings.put("a", 2); bindings.put("b", 3); //调用Javascript的pow函数 Object result = jsEngine.eval("Math.pow(a,b)", bindings); //打印返回值”8.0” System.out.println(result); } catch (ScriptException e) { e.printStackTrace(); } }

变量、函数、与java互调用等更复杂的功能,大家可以一试,就不在本文的讨论范围之内。

除了自带的Javascript引擎,通过”Service Provider”机制,很容易扩展ScriptEngine,支持其它脚本,比如引入”org.python:jython:2.7.0”后,ScriptEngine就具备了执行Python脚本的能力:

package org.ctstudio;import org.python.util.PythonInterpreter;import javax.script.ScriptEngine;import javax.script.ScriptEngineManager;import javax.script.ScriptException;import java.util.Properties;public class PyScriptEngineDemo { //打招呼函数,供Python脚本调用 public static void sayHello(String name) { System.out.format("Hello %s!", name); }public static void callPython() { //Jython引擎使用前需要提前做一些初始化工作 Properties props = new Properties(); props.setProperty("python.import.site", "false"); PythonInterpreter.initialize(System.getProperties(), props, new String[]{}); //创建脚本引擎管理器 ScriptEngineManager m = new ScriptEngineManager(); //通过脚本引擎管理器查找并创建jython引擎 ScriptEngine pyEngine = m.getEngineByName("jython"); try { //执行Python脚本,在其中调用java函数 pyEngine.eval("from org.ctstudio import PyScriptEngineDemo" + "PyScriptEngineDemo.sayHello('Jack')"); //Hello Jack! } catch (ScriptException e) { e.printStackTrace(); } } public static void main(String[] args) { callPython(); }}

ScriptEngineManager是如何使用”Service Provider”机制的呢?通过JDK源代码,我们可以看到,ScriptEngineManager正是通过”ServiceLoader.load()”方法来发现所有脚本引擎:

2c109012da05e677f4e5cb3480aee9b3.png
597baac7437c5ddef6c2aa6373ceeac7.png
2eee09a4639019db0729858ebac52f53.png

由于脚本引擎实例的创建是一件开销比较大的事情,而”ServiceLoader”在迭代过程中就会直接创建提供者的实例,所以ScriptEngineManager没有直接返回ScriptEngine,而是使用抽象工厂模式发现并保存ScriptEngineFactory实例,只在真正需要的时候才通过具体工厂创建ScriptEngine的实例:

aba35cee40313e96b7e2cd8e14f28484.png

Servlet3.0的设计则较直接,”ServletContainerInitializer”的提供者被发现并创建实例,随后所有提供者的” onStartup”被调用,以触发用户自定义的初始化过程,具体可参考Tomcat的源码:

org.apache.catalina.startup.ContextConfig:

4de0111e28ce5d2ef451d78e40cb9986.png

org.apache.catalina.core.StandardContext:

091dadb67a79575c90596828328e996f.png

Java最有名的日志框架之一logback就是利用Servlet3.0的这一机制来初始化:

13d6fd1d7811a72562163085ac964a94.png
0e2ed799629da30eb7494ab51189b2de.png

而Spring框架则利用Servlet3.0的这一机制实现了将Bean与服务提供者集成起来的WebApplicationInitializer,Spring-Boot则进一步在WebApplicationInitializer的基础之上实现了Web应用拉起工具基类SpringBootServletInitializer,使得我们可以在Servlet容器中轻松拉起我们的Spring应用:

7b2871bcb5c0a8aa53d4d5f706de5863.png

最新版本的日志外观slf4j2.0(未发布)的LoggerFactory已改由”Service Provider”标准来加载日志实现:

e52518d8098b09e73293aa5975ce7fcb.png

Logback1.3(未发布,其作者也是log4j、slf4j的作者)也改用”Service Provider”机制来实现与日志外观的集成:

e6ab783d25bac5e7a91042b995d2bb8f.png

结语

如此多举足轻重的开源软件的重视,足以体现”Service Provider”作为Java标准的控制反转机制的重要影响,并可预见其将会越来越重要。不管你是想要为开源软件作贡献,或是设计自己的可扩展组件,”Service Provider”都是你有必要掌握的知识。



推荐阅读
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
author-avatar
生命的无言
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有