Detailed explanation of Java SPI mechanism

What is SPI?

The full name of SPI is service provider interface, which is a service provider discovery mechanism built into JDK. SPI is a dynamic replacement discovery mechanism. For example, there is an interface. If you want to dynamically add an implementation to it at runtime, you only need to add one implementation. What we often encounter is Java sql. Driver interface, other different manufacturers can make different implementations for the same interface. mysql and postgresql have different implementations for users, while Java SPI mechanism can find service implementations for an interface.

 

 

 

In the class diagram, the interface corresponds to the defined Abstract SPI interface; The implementer implements SPI interface; The caller depends on the SPI interface.

SPI interface is defined on the caller, which is more dependent on the caller in concept; Located in the package of the caller on the organization; The implementation is in a separate package.

When the interface belongs to the implementer, the implementer provides the interface and implementation. This usage is very common and belongs to API call. We can refer to the interface to call the function of an implementation class.

Java SPI application instance

After the service provider provides an interface implementation, it is necessary to create a file named after the service interface in the META-INF/services / directory under the classpath. The content of this file is the specific implementation class of the interface. When other programs need this service, you can find the configuration file in META-INF/services / of the jar package (generally relying on the jar package). The configuration file has the specific implementation class name of the interface. You can load and instantiate according to this class name to use the service. The tool class for finding service implementation in JDK is: Java util. ServiceLoader.

SPI interface

public interface ObjectSerializer {

    byte[] serialize(Object obj) throws ObjectSerializerException;

    <T> T deSerialize(byte[] param, Class<T> clazz) throws ObjectSerializerException;

    String getSchemeName();
}
Copy code

Defines an object serialization interface with three methods: serialization method, deserialization method and serialization name.

Specific implementation of SPI

public class KryoSerializer implements ObjectSerializer {

    @Override
    public byte[] serialize(Object obj) throws ObjectSerializerException {
        byte[] bytes;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            //Get kryo object
            Kryo kryo = new Kryo();
            Output output = new Output(outputStream);
            kryo.writeObject(output, obj);
            bytes = output.toBytes();
            output.flush();
        } catch (Exception ex) {
            throw new ObjectSerializerException("kryo serialize error" + ex.getMessage());
        } finally {
            try {
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {

            }
        }
        return bytes;
    }

    @Override
    public <T> T deSerialize(byte[] param, Class<T> clazz) throws ObjectSerializerException {
        T object;
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(param)) {
            Kryo kryo = new Kryo();
            Input input = new Input(inputStream);
            object = kryo.readObject(input, clazz);
            input.close();
        } catch (Exception e) {
            throw new ObjectSerializerException("kryo deSerialize error" + e.getMessage());
        }
        return object;
    }

    @Override
    public String getSchemeName() {
        return "kryoSerializer";
    }

}
Copy code

Use kryo's serialization method. Kryo is a fast and efficient Java object graphics serialization framework. It supports Java natively and is even better than google's famous serialization framework protobuf in Java serialization.

public class JavaSerializer implements ObjectSerializer {
    @Override
    public byte[] serialize(Object obj) throws ObjectSerializerException {
        ByteArrayOutputStream arrayOutputStream;
        try {
            arrayOutputStream = new ByteArrayOutputStream();
            ObjectOutput objectOutput = new ObjectOutputStream(arrayOutputStream);
            objectOutput.writeObject(obj);
            objectOutput.flush();
            objectOutput.close();
        } catch (IOException e) {
            throw new ObjectSerializerException("JAVA serialize error " + e.getMessage());
        }
        return arrayOutputStream.toByteArray();
    }

    @Override
    public <T> T deSerialize(byte[] param, Class<T> clazz) throws ObjectSerializerException {
        ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(param);
        try {
            ObjectInput input = new ObjectInputStream(arrayInputStream);
            return (T) input.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new ObjectSerializerException("JAVA deSerialize error " + e.getMessage());
        }
    }

    @Override
    public String getSchemeName() {
        return "javaSerializer";
    }

}
Copy code

Java Native serialization.

Add META-INF directory file

Create a file named after the service interface in the META-INF/services directory under Resource

 

 

com.blueskykong.javaspi.serializer.KryoSerializer
com.blueskykong.javaspi.serializer.JavaSerializer
 Copy code

Service class

@Service
public class SerializerService {


    public ObjectSerializer getObjectSerializer() {
        ServiceLoader<ObjectSerializer> serializers = ServiceLoader.load(ObjectSerializer.class);

        final Optional<ObjectSerializer> serializer = StreamSupport.stream(serializers.spliterator(), false)
                .findFirst();

        return serializer.orElse(new JavaSerializer());
    }
}
Copy code

Get the defined serialization method, and only take the first one (we wrote two in the configuration). If it is not found, it returns the Java Native serialization method.

Test class

    @Autowired
    private SerializerService serializerService;

    @Test
    public void serializerTest() throws ObjectSerializerException {
        ObjectSerializer objectSerializer = serializerService.getObjectSerializer();
        System.out.println(objectSerializer.getSchemeName());
        byte[] arrays = objectSerializer.serialize(Arrays.asList("1", "2", "3"));
        ArrayList list = objectSerializer.deSerialize(arrays, ArrayList.class);
        Assert.assertArrayEquals(Arrays.asList("1", "2", "3").toArray(), list.toArray());
    }
Copy code

The test case passes and outputs kryoSerializer.

Purpose of SPI

SPI mechanism is used in database DriverManager, Spring and ConfigurableBeanFactory. Here, take database DriverManager as an example to see the inside story of its implementation.

DriverManager is a tool class for managing and registering different database drivers in jdbc. For a database, there may be different database driven implementations. When using a specific driver implementation, we don't want to modify the existing code, but want to achieve the effect through a simple configuration. When using mysql driver, you will have a question: how does DriverManager get a certain driver class? We're using class After forname ("com. mysql. jdbc. Driver") loads the mysql driver, it will execute the static code and register the driver in the DriverManager for subsequent use.

In JDBC 4 Before 0, class is usually used when connecting to the database The sentence forname ("com. Mysql. JDBC. Driver") first loads the driver related to the database, and then obtains the connection. And JDBC 4 Class is not required after 0 Forname to load the driver and directly obtain the connection. Here, the SPI extension mechanism of Java is used.

The interface java is defined in Java sql. There is no specific implementation of driver. The specific implementation is provided by different manufacturers.

mysql

In mysql-connector-java-5.1.45 Jar, there will be a java sql. Driver files:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
 Copy code

pg

In postgresql-42.2.2 Jar, under the META-INF/services directory, there will be a file named Java sql. Driver files:

org.postgresql.Driver
 Copy code

usage

String url = "jdbc:mysql://localhost:3306/test";
Connection conn = DriverManager.getConnection(url,username,password);
Copy code

The above shows the usage of mysql, and the usage of pg is similar. Class is not required Forname ("com. mysql. JDBC. Driver") to load the driver.

Mysql DriverManager implementation

There is no loading driver code in the above code. How can we determine which database connection driver to use? This involves using Java's SPI extension mechanism to find relevant drivers. In fact, the driver search is in DriverManager. DriverManager is an implementation in Java to obtain database connection. There is a static code block in DriverManager as follows:

static {
	loadInitialDrivers();
	println("JDBC DriverManager initialized");
}
Copy code

You can see that there is a loadInitialDrivers method in its internal static code block. The usage of loadInitialDrivers uses the spi tool class ServiceLoader mentioned above:

    public Void run() {

        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();

        /* Load these drivers, so that they can be instantiated.
         * It may be the case that the driver class may not be there
         * i.e. there may be a packaged driver with the service class
         * as implementation of java.sql.Driver but the actual class
         * may be missing. In that case a java.util.ServiceConfigurationError
         * will be thrown at runtime by the VM trying to locate
         * and load the service.
         *
         * Adding a try catch block to catch those runtime errors
         * if driver not available in classpath but it's
         * packaged as service and that service is there in classpath.
         */
        try{
            while(driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch(Throwable t) {
        // Do nothing
        }
        return null;
    }
Copy code

Traverse the specific implementation obtained by SPI and instantiate each implementation class. When traversing, first call driveriterator The hasnext () method will search java. Net under the classpath and all META-INF/services directories in the jar package sql. Driver file and find the name of the implementation class in the file. At this time, no specific implementation class is instantiated.

summary

SPI mechanism is also used in many scenarios in actual development. In particular, for the implementation of unified standards by different manufacturers, after the relevant organizations or companies define the standards, specific manufacturers or framework developers implement them, and then provide them to developers for use.



Link: https://juejin.cn/post/6844903605695152142

Tags: Java Database

Posted by ClarkF1 on Fri, 15 Apr 2022 02:28:55 +0930