Explain the SpringFactories mechanism with specific examples

Problem is introduced into

No injection error reported

When I was developing my project yesterday, there were two separate modules. I needed to call Module B’s method from Module A:

@Slf4j
@SpringBootTest(classes = Application.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class AlertMsgTest {
    // Module B's class
    @Autowired
    private AlarmMsgMqConsumer alarmMsgMqConsumer;
    @Test
    public void testLoad(a) throws MQClientException {
        alarmMsgMqConsumer.init();
        return; }}Copy the code

Error message during call:

Let’s look at the AlarmMsgMqConsumer class:

@Component
public class AlarmMsgMqConsumer {}Copy the code

Module A does not add AlarmMsgMqConsumer to the Spring Bean. Module A uses @ComponentScan to load Module B classes. You can introduce spring.Factories instead of decoupling.

Automatic loading

To enable beans in Module B to be injected into Spring ahead of time, we add a new class:

@Configuration
@ComponentScan(basePackages = "com.mi.info.sales.middle.alarm")
public class AlertAutoConfig {}Copy the code

Where “com. Mi. Info. Sales. Middle. The alarm” is the path of the Module B, then how will this file can automatically start, we add a spring factories files:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mi.info.sales.middle.alarm.AlertAutoConfig
Copy the code

With the spring.factories command, you can configure AlertAutoConfig to enable automatic loading.

SpringFactories mechanism

Spring Boot has a very decoupled extension mechanism: Spring Factories. This extension mechanism is actually modeled after the SPI extension mechanism in Java.

What is the SPI mechanism

SPI’s full name is Service Provider Interface. Most developers may not be familiar with this, because this is vendor-specific or plug-in specific. This is explained in more detail in the java.util.ServiceLoader documentation.

Briefly summarize the idea behind the Java SPI mechanism. Abstract modules in our system often have many different implementation schemes, such as log module scheme, XML parsing module, JDBC module scheme and so on. In object-oriented design, we generally recommend interface programming between modules rather than hard-coding implementation classes between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability, and if an implementation needs to be replaced, the code needs to be changed. A service discovery mechanism is needed in order to realize the dynamic specification during module assembly.

The Java SPI provides a mechanism for finding a service implementation for an interface. Similar to the IOC idea of moving control of assembly out of the program, this mechanism is especially important in modular design.

SPI mechanism in Spring Boot

Spring also has a loading mechanism similar to the Java SPI. It configures the implementation class names of the interfaces in meta-INF/Spring.Factories, and then reads and instantiates those configuration files in the program. This custom SPI mechanism is the basis for the Spring Boot Starter implementation.

In everyday work, you might need to implement SDKS or Spring Boot Starter to be used by people, so you can use the Factories mechanism. The Factories mechanism allows the SDK or Starter to be used with little or no configuration, just by importing our JAR packages into the service.

spring.factories

The spring-.factories are done with Properties parsing, so the content in the write file is configured as follows:

com.xxx.interface=com.xxx.classname
Copy the code

Such as the spring.factories file in the spring-boot package:

# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader # Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener # Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ Org. Springframework. Boot. Web. Context. ServerPortInfoApplicationContextInitializer # omit...Copy the code

SpringFactoriesLoader

The main method in this class is loadFactories: Gets an instance of its implementation class based on the interface class. This method returns a list of objects.

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryClass, "'factoryClass' must not be null");
   
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    Call loadFactoryNames to get the implementation class of the interface
    List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
    }
    // Iterate through the factoryNames array to create an object that implements the class
    List<T> result = new ArrayList<>(factoryNames.size());
    for (String factoryName : factoryNames) {
        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    }
    / / sorting
    AnnotationAwareOrderComparator.sort(result);
    return result;
}
Copy the code

LoadFactoryNames: Gets the name of its interface class by interface. This method returns a list of class names.

public static List<String> loadFactoryNames(Class
        factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try{ Enumeration<URL> urls = (classLoader ! =null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); }}Copy the code

InstantiateFactory: Create instance objects from a class.

private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
    try{ Class<? > instanceClass = ClassUtils.forName(instanceClassName, classLoader);// Whether the specified interface is implemented
        if(! factoryClass.isAssignableFrom(instanceClass)) {throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
        }
        // Create an object
        return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
    } catch (Throwable ex) {
        throw new IllegalArgumentException("Unable to instantiate factory class: "+ factoryClass.getName(), ex); }}Copy the code

Welcome everyone to like a lot, more articles, please pay attention to the wechat public number “Lou Zai advanced road”, point attention, do not get lost ~~