SPI概念及使用方法

簡介

SPI全稱Service Provider Interfaces,用於發現接口的實現。在jdbc、日誌、dubbo的設計中都使用SPI用於服務的發現。簡單的以jdbc爲例:

jdbc Driver實現了java.sql.Driver接口,實現具體的功能,也就是Java SQL framework定義了用於數據庫連接接口規範,不同的數據庫廠商要想使用Java連接數據庫必須實現該接口纔可以,當廠商實現後,如何使用呢?也就是上圖中的在 META-INF/services/下,配置以接口 java.sql.Driver爲名稱的文件,文件里加上具體的實現 com.mysql.jdbc.Driver即可,在程序中註冊註冊驅動即可使用,如在使用jdbc時:

// Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");

加載該jdbc驅動時,會執行靜態塊,並使用SPI機制在 java.sql.DriverManager中加載 java.sql.Driver的實現類:

// Register ourselves with the DriverManager
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);
    ...
}

項目中使用

在項目中我們會對外提供接口,爲了在controller內內減少接口數量,使用SPI機制去實現相應的功能。

首先定義接口
/**
 * @author wenchao.wang
 * @description
 * @date 2018/1/20 下午10:53
 */
public interface BaseApplication {
}
接口實現
@Component
public class OpenOrderApplication implements BaseApplication {

    @MethodMapping("com.lios.test1")
    public ApiResponse filter(OpenApiJsonObject openApiJsonObject, String appId) {
        return null;
    }
    @MethodMapping("com.lios.test2")
    @CheckField(value = CheckEnum.JSON_OBJECT)
    public ApiResponse<DataVO> orderPush(OpenApiJsonObject openApiJsonObject, String appId) {
        return null;
    }

    @MethodMapping("com.lios.test3")
    public ApiResponse<DataVO> orderfeedback(OpenApiJsonObject openApiJsonObject, String appId) {
      return null;
    }

}
使用SPI機制獲取接口實現,並把註解值與方法綁定註冊
public class MappingFactory {
private static ConcurrentHashMap<String, MethodApplication> methodMappings = new ConcurrentHashMap<String, MethodApplication>();
private volatile static boolean init = false;
private MappingFactory() {
}
private static void initHandlerMethod() {
    ServiceLoader<BaseApplication> applications = ServiceLoader.load(BaseApplication.class);
    for (BaseApplication application : applications) {
        Method[] methods = application.getClass().getDeclaredMethods();
        for (Method method : methods) {
            MethodMapping methodMapping = method.getAnnotation(MethodMapping.class);
            if (methodMapping != null && methodMapping.value() != null) {

                String mapping = methodMapping.value();
                addMethodMapping(mapping, new MethodApplication(StringUtils.uncapitalize(application.getClass().getSimpleName()), method));
            }
        }
    }
}
private static void addMethodMapping(String mapping, MethodApplication methodApplication) {
    methodMappings.put(mapping, methodApplication);
}
public static MethodApplication getMethodMapping(String url) {
    if (!init) {
        initHandlerMethod();
        init = true;
    }
    return methodMappings.get(url);
 }
}
文件配置

在META-INF/services中添加文件com.lios.base.application.BaseApplication,寫入:

com.lios.api.application.OpenApplication
controller中調用
@RestController
public class OpenController {
    private static final Logger LOGGER = LoggerFactory.getLogger(OpenController.class);
    @Autowired
    private Map<String, BaseApplication> baseApplications;
    @RequestMapping(value = "/open/gateway/{method:.+}", method = RequestMethod.POST)
    @ResponseBody
    public OpenApiResponse dispatcher(@PathVariable String method, @RequestBody OpenApiJsonObject openApiJsonObject) {
        ...
        MethodApplication methodApplication = MappingFactory.getMethodMapping(method);
        if (methodApplication != null && baseApplications.get(methodApplication.getApplicationName()) != null) {
            try {
                return (OpenApiResponse) methodApplication.getMethod().invoke(baseApplications.get(methodApplication.getApplicationName()), openApiJsonObject, appId);
            } catch (Exception e) {
               LOGGER.error(e.getMessage(), e);
            }
        }
        ...
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章