小白经历了人生中最虐的三个月后成功转正了,回想起来刚入职的时候连枚举都看的吃力真是菜的扣jio,无数次怀疑自己有没有选错行,虽然现在也没有答案,也不知未来可以干多久,但是大大说我们要干一行爱一行,至少我还不厌恶写代码,那就慢慢培养感情吧~
自定义SPI
看这个SPI机制其实看的蛮久的,现在也不能说完全懂。以下是自定义的SPI代码的类图:
每个类的作用:
1、shoutService接口,是服务抽象的接口;
2、Cat、Dog、Mow三个具体的实现;
3、BaseThirdService,只有一个getCode()方法,作用是选择哪一个实现;
4、AbstractThirdService抽象类是加载自定义服务的,把code和对应的实现类放于map中,方便之后根据code拿实现类;
5、ShoutServiceManager用于指定某个自定义服务,在这里就是指定shoutService服务,因为有时我们可能会自定义多个服务;
6、IServiceLoader是具体的加载动作,加载resources里的文件内容
具体实现
1.先定义抽象服务和实现
(1)所有自定义的服务都需要先继承这个类
public interface BaseThirdService {
String getCode();
}
(2)自定义一个抽象服务
public interface ShoutService extends BaseThirdService {
void shout();
}
(3)定义该服务的3个实现
public class CatShoutServiceImpl implements ShoutService{
@Override
public void shout() {
System.out.println("miao~");
}
@Override
public String getCode() {
return "cat";
}
}
public class DogShoutServiceImpl implements ShoutService{
@Override
public void shout() {
System.out.println("wang!");
}
@Override
public String getCode() {
return "dog";
}
}
public class MowShoutServiceImpl implements ShoutService{
@Override
public void shout() {
System.out.println("mow~");
}
@Override
public String getCode() {
return "mow";
}
}
2.在resources写下资源文件
目录结构:
文件内容:
com.cainiao.demo.spi.service.CatShoutServiceImpl
com.cainiao.demo.spi.service.DogShoutServiceImpl
com.cainiao.demo.spi.service.MowShoutServiceImpl
3.具体加载过程
AbstractThirdService维护一个(code和服务具体实现的)map
@Slf4j
public abstract class AbstractThirdService<T extends BaseThirdService> {
private T service = null;
private AtomicBoolean initFlag = new AtomicBoolean(false);
private Map<String, T> serviceMap = new HashMap<>(4);
public void load() {
if (initFlag.compareAndSet(false,true)) {
// 返回直接继承的父类(包含泛型参数),并返回泛型的第一个参数类型,实际上是想获取ShoutService
Class<T> entityClass = (Class<T>) ((ParameterizedType)getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
load(entityClass);
}
}
public void load(Class<T> clazz) {
List<T> serviceList = IServiceLoader.load(clazz);
for (T service : serviceList) {
serviceMap.put(service.getCode(), service);
}
}
public T getService(String code) {
load();
return serviceMap.get(code);
}
}
IServiceLoader具体加载过程,它维护了服务与具体实现list的map
public class IServiceLoader {
private static final Map<String, Object> SPI_SERVICE_MAP = new ConcurrentHashMap<>();
private static final String PREFIX = "META-INF/services/";
public static <S> List<S> load(Class<S> service) {
Objects.requireNonNull(service, "Service interface cannot be null");
// 获取类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
ClassLoader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
Objects.requireNonNull(loader, "class loader cannot be null");
String uk = getUK(service, loader);
// map中有就直接获取,否则加载
if (SPI_SERVICE_MAP.containsKey(uk)) {
return (List<S>) SPI_SERVICE_MAP.get(uk);
}
String fullName = PREFIX + service.getName();
try {
List<S> result = getService(service, loader, fullName);
SPI_SERVICE_MAP.put(uk,result);
return result;
} catch (Exception e) {
throw new ServiceConfigurationError("service" + service.getName() + "get sub type error." + e);
}
}
private static <S> String getUK(Class<S> service, ClassLoader loader) {
return loader.getClass().getName() + "_" + service.getName();
}
private static <S> List<S> getService(Class<S> service, ClassLoader loader, String fullName) throws IOException {
List<S> result = new ArrayList<>(16);
// 加载器加载资源文件
Enumeration<URL> configs = loader.getResources(fullName);
while (configs.hasMoreElements()) {
// 获取文件的url
URL spiFileUrl = configs.nextElement();
// 获取文件中的内容(实现类名)
Set<String> classNames = loadSpiFileContent(spiFileUrl);
// 加载每个实现类形成list返回
classNames.forEach(each -> result.add(createService(service,loader,each)));
}
return result;
}
private static <S> S createService(Class<S> service, ClassLoader loader, String cn) {
Class<?> c;
try {
c = Class.forName(cn,false,loader);
} catch (ClassNotFoundException x) {
throw new ServiceConfigurationError("Provider" + cn + "not found", x);
}
// 如果service不是c的父类
if (!service.isAssignableFrom(c)) {
throw new ServiceConfigurationError("Provider" + cn + "not a subtype");
}
try {
return service.cast(c.newInstance());
}catch (Throwable x) {
throw new ServiceConfigurationError("Provider " + cn + "could not be instantiated");
}
}
private static Set<String> loadSpiFileContent(URL url) {
Set<String> result = new HashSet<>(16);
InputStream in = null;
BufferedReader r = null;
try {
in = url.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
String eachLine = r.readLine();
if (eachLine == null) {
return result;
}
while (StringUtils.isNotBlank(eachLine)) {
int ci = eachLine.indexOf("#");
if (ci >= 0) {
eachLine = eachLine.substring(0,ci);
}
eachLine = eachLine.trim();
int n = eachLine.length();
if (n != 0) {
if (eachLine.indexOf(' ') >= 0 || eachLine.indexOf('\t') >= 0) {
throw new ServiceConfigurationError("Illegal configuration-file syntax");
}
int cp = eachLine.codePointAt(0);
// 校验java定义符的第一个字母是否合法
if (!Character.isJavaIdentifierStart(cp)) {
throw new ServiceConfigurationError("Illegal provider-class name:" + eachLine);
}
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = eachLine.codePointAt(i);
// 校验java定义符的非首字母是否合法
if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
throw new ServiceConfigurationError("Illegal provider-class name:" + eachLine);
}
}
}
result.add(eachLine);
eachLine = r.readLine();
}
return result;
} catch (Exception e) {
throw new ServiceConfigurationError("Error reading configuration file", e);
} finally {
try {
if (r != null) {
r.close();
}
if (in != null) {
in.close();
}
} catch (IOException y) {
throw new ServiceConfigurationError("Error closing configuration file", y);
}
}
}
}
4.针对这一个自定义服务写一个调用类
public class ShoutServiceManager extends AbstractThirdService<ShoutService>{
private ShoutServiceManager(){}
private static class Singleton {
static final ShoutServiceManager INSTANCE = new ShoutServiceManager();
}
public static ShoutServiceManager getInstance() {
return Singleton.INSTANCE;
}
}
以上其实就已经完成了。最后可以写个单测验证一下:
@SpringBootTest(classes = TestBase.class)
@RunWith(SpringRunner.class)
public class SpiTest {
@Test
public void testSpi() {
ShoutService service = ShoutServiceManager.getInstance().getService("cat");
service.shout();
service = ShoutServiceManager.getInstance().getService("dog");
service.shout();
service = ShoutServiceManager.getInstance().getService("mow");
service.shout();
}
}
结果:
miao~
wang!
mow~
对于我来说其实顺着理解有些难度,可以先敲了一遍再debug,就理解了整个过程,这个对于我这样的小白来说真的是很新鲜,有一种,wa java还能这样写的感觉~睡觉