JAVA中的SPI实践学习

介绍

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。其实介绍可以自行百度,下面通过简单的示例代码介绍相关功能。

Animal接口

定义一个单独的工程用于定义Animal接口并定义方法sayHello,代码如下,非常简单

package com.zte.sunquan.spi;

/**
 * Animal class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public interface Animal {
    String sayHello();
}

代码结构为:
工程目录结构

Animal接口实现

在另一个工程中完成Animal接口的实现,简单地打印相关信息即可。代码如下:

package com.zte.sunquan.demo.spi.impl;

import com.zte.sunquan.spi.Animal;

/**
 * Bird class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class Bird implements Animal {
    @Override
    public String sayHello() {
        System.out.println("bird say hello.");
        return "bird say hello.";
    }
}

在实现的模块中需要注意的是,为能SPI能够发现这个Animal的实现,需要在META-INF.services中增加配置文件,文件名以接口类的全路径名命名,内容为实现类的全路径名。具体为:
在这里插入图片描述
内容为:
com.zte.sunquan.demo.spi.impl.Bird

调用测试模块

另外新建一个测试调用的模块,用于执行Animal中的打印功能,首先需要在pom中加入两者的依赖

        <dependency>
            <groupId>com.zte.sunquan.demo</groupId>
            <artifactId>spi-demo-interface</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.zte.sunquan.demo</groupId>
            <artifactId>spi-demo-impl</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

此时如果没有SPI机制,当然也是可以调用到Bird里的animal实现的,最简单的可以通过

Animal animal=new Bird();

如果是基于Spring或者OSGI的服务引用,可以直接引用到

@Service
private Animal bird;

下面看SPI是怎么实现的,代码为:

package com.zte.sunquan.demo.main;

import java.util.ServiceLoader;

import com.zte.sunquan.demo.spi.impl.Bird;
import com.zte.sunquan.spi.Animal;

/**
 * App class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class App {
    public static void main(String[] args) {
        ServiceLoader<Animal> shouts = ServiceLoader.load(Animal.class);
        for (Animal s : shouts) {
            System.out.println(s.getClass().getName());
            s.sayHello();
        }
    }
}

通过ServiceLoader.load可以直接取得所有的animal实现。特别的在基于OSGI的bundle热加载的机制中,可以利用这点实现更为灵活的控制。

更进一步

利用SPI的特点实现一个按优点级对数据处理的处理器

具体实现逻辑依赖为三步

  • 定义接口
package com.zte.sunquan.spi;

import com.zte.sunquan.spi.demo.Cow;

/**
 * Animal class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public interface Handler {
    void process(String input);

    void process(Cow input);

    default Integer getPriority() {
        return 10;
    }
}

这边出于测试,定义了两个process接口,一个处理基本类型,一个处理对象类型,gePriority为默认Handler的优先级,因为需求中要按优先级对handler列表进行排序

  • 定义实现

这里代码两个实现的Handler,代码为:

package com.zte.sunquan.demo.spi.impl;


import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;

/**
 * AProcessHandler class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class AProcessHandler implements Handler {
    @Override
    public void process(String input) {
        System.out.println("AProcessHandler:" + input);
        input += "AProcessHandler:";
    }

    @Override
    public void process(Cow input) {
        System.out.println("AProcessHandler:" + input);
        String name = input.getName();
        input.setName(name + "AProcessHandler");
    }

    @Override
    public Integer getPriority() {
        return 15;
    }
}

package com.zte.sunquan.demo.spi.impl;


import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;

/**
 * AProcessHandler class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class BProcessHandler implements Handler {
    @Override
    public void process(String input) {
        System.out.println("BProcessHandler:" + input);
        input += "BProcessHandler:";
    }

    @Override
    public void process(Cow input) {
        System.out.println("BProcessHandler:" + input);
        String name = input.getName();
        input.setName(name + "BProcessHandler");
    }

    @Override
    public Integer getPriority() {
        return 5;
    }
}

可以发现BProcessHandler的优先级要高于AProcessHandler。
同样在META-INF.services文件中要加入SPI机制可识别的配置文件,

com.zte.sunquan.spi.Handler
com.zte.sunquan.demo.spi.imp.ProcessHandler
com.zte.sunquan.demo.spi.imp.BProcessHandler

  • 执行
    在执行前,通过==SericeLoader.load(Handler.class)==获得所有的Handler,还需要排序
    所以增加了一个基于泛型的HandlerEx用于对普通Handler包装并排序,代码如下:
package com.zte.sunquan.demo.domain;

import java.util.Objects;

import com.zte.sunquan.spi.Handler;

/**
 * HandlerExt class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class HandlerExt<T extends Handler> implements Comparable<HandlerExt<T>> {
    private T handler;
    private Integer pri;

    public HandlerExt(T handler, Integer pri) {
        this.handler = handler;
        this.pri = pri;
    }

    public T getHandler() {
        return handler;
    }

    public Integer getPri() {
        return pri;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        HandlerExt<?> that = (HandlerExt<?>) o;
        return Objects.equals(handler, that.handler) &&
                Objects.equals(pri, that.pri);
    }

    @Override
    public int hashCode() {
        return Objects.hash(pri);
    }

    @Override
    public int compareTo(HandlerExt<T> o) {
        return this.getPri() - o.getPri();
    }
}

最后调用代码如下:

package com.zte.sunquan.demo.main;

import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.zte.sunquan.demo.domain.HandlerExt;
import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;

/**
 * App class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class App2 {
    //这样方式可以取代目前的handler机制吗,省去注册的步骤(如下是可以的)
    //优先级控制
    public static void main(String[] args) {
        ServiceLoader<Handler> shouts = ServiceLoader.load(Handler.class);
        List<Handler> handlers = Lists.newArrayList(shouts.iterator());
        List<HandlerExt<Handler>> handlerList = Lists.newLinkedList();

        handlers.forEach(handler -> {
            handlerList.add(new HandlerExt<>(handler,handler.getPriority()));
        });
        Collections.sort(handlerList);


        ImmutableList<Handler> handlers1 = ImmutableList.copyOf(handlerList.stream()
                .map(HandlerExt::getHandler).collect(Collectors.toList()));

        String input = "sunquan";
        shouts.forEach(handler -> handler.process(input));
        //如果入参是对象,这种方式数据变更可以传递
        Cow cow = new Cow();
        cow.setName("sunquan");
        handlers1.forEach(handler -> handler.process(cow));
    }
}

返回值
在这里插入图片描述

  • 结论
    基于类型的修改无法在handler间传递,但对象类型的变化可以在handler间传递
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值