In the spring-Boot project’s most elegant HTTP client tool, it’s enough, it smells good! In this article, we learned how retrofit-spring-boot-starter can be used. This article continues with the implementation of Retrofit-Spring-boot-starter, showing you how to implement your own lightweight HTTP calling tool based on Retrofit in a Spring-boot project from scratch.
Retrofit-spring-boot-starter
Determine the implementation idea
Let’s start by looking directly at how to initiate an HTTP request using the Retrofit raw API.
-
Defines the interface
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); } Copy the code
-
Create an interface proxy object
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); // Building Retrofit in a real business scenario is much more complex than that, and this is the easiest way to deal with it GitHubService service = retrofit.create(GitHubService.class); Copy the code
-
The initiating
Call<List<Repo>> repos = service.listRepos("octocat"); Copy the code
As you can see, Retrofit itself already does a good job of supporting HTP requests through interfaces. However, if every business code of our project has to write the above boilerplate code, it will be very tedious. Is there a way for users to just focus on the interface definition and let the framework handle everything else automatically? At this time, we may think of using Mybatis under the Spring-boot project, the user only needs to define Mapper interface and write SQL, not care about the details of JDBC interaction. Similarly, we will eventually implement an implementation where the user only needs to define the HttpService interface, regardless of other low-level implementation details.
Introduction to relevant knowledge
In order to facilitate the following introduction, we first have to understand a few relevant knowledge points.
The Spring container is initialized
We’ll start with a brief look at Spring container initialization. In brief, spring container initialization consists of the following two steps:
- Register Bean definitions: Scanning and parsingThe configuration fileorSome annotationsGet the Bean properties (including
beanName
,beanClassName
,scope
,isSingleton
Etc.) and then based on thisbean
Attributes to createBeanDefinition
Object, and finally registers it toBeanDefinitionRegistry
In the. - Creating a Bean instance: according to the
BeanDefinitionRegistry
The inside of theBeanDefinition
Information to create the Bean instance and save the instance object tospring
In the container, the creation methods include reflection creation, factory method creation, and factory beans (FactoryBean
) create and so on.
Of course, the actual Initialization of the Spring container is much more complicated than this, and since this is not the focus of this article, it will suffice for now.
Retrofit
Object description
We already know how to useRetrofit
Object to create interface proxy objects, see nextRetrofit
UML class diagram (listing only the dependencies we are concerned with) :By analyzing UML class diagrams, we can discover, buildRetrofit
Object, you can inject the following four properties:
HttpUrl
:http
The request ofbaseUrl
.CallAdapter
Will:Call<T>
Fit to the interface method return value type.Converter
Will:@Body
The method parameters of the tag are serialized into the request body data; Deserialize the response body data into the response object.OkHttpClient
: Bottom sendinghttp
The requested client object.
When you build the OkHttpClient object, you inject the Interceptor and ConnectionPool properties.
So to build a Retrofit object, we first create HttpUrl, CallAdapter, Converter, and OkHttpClient; To build the OkHttpClient object, you need to create an Interceptor and a ConnectionPool.
Implementation,
Register Bean definitions
To fully commit the HttpService interface proxy object to the Spring container, the HttpService interface must first be scanned and registered with BeanDefinitionRegistry. Spring provides ImportBeanDefinitionRegistrar interface, the custom register BeanDefinition support functions. So let’s define the RetrofitClientRegistrar class to implement the above functionality. The concrete implementation is as follows:
-
RetrofitClientRegistrar
RetrofitClientRegistrar extracted from @ RetrofitScan annotations to scan after package path, the basis of the specific scan registration logic to ClassPathRetrofitClientScanner processing.
public class RetrofitClientRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware.BeanClassLoaderAware { // Omit other code @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName())); // Scan the @RetrofitClient annotated interface in the specified path and register with BeanDefinitionRegistry / / registered real scan logic to ClassPathRetrofitClientScanner execution ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader); if(resourceLoader ! =null) { scanner.setResourceLoader(resourceLoader); } // Specify the base package to scan String[] basePackages = getPackagesToScan(attributes); scanner.registerFilters(); // Scan and register to BeanDefinitionscanner.doScan(basePackages); }}Copy the code
-
ClassPathRetrofitClientScanner
ClassPathRetrofitClientScanner inherited ClassPathBeanDefinitionScanner, this is Spring provides classpath BeanDefinition scanner. One thing to note: All BeanDefinition beanClass attribute set to RetrofitFactoryBean. Class, and transfer the type of the interface itself to RetrofitFactoryBean retrofitInterface attributes. This shows that the final creation of the Bean instance is done through RetrofitFactoryBean.
public class ClassPathRetrofitClientScanner extends ClassPathBeanDefinitionScanner { // Omit other code private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating RetrofitClientBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' Interface"); } definition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(definition.getBeanClassName())) ;// beanClass is all set to RetrofitFactoryBeandefinition.setBeanClass(RetrofitFactoryBean.class); }}}Copy the code
This completes our ability to scan the @RetrofitClient annotated interface under the specified path and register it with BeanDefinitionRegistry.
The @RetrofitClient annotation must be identified on the HttpService interface! @retrofitScan specifies the packet path to scan. Specific can look at the source code.
Creating a Bean instance
As stated above, creating Bean instances is actually done through RetrofitFactoryBean. Implement the FactoryBean
interface, and then override the getObject() method to complete the logic of creating an instance of the interface Bean. Also, we already know that interface proxy objects can be generated with Retrofit objects. So the core of the getObject() method is to build a Retrofit object and generate an HTTP interface proxy object based on it.
-
Configuration items and @RetroFITClient To make building Retrofit objects more flexible, we can pass dynamic parameter information through configuration items and the @RetrofitClient attribute. @RetrofitClient contains the following attributes:
baseUrl
: used to createRetrofit
theHttpUrl
Is applicable to all requests on the interfaceBased on the url
.poolName
: Specifies the name of the connection pool to be used for requests under this interfaceConnectionPool
Object value.connectTimeoutMs/readTimeoutMs/writeTimeoutMs
: for buildOkHttpClien
T Specifies the timeout period for the object.logLevel/logStrategy
: Sets the log printing level and log printing policy for requests on this interface, which can be used to create a log printing interceptorInterceptor
.
-
RetrofitFactoryBean RetrofitFactoryBean implementation logic is very complex, which can be summarized as follows:
- By configuring item data as well
@RetrofitClient
The annotation data is completeRetrofit
Object construction. - each
HttpService
The interface will build oneRetrofit
Object, every one of themRetrofit
Object will build the correspondingOkHttpClient
Object. - Extensible annotated interceptor is adopted
InterceptMark
The annotation tag is implemented and the path intercept match is passedBasePathMatchInterceptor
The implementation.
public class RetrofitFactoryBean<T> implements FactoryBean<T>, EnvironmentAware.ApplicationContextAware { // Omit other code public RetrofitFactoryBean(Class<T> retrofitInterface) { this.retrofitInterface = retrofitInterface; } @Override @SuppressWarnings("unchecked") public T getObject(a) throws Exception { // Interface verification checkRetrofitInterface(retrofitInterface); // Build Retrofit objects Retrofit retrofit = getRetrofit(retrofitInterface); // Create interface proxy objects based on Retrofit return retrofit.create(retrofitInterface); } /** * Get the OkHttpClient instance, one interface corresponds to one OkHttpClient **@paramRetrofitClientInterfaceClass retrofitClient interface class *@return* / OkHttpClient instance private synchronized OkHttpClient getOkHttpClient(Class retrofitClientInterfaceClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // Build OkHttpClient based on various conditions } /** * Get a Retrofit instance. One retrofitClient interface corresponds to one Retrofit instance **@paramRetrofitClientInterfaceClass retrofitClient interface class *@return* / Retrofit instance private synchronized Retrofit getRetrofit(Class retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { / / build retrofit } Copy the code
- By configuring item data as well
This completes our ability to create an instance of HttpServiceBean. When used, inject HttpService directly and then call its methods to send the corresponding HTTP request.
conclusion
In summary, the core of implementing your own lightweight HTTP calling tool based on Retrofit in the Spring-Boot project is just two things: The first is to register the BeanDefinition of the HttpService interface, and the second is to build Retrofit to create a proxy object for HttpService. For more details, it is recommended to look directly at the retrofit-spring-boot-Starter source code.
Original is not easy, feel that the article is written well small partners, a praise 👍 to encourage it ~
Welcome to my open source project: a lightweight HTTP invocation framework for SpringBoot