ObjectProvider source code trace of Spring Boot annotation

Keywords: Java Spring Tomcat

Recently, I'm still learning to read the source code of Spring Boot. In this process, I've involved many features that are rare in daily projects. It's interesting to study this in depth, which is also one of the charms of reading the source code. Write an article here and share it with you.

ObjectProvider in auto configuration

When reading the configuration of Tomcat in Spring Boot auto configuration source code, you can see the following auto configuration source code.

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class,Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(
            ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
            ObjectProvider<TomcatContextCustomizer> contextCustomizers,
            ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
        // ...
    }
}

This is a regular Java based configuration class, so do you find that its usage is different from others? Yes, that's the three ObjectProvider parameters. This is also the content of this article.

Spring injection

Before we introduce the use of ObjectProvider, let's review the knowledge of injection.

In the process of using Spring, we can inject one class into another through various forms, such as @ Autowired and @ Resources annotation.

And @ Autowired can annotate in different places to achieve the injection effect, such as on the constructor:

@Service
public class FooService {
    private final FooRepository repository;
    @Autowired
    public FooService(FooRepository repository) {
        this.repository = repository
    }
}

Comments on attributes:

@Service
public class FooService {
    @Autowired
    private final FooRepository repository;
}

The annotation is on the setter method:

@Service
public class FooService {
    private final FooRepository repository;
    @Autowired
    public void setFooRepository(FooRepository repository) {
        this.repository = repository
    }
}

What's new in spring 4.3

The above is the most common injection method. If you forget to write the @ Autowired annotation, an exception will be thrown at startup.

However, after spring 4.3, a new feature was introduced: when the parameters of the construction method are single construction parameters, @ Autowired can not be used for annotation.

Therefore, the above code can be changed into the following form:

@Service
public class FooService {
    private final FooRepository repository;
    public FooService(FooRepository repository) {
        this.repository = repository
    }
}

It will be more elegant to use this form. This feature is widely used in Spring Boot's auto configuration class.

Improvement of dependency

Also in Spring 4.3, not only the attributes of single construction parameters are implicitly injected. The ObjectProvider interface is also introduced.

ObjectProvider interface is an extension of ObjectFactory interface. It is specially designed for injection point, which can make injection more loose and optional.

So when do you use the ObjectProvider interface?

If the Bean of the parameter to be injected is empty or has more than one, it is time for ObjectProvider to work.

If the injection instance is empty, the use of ObjectProvider avoids the exception of the dependent object caused by strong dependency. If there are multiple instances, the ObjectProvider's method will obtain a Bean according to the Order specified by the Ordered interface of the Bean implementation or the @ Order annotation. Thus, it provides a more relaxed way of dependency injection.

Spring 5.1 provides a Stream based orderedStream method to obtain ordered streams.

After using ObjectProvider, the above code changes to the following way:

@Service
public class FooService {
    private final FooRepository repository;
    public FooService(ObjectProvider<FooRepository> repositoryProvider) {
        this.repository = repositoryProvider.getIfUnique();
    }
}

This benefit is obvious when there is no FooRepository or more than one in the container. However, the disadvantage is also obvious. If the FooRepository cannot be null, the exception may be transferred from the startup phase to the business operation phase.

ObjectProvider source code

The source code and resolution of ObjectProvider are as follows:

public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {

    // Returns the bean of the specified type. If it does not exist in the container, a NoSuchBeanDefinitionException is thrown
    // If there are multiple beans of this type in the container, a NoUniqueBeanDefinitionException is thrown
    T getObject(Object... args) throws BeansException;

    // If the bean of the specified type is registered in the container, the bean instance is returned, otherwise null is returned
    @Nullable
    T getIfAvailable() throws BeansException;

    // If the return object does not exist, a callback is made, and the callback object is passed in by the Supplier
    default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
        T dependency = getIfAvailable();
        return (dependency != null ? dependency : defaultSupplier.get());
    }

     // An instance of the Consumer object, which may be shared or independent, if there is a Consumer callback consuming the target object.
    default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
        T dependency = getIfAvailable();
        if (dependency != null) {
            dependencyConsumer.accept(dependency);
        }
    }

    // Returns null if not available or unique (no primary specified). Otherwise, return the object.
    @Nullable
    T getIfUnique() throws BeansException;

    // Call the Supplier's callback function if a unique object exists
    default T getIfUnique(Supplier<T> defaultSupplier) throws BeansException {
        T dependency = getIfUnique();
        return (dependency != null ? dependency : defaultSupplier.get());
    }

    // If there is a unique object, the object is consumed
    default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
        T dependency = getIfUnique();
        if (dependency != null) {
            dependencyConsumer.accept(dependency);
        }
    }

    // Returns the Iterator of qualified objects without special order guarantee (generally registration order)
    @Override
    default Iterator<T> iterator() {
        return stream().iterator();
    }

    // Returns a continuous Stream of qualified objects without special order guarantee (generally registration order)
    default Stream<T> stream() {
        throw new UnsupportedOperationException("Multi element access not supported");
    }

    // Returns a continuous Stream of eligible objects. Adopt @ Order annotation or implement Order interface in annotation Spring application context
    default Stream<T> orderedStream() {
        throw new UnsupportedOperationException("Ordered element access not supported");
    }
}

The interface is also used in BeanFactory to define the return value of the method:

public interface BeanFactory {

    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    ...
}

At this point, the use of ObjectProvider and source code parsing are completed.

Original link:< Tracing the source code of OBJECTPROVIDER of SPRING BOOT annotation>


New vision of < center > program: you can't miss the highlights and growth < / center >

Posted by Jeremias on Sat, 07 Dec 2019 05:20:37 -0800