面向接口编程就是先把客户的业务逻辑先提取出来,作为接口,业务具体实现通过该接口的实现类来完成。
当客户需求变化时,只需编写该业务逻辑的新的实现类,不需要改写现有代码,减少对系统的影响。
其遵循的思想是:对扩展开放,对修改关闭。在使用面向接口的编程过程中,将具体逻辑与实现分开,减少了各个类之间的相互依赖。
面向接口编程的优点:
面向接口编程更多的是体现在服务器端,但是在我们客户端还是需要将具体的业务处理逻辑(接口或者抽象类的实现类)传递给服务器端。所以在某种程度上面向接口编程只是把服务器端的耦合度降低了,客户端依旧存在过高的耦合度。一个典型的例子就是 JDBC 。
如何不把实现类硬编码到程序中?
或者说我们要在更改具体实现时不需要重新编译源代码?
import java.sql.*;
public class jdbc {
public static void main(String[] args) {
String driverClass = "com.mysql.jdbc.Driver";
String jdbcUrl = "jdbc:mysql:///test?useUnicode=true&characterEncoding=UTF-8&useSSL=true";
String user = "root";
String password = "root";
String sql = "SELECT * FROM persons";
Connection conn = null;
// 有碍观瞻
try {
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
conn = DriverManager.getConnection(jdbcUrl, user, password);
System.out.println(conn);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
Class.forName("com.mysql.jdbc.Driver");
这句代码的存在多少有点让人以外?因为此处又和实现耦合一起了。为了解耦,JDBC使用了SPI机制,也就是服务发现机制。
详细解析流程见:
JDBC解析1
JDBC解析2
SPI 的全名为Service Provider Interface。
简单来说就是通过配置文件指定接口的实现类。
当我们开发一套框架、一套机制、一个插件或一套API时候,如果需要第三方的服务支持(接口或者抽象类的实现类),可以直接写死到代码里面,但这种方式耦合太强,不利于切换到其它服务,好的方法是写一个配置文件指定服务的实现方,幸运的是 java 的 SPI 机制已经帮我们做好了。通过阅读源码我们也会了解到 SPI 机制的弊端。
核心属性:
// 默认读取 META-INF/services/XXX (XXX-->接口或者抽象类的全限定类名)
private static final String PREFIX = "META-INF/services/";
// 接口或者抽象类所对应的 Class
private final Class service;
// 存放已经加载的接口或者抽象类(以 String 保存)以及其实现类对应的对象
private LinkedHashMap providers = new LinkedHashMap<>();
// 懒加载迭代器
private LazyIterator lookupIterator;
核心方法:
// 唯一的私有构造方法(故 ServiceLoader 是以单例运行的)
private ServiceLoader(Class svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
// 重置缓存以及懒加载器
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
// 构造 ServiceLoader
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static ServiceLoader load(Class service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
// 迭代器
public Iterator iterator() {
return new Iterator() {
// 缓存(不用每次都遍历了)
Iterator> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
懒加载器
private class LazyIterator
implements Iterator
{
Class service;
ClassLoader loader;
// 保存 "META-INF/services/" 里的文件(可以有多个)
Enumeration configs = null;
// 文件里的每一行,也就是对应的实现类
Iterator pending = null;
String nextName = null;
private LazyIterator(Class service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
// 加载文件
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析每一个文件
pending = parse(service, configs.nextElement());
} //while
// 缓存,避免下一次的解析
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// 实现类名称
String cn = nextName;
nextName = null;
Class> c = null;
try {
// false 表示不进行初始化操作,只进行链接
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 创建实现类对应的实例(需要无参构造方法)
S p = service.cast(c.newInstance());
// 缓存起来
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
ServiceLoader serviceLoader = ServiceLoader.load(Driver.class);
Iterator iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
处理流程:
可以看到 SPI 机制就可以保证我们在更换业务实现类的时候不再需要重新编译源代码。
在 Java 中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口或者抽象类获取它的所有实现类却没那么容易。而 SPI 实现了这个诉求。
java 层面本质就是读取方法区中的运行时常量池(Class常量池在加载阶段进入运行时常量池)。
import org.junit.Test;
class Father {
}
class Son extends Father {
}
public class T {
@Test
public void test() {
Son son = new Son();
System.out.println(son.getClass().getSuperclass());
}
}
可以看到 SPI 与 IOC/DI 有异曲同工之妙。
原来觉得 SPI 与 IOC/DI 是对面向接口编程的更进一步,现在仔细想想:
google的AutoService注解(自动生成META-INF/services/的接口文件)