強!Spring Boot 通過服務定位干掉if-else
環境:SpringBoot3.4.2
1. 簡介
相信在項目中都遇到過這樣的需求,根據不同的傳入類型調用同一個接口的不同實現類或服務處理邏輯。
例如,需要不同的解析器來處理不同的文件類型。例如,XML 文件由 XML 解析器處理,而 JSON 文件則由 JSON 解析器處理。
圖片
對于這樣的場景,我們通常會在調用客戶端中使用 if-else 語句。例如,如下代碼示例:
public void processFile(String contentType, String filePath) {
if ("json".equalsIgnoreCase(contentType)) {
// ..
} else if ("xml".equalsIgnoreCase(contentType)) {
// ...
} else if ("csv".equalsIgnoreCase(contentType)) {
// ...
} else {
// ...
}
}
本篇文章將介紹另外一種使用 服務定位器模式(Service Locator Pattern)的方法。其核心思想是面向接口編程,幫助我們消除緊密耦合的實現,并減輕客戶端對具體實現類的依賴。
2.實戰案例
2.1 定義枚舉
在該枚舉類中,我們定義了將要處理的文件類型。
public enum ContentType {
JSON(TypeConstants.JSON_PARSER),
XML(TypeConstants.XML_PARSER),
CSV(TypeConstants.CSV_PARSER);
private final String parserName;
ContentType(String parserName) {
this.parserName = parserName;
}
@Override
public String toString() {
return this.parserName;
}
public interface TypeConstants {
String CSV_PARSER = "csvParser";
String JSON_PARSER = "jsonParser";
String XML_PARSER = "xmlParser";
}
}
2.2 定義解析器接口
針對不同的文件類型,我們只需要定義對應的接口實現即可。
public interface Parser {
Map<String, Object> parse(Reader r);
}
針對上面定義的3種文件類型,分別實現對應的Parser。
@Component(TypeConstants.CSV_PARSER)
public class CSVParser implements Parser {
@Override
public Map<String, Object> parse(Reader r) {
return Map.of("csv", "csv文件解析成功") ;
}
}
@Component(TypeConstants.JSON_PARSER)
public class JSONParser implements Parser {
@Override
public Map<String, Object> parse(Reader r) {
return Map.of("json", "json文件解析成功") ;
}
}
@Component(TypeConstants.XML_PARSER)
public class XMLParser implements Parser {
@Override
public Map<String, Object> parse(Reader r) {
return Map.of("xml", "xml文件解析成功") ;
}
}
注意,我們這里的beanName。我們接下來將直接通過beanName自動的查找對應解析器實現。
2.3 定義服務定位器接口
該接口中只有一個方法 getParser,該方法接受一個內容類型(contentType)作為參數,并返回 Parser 接口。
public interface ParserFactory {
Parser getParser(ContentType contentType);
}
我們將直接通過參數ContentType來獲取對應的Parser具體實現。
2.4 配置ServiceLocatorFactoryBean
該類是我們的重點,我們就是通過它來定義具體的Parser實現。我們配置 ServiceLocatorFactoryBean 來使用 ParserFactory 作為服務定位器接口。ParserFactory 接口不需要具體的實現類。
@Configuration
public class ParserConfig {
@Bean("parserFactory")
ServiceLocatorFactoryBean serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(ParserFactory.class);
return factoryBean;
}
}
如上配置后,ServiceLocatorFactoryBean底層會生成ParserFactory的代理類,對應的 InvocationHandler 實現會根據當前調用的方法參數(第一個參數)來獲取對應的beanName。
2.5 測試使用
接下來,在使用 Parser 時就無需關心去引入具體的實現了。通過上面的ServiceLocatorFactoryBean 可以直接根據類型獲取具有相應功能的 Parser 接口。
@Service
public class ParserService {
private final ParserFactory parserFactory;
public ParserService(ParserFactory parserFactory) {
this.parserFactory = parserFactory;
}
public Map<String, Object> getData(ContentType contentType) {
Parser parser = parserFactory.getParser(contentType) ;
InputStreamReader reader = null ;
return parser.parse(reader);
}
}
接下來,我們定義一個Runner進行測試
@Component
public class ParserRunner implements CommandLineRunner {
private final ParserService parserService ;
public ParserRunner(ParserService parserService) {
this.parserService = parserService;
}
@Override
public void run(String... args) throws Exception {
Map<String, Object> data = this.parserService.getData(ContentType.CSV) ;
System.err.println(data) ;
data = this.parserService.getData(ContentType.JSON) ;
System.err.println(data) ;
data = this.parserService.getData(ContentType.XML) ;
System.err.println(data) ;
}
}
啟動服務后,控制臺輸出結果如下:
2.6 工作原理
如下圖是ServiceLocator服務定位的工作原理:
總結:服務定位器模式消除了客戶端對具體實現的依賴。以下是 Martin Fowler 文章中的一段話,它總結了該模式的核心思想:
“服務定位器的基本思想是擁有一個對象,該對象知道如何獲取應用程序可能需要的所有服務。”