簡介
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); } } ... } }