How Java SPI Powers Plug‑In Song Parsers: Build an Extensible Framework in Minutes
This article walks through creating a generic song‑parsing framework using Java's Service Provider Interface (SPI), showing the evolution from a hard‑coded parser to a modular, plug‑in architecture that supports adding new formats like MP3, MP4, and RMVB without changing core code.
Background
After joining the architecture team, Xiaohei was assigned to develop a generic song‑information parsing framework that, given raw song data, returns the song name, author, duration, and other metadata.
First Two Versions
The initial implementation exposed static methods for each format:
public class ParseUtil {
public static Song parseMp3Song(byte[] data) {
// parse song according to mp3 data format
}
}Later a second version added a method for MP4:
public class ParseUtil {
public static Song parseMp4Song(byte[] data) {
// parse song according to mp4 data format
}
}Both versions required callers to know the exact format and invoke the corresponding method, which quickly became unmaintainable.
Inspiration from JDBC
Observing that JDBC hides driver details behind a common API, Xiaohei decided to apply the same Service Provider Interface (SPI) pattern to the song parser.
Modular Design (Version 3.0)
song-parser – defines the generic Parser interface and the ParserManager utility.
song-parser-mp3 – provides an MP3 implementation.
song-parser-mp4 – provides an MP4 implementation.
Clients only need to add the desired implementation JARs as Maven dependencies:
<dependency>
<groupId>com.chenshuyi.demo</groupId>
<artifactId>song-parser</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.xiaohei.demo</groupId>
<artifactId>song-parser-mp3</artifactId>
<version>1.0.0</version>
</dependency>Parsing is performed with a single call:
Song song = ParserManager.getSong(mockSongData("MP3"));Core SPI Components
Parser Interface
public interface Parser {
Song parse(byte[] data) throws Exception;
}ParserManager
public class ParserManager {
private static final CopyOnWriteArrayList<ParserInfo> registeredParsers = new CopyOnWriteArrayList<>();
static { loadInitialParsers(); System.out.println("SongParser initialized"); }
private static void loadInitialParsers() {
ServiceLoader<Parser> loadedParsers = ServiceLoader.load(Parser.class);
Iterator<Parser> it = loadedParsers.iterator();
try { while (it.hasNext()) { it.next(); } } catch (Throwable t) { /* ignore */ }
}
public static synchronized void registerParser(Parser parser) {
registeredParsers.add(new ParserInfo(parser));
}
public static Song getSong(byte[] data) {
for (ParserInfo info : registeredParsers) {
try {
Song song = info.parser.parse(data);
if (song != null) { return song; }
} catch (Exception e) { /* wrong parser, ignore */ }
}
throw new ParserNotFoundException("10001", "Can not find corresponding data:" + new String(data));
}
}The loadInitialParsers method uses ServiceLoader to discover all Parser implementations declared in META-INF/services/com.chenshuyi.demo.Parser files.
Adding a New Format (RMVB)
To support a new format, create a separate module:
<dependency>
<groupId>com.anonymous.demo</groupId>
<artifactId>song-parser-rmvb</artifactId>
<version>1.0.0</version>
</dependency>Implement the parser:
public class RmvbParser implements com.chenshuyi.demo.Parser {
public final byte[] FORMAT = "RMVB".getBytes();
public final int FORMAT_LENGTH = FORMAT.length;
@Override
public Song parse(byte[] data) throws Exception {
if (!isDataCompatible(data)) { throw new Exception("data format is wrong."); }
return new Song("AGA", "rmvb", "《Wonderful U》", 240L);
}
private boolean isDataCompatible(byte[] data) {
byte[] format = Arrays.copyOfRange(data, 0, FORMAT_LENGTH);
return Arrays.equals(format, FORMAT);
}
}Register the parser at class loading time:
public class Parser {
static {
try { ParserManager.registerParser(new RmvbParser()); }
catch (Exception e) { throw new RuntimeException("Can't register parser!"); }
}
}Provide a service descriptor file resources/META-INF/services/com.chenshuyi.demo.Parser containing:
com.anonymous.demo.RmvbParserAfter adding the dependency, the same ParserManager.getSong call can parse RMVB data:
Song song = ParserManager.getSong(mockSongData("RMVB"));
System.out.println("Name:" + song.getName());
System.out.println("Author:" + song.getAuthor());
System.out.println("Time:" + song.getTime());
System.out.println("Format:" + song.getFormat());Output:
Name:《Wonderful U》
Author:AGA
Time:240
Format:rmvbConclusion
The Java SPI mechanism enables a clean, plug‑in architecture where new parsers can be added simply by providing an implementation JAR and a service descriptor, without modifying the core framework. This pattern mirrors JDBC and other Java libraries, offering a scalable way to extend backend functionality.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
