2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
SPI on lyhenne sanoista Service Provider Interface, joka voidaan ymmärtää käyttöliittymänä, joka on tarkoitettu erityisesti palveluntarjoajille tai kehittäjille, jotka laajentavat kehystoimintoja. SPI erottaa palvelurajapinnan tietystä palvelun toteutuksesta, erottaa palvelun kutsujan ja palvelun toteuttajan ja voi parantaa ohjelman skaalautuvuutta ja ylläpidettävyyttä. Palvelutoteutuksen muokkaaminen tai korvaaminen ei edellytä soittajan muokkaamista. Monet puitteet käyttävät Javan SPI-mekanismia, kuten: Spring Framework, tietokannan latausohjain, lokirajapinta ja Dubbo-laajennuksen toteutus jne. Katso Dubbon SPI-mekanismi tämän RPC-kehyksen SPI-osan toteuttamiseksi.
Esimerkiksi kun asiakas kommunikoi palvelimen kanssa, sen on sarjoitava viesti. Sarjaamisen aikana voidaan käyttää monia serialisointialgoritmeja, mukaan lukien Hessian, Kryo ja ProtoStuff. Järjestelmän vaatimus on kutsua menetelmää kyseisessä luokassa, joka vastaa viestin serialisointialgoritmin nimeä, serialisoinnin ja deserialisoinnin suorittamiseksi. Lisäksi SPI:tä on käytettävä irrotukseen .
SPI:tä käytetään seuraavasti:
- Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
- .getExtension(codecName);
codecName on serialisointialgoritmin nimi, ja vastaava luokka on ladattava tämän nimen mukaan.
- private final Class<?> type;
-
- private ExtensionLoader(Class<?> type) {
- this.type = type;
- }
-
- // 每个SPI接口都有自身的ExtensionLoader
- public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type should not be null.");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type must be an interface.");
- }
- if (type.getAnnotation(SPI.class) == null) {
- throw new IllegalArgumentException("Extension type must be annotated by @SPI");
- }
- // firstly get from cache, if not hit, create one
- ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- if (extensionLoader == null) {
- EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));
- extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);
- }
- return extensionLoader;
- }
Jokaisella SPI-liitännällä on oma ExtensionLoader -ohjelma, joka suorittaa ensin joukon laillisia tarkistustoimintoja ja yrittää sitten hankkia liitännän ExtensionLoader, jos se ei ole mahdollista saatuaan, luo Loader-objekti.
Tämän jälkeen ilmentymä saadaan getExtensionin kautta, ja ilmentymä tallennetaan myös välimuistiin, jos se ei ole välimuistissa, luo ilmentymä uudelleen.
- private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
-
- public T getExtension(String name) {
- if (StringUtil.isBlank(name)) {
- throw new IllegalArgumentException("Extension name should not be null or empty.");
- }
- // firstly get from cache, if not hit, create one
- // 缓存holder
- Holder<Object> holder = cachedInstances.get(name);
- if (holder == null) {
- cachedInstances.putIfAbsent(name, new Holder<>());
- holder = cachedInstances.get(name);
- }
- // create a singleton if no instance exists
- // holder为空,双重检查锁创建示例
- Object instance = holder.get();
- if (instance == null) {
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
Kun olet hankkinut luokan Class-objektin, voit luoda tämän objektin heijastuksen avulla.
- // 缓存
- private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
-
- private T createExtension(String name) {
- // load all extension classes of type T from file and get specific one by name
- // SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取class
- Class<?> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw new RuntimeException("No such extension of name " + name);
- }
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- try {
- // 缓存中不存在,则创建实例
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- } catch (Exception e) {
- log.error(e.getMessage());
- }
- }
- return instance;
- }
Avain on Class-objektin hankintaprosessi, eli getExtensionCalsses-menetelmä:
- // 该SPI接口所有实现类的标识与其Class对象的缓存
- private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
-
- private static final String SERVICE_DIRECTORY = "META-INF/extensions/";
-
- private Map<String, Class<?>> getExtensionClasses() {
- // get the loaded extension class from the cache
- // 根据Interface实现类的类名获取对应类的缓存
- Map<String, Class<?>> classes = cachedClasses.get();
- // double check
- if (classes == null) {
- synchronized (cachedClasses) {
- classes = cachedClasses.get();
- if (classes == null) {
- classes = new HashMap<>();
- // load all extensions from our extensions directory
- loadDirectory(classes);
- // 将Map集合存储在Holder中进行缓存
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- private void loadDirectory(Map<String, Class<?>> extensionClasses) {
- // 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类
- String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
- try {
- Enumeration<URL> urls;
- // 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的
- ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
- // 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的
- urls = classLoader.getResources(fileName);
- if (urls != null) {
- while (urls.hasMoreElements()) {
- URL resourceUrl = urls.nextElement();
- // 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中
- loadResource(extensionClasses, classLoader, resourceUrl);
- }
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
-
- private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
- String line;
- // read every line
- // #是注释,截取注释之前的部分
- while ((line = reader.readLine()) != null) {
- // get index of comment
- final int ci = line.indexOf('#');
- if (ci >= 0) {
- // string after # is comment so we ignore it
- line = line.substring(0, ci);
- }
- line = line.trim();
- if (line.length() > 0) {
- try {
- final int ei = line.indexOf('=');
- // 标识与类名
- String name = line.substring(0, ei).trim();
- String clazzName = line.substring(ei + 1).trim();
- // our SPI use key-value pair so both of them must not be empty
- if (name.length() > 0 && clazzName.length() > 0) {
- // 加载类
- Class<?> clazz = classLoader.loadClass(clazzName);
- // 在map中保存
- extensionClasses.put(name, clazz);
- }
- } catch (ClassNotFoundException e) {
- log.error(e.getMessage());
- }
- }
-
- }
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
- kyro=github.javaguide.serialize.kyro.KryoSerializer
- protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
- hessian=github.javaguide.serialize.hessian.HessianSerializer
Kerros-kerroksinen menetelmäkutsu toteuttaa vastaavan SPI-määritystiedoston lataamisen META-INF/extensions/-polun alla Class-objektin lataamiseksi ja ilmentymän hankkimiseksi. Katso kommentit tärkeistä osista.
On huomattava, että META-INF/extensions/ -kohdan tiedostonimen on oltava yhdenmukainen koodin kanssa. Koodissa määritetty tiedostonimi on SPI-rajapintaluokan koko luokan nimi. Myös tiedoston sisältö on kirjoitettava mukaan (toteutusluokan tunniste = toteutusluokan koko luokan nimi), jotta se voi olla yhdenmukainen koodin kanssa ja ohjelma osaa jäsentää tiedoston oikein ja käyttää luokan latausohjelmaa. lataa vastaava luokka. Lopuksi välimuistiin <toteutusluokan tunniste, toteutusluokka Luokkaobjekti> mukaan.
Kolmen välimuistin (luokkatunniste, luokkaobjekti), (luokkaobjekti, objektiinstanssi) ja (luokan tunniste, objektiinstanssi) olemassaolon vuoksi voit välittää tunnisteen suoraan sisään saadaksesi vastaavan luokan ilmentymän, joka optimoi myös RPC-kehyksen.