Forest is an open source Java HTTP client framework that binds all HTTP request information (including URLS, headers, and bodies) to your own custom Interface methods. The ability to send HTTP requests by calling local interface methods.

Through the analysis of its source code, this article lists the relevant important component classes, and finally through a flow chart, analysis of its entire coding implementation, to share with you. Most importantly, this project source code, through the introduction of a large number of design patterns (template methods, factories, agents, policies, etc.), makes the code organization quite beautiful, clean, help to improve your reading interest, so I wrote this article as a memory and share with you.

[toc]

I. Relevant introduction

1. Why Forest?

Using Forest is like using an RPC framework like Dubbo, where you just define the interface and invoke it without worrying about the details of sending HTTP requests. In addition, HTTP request information is decouple from service code, facilitating unified management of a large number of HTTP urls and headers. The caller of the request does not care about the HTTP at all, and even if the HTTP request information changes, in most cases the code that invokes the request does not need to be modified.

2. How to use Forest?

Forest does not require you to write a specific HTTP call; it simply requires you to define an interface and add the HTTP request information to the interface’s methods via Forest annotations. The request sender can automatically send the request and receive the response to the request by invoking the interface you define.

3. Working principle of Forest

Forest will generate a specific implementation class through the dynamic proxy of your defined interface, and then organize and verify HTTP request information, bind dynamic data, transform data form, SSL verification signature, call the back-end HTTP API(HttpClient AND other APIS) to execute the actual request, and wait for the response. The hard work of failure retries, converting response data to Java types, and so on is outsourced by the implementation class of the dynamic proxy. When the request sender invokes this interface, it is actually invoking the implementation class that does the dirty work.

4. Architecture of Forest

5. Official website address

[official website address] : forest.dtflyx.com/

【 project source 】 : gitee.com/dromara/for…

Second, source code analysis

Because forest source code looks a lot, but the engineering module division related beauty. The responsibilities of each class are evident in the naming of the package. Below, some important classes are listed to analyze their functions. And these classes, constitute the entire creation of request, request assembly, request response, response parsing and other a number of functions.

1, ForestConnectionManager

This class is responsible for the relevant configuration parameters obtained through ForestConfiguration, then create an HttpClient, it contains two implementation class HttpclientConnectionManager and OkHttp3ConnnectionManager.

public interface ForestConnectionManager {

    void init(ForestConfiguration configuration);
}
Copy the code

1.1, OkHttp3ConnectionManager

Implement HTTP Connection manager based on OKHttp3

public void init(ForestConfiguration configuration) {
        pool = new ConnectionPool();
    }
Copy the code

This class implements the init method and, as you can see from the source code, only the ConnectionPool object is initialized.

getClient

One of the more important methods

public OkHttpClient getClient(ForestRequest request, LifeCycleHandler lifeCycleHandler)
Copy the code

The OkHttpClient instance object is created using the OkHttpClient.Builder constructor according to ForestRequest and LifeCycleHandler. The method internally initializes the parameters of the instance, including setting the HttpProxy. And whether the HTTPS protocol is supported.

1.2, HttpclientConnectionManager

Implement HTTP Connection manager based on Apache HttpClient

This class implements the init method, which can be seen from the source code, through the parameters passed in the ForestConfiguration class, initialize the member variables, including the following variables

private static PoolingHttpClientConnectionManager tsConnectionManager;

private static PoolingNHttpClientConnectionManager asyncConnectionManager;

private static Lookup<AuthSchemeProvider> authSchemeRegistry;

Copy the code

The more important membership method is

public HttpClient getHttpClient(ForestRequest request, CookieStore cookieStore)
Copy the code

The CloseableHttpAsyncClient member method is also provided.

2, HttpExecutor

Make Http requests with LifeCycleHandler and ConnectionManager. Request types include POST, POST, PUT, DELETE, etc. The interface contains two important methods: Execute (LifeCycleHandler) and close. Support for the apache client and okhttp3, provides the corresponding abstract class AbstractHttpclientExecutor, AbstractOkHttp3Executor.

2.1, AbstractHttpclientExecutor

Apache HttpClient based implementation of Http call executor, this class inherits AbstractHttpExecutor class, the most core method execute.

This class contains three important member variables

protected final HttpclientResponseHandler httpclientResponseHandler;
protected String url;
protected final String typeName;
protected T httpRequest;
protected BodyBuilder<T> bodyBuilder;
protected CookieStore cookieStore;
Copy the code

And a member variable of the subclass AbstractHttpExecutor

protected final ForestRequest request;
protected HttpclientRequestSender requestSender;
protected ForestResponse response;
Copy the code

Core method of this class

public void execute(int retryCount, LifeCycleHandler lifeCycleHandler)
Copy the code

Source code analysis

public void execute(int retryCount, LifeCycleHandler lifeCycleHandler) {
    Date startDate = new Date();
    ForestResponseFactory forestResponseFactory = new HttpclientForestResponseFactory();
    try {
        // this actually delegates to HttpclientRequestSender#sendRequest to send the HTTP request
        Request. GetRetryCount () = request.getretryCount ();
      	// Call httprequest.abort () to discard the request
        requestSender.sendRequest(
                request,
                httpclientResponseHandler,
                httpRequest,
                lifeCycleHandler,
                cookieStore,
                startDate, 0);
    } catch (IOException e) {
        if (retryCount >= request.getRetryCount()) {
            httpRequest.abort();
            response = forestResponseFactory.createResponse(request, null, lifeCycleHandler, e, startDate);
            lifeCycleHandler.handleSyncWithException(request, response, e);
            return;
        }
        log.error(e.getMessage());
    } catch (ForestRuntimeException e) {
        httpRequest.abort();
        throwe; }}Copy the code

2.1.1, HttpclientRequestSender

This class contains two important member variable HttpclientConnectionManager and ForestRequest

protected final HttpclientConnectionManager connectionManager;

protected final ForestRequest request;

public AbstractHttpclientRequestSender(HttpclientConnectionManager connectionManager, ForestRequest request) {
    this.connectionManager = connectionManager;
    this.request = request;
}
Copy the code

For the Apache Http Client, HttpExecutor -> RequestSender -> ConnectionManager.

The HttpclientRequestSender interface contains only one method, sendRequest, Two abstract class AsyncHttpclientRequestSender and SyncHttpclientRequestSender respectively based on asynchronous or synchronous processing mode to realize the method.

public interface HttpclientRequestSender {

    void sendRequest(ForestRequest request,
                     HttpclientResponseHandler responseHandler,
                     HttpUriRequest httpRequest,
                     LifeCycleHandler lifeCycleHandler,
                     CookieStore cookieStore,
                     Date startDate,
                     int retryCount) throws IOException;

}
Copy the code

2.1.2, AsyncHttpclientRequestSender

HttpClient requests are sent based on asynchronous processing

public void sendRequest(
            final ForestRequest request, final HttpclientResponseHandler responseHandler,
            final HttpUriRequest httpRequest, LifeCycleHandler lifeCycleHandler,
            CookieStore cookieStore, Date startDate, int retryCount)  {
  			/ / 1, from connectionManager. GetHttpAsyncClient obtain client object, then calls the client. The start ()
        final CloseableHttpAsyncClient client = connectionManager.getHttpAsyncClient(request);
        client.start();
  			// call logRequest to log the request
        final ForestResponseFactory forestResponseFactory = new HttpclientForestResponseFactory();
        logRequest(retryCount, (HttpRequestBase) httpRequest);
  			Call client.execute to initiate the request and set the callback function. Completed, failed, cancelled
        final Future<HttpResponse> future = client.execute(httpRequest, new FutureCallback<HttpResponse>() {
            @Override
            public void completed(final HttpResponse httpResponse) {
                ForestResponse response = forestResponseFactory.createResponse(request, httpResponse, lifeCycleHandler, null, startDate);
                if (response.isError()) {
                    ForestNetworkException networkException =
                            new ForestNetworkException("", response.getStatusCode(), response);
                    ForestRetryException retryException = new ForestRetryException(
                            networkException,  request, request.getRetryCount(), retryCount);
                    try {
                        request.getRetryer().canRetry(retryException);
                    } catch (Throwable throwable) {
                        responseHandler.handleError(response);
                        return;
                    }
                  	//3.1. If the response is incorrect, request.getretryer ().canretry is used to determine whether a retry can be initiated, and sendRequest is recursively called
                    sendRequest(request, responseHandler, httpRequest, lifeCycleHandler, cookieStore, startDate, retryCount + 1);
                    return;
                }
              	/ / 3.2, through lifeCycleHandler handleSaveCookie processing cookies,
              	/ / then calls the responseHandler. HandleSuccess process the response results.
                ForestCookies cookies = getCookiesFromHttpCookieStore(cookieStore);
                lifeCycleHandler.handleSaveCookie(request, cookies);
                responseHandler.handleSuccess(response);
            }

            @Override
            public void failed(final Exception ex) {
                ForestResponse response = forestResponseFactory.createResponse(request, null, lifeCycleHandler, ex, startDate);
                ForestRetryException retryException = new ForestRetryException(
                        ex,  request, request.getRetryCount(), retryCount);
                try {
                    request.getRetryer().canRetry(retryException);
                } catch (Throwable throwable) {
                    responseHandler.handleError(response, ex);
                    return;
                }
              	//3.3. If the request fails to be sent, request.getretryer ().canretry is used to determine whether a retry can be initiated, and sendRequest is recursively called
                sendRequest(request, responseHandler, httpRequest, lifeCycleHandler, cookieStore, startDate, retryCount + 1);
            }

            @Override
            public void cancelled(a) {}});/ / 4, delegated to the responseHandler. HandleFuture process the response results
        responseHandler.handleFuture(future, startDate, forestResponseFactory);
    }
Copy the code

2.1.3, SyncHttpclientRequestSender

HttpClient requests are sent based on synchronous processing

public void sendRequest(
        ForestRequest request, HttpclientResponseHandler responseHandler,
        HttpUriRequest httpRequest, LifeCycleHandler lifeCycleHandler,
        CookieStore cookieStore, Date startDate, int retryCount)
        throws IOException {
    HttpResponse httpResponse = null;
    ForestResponse response = null;
    client = getHttpClient(cookieStore);
    ForestResponseFactory forestResponseFactory = new HttpclientForestResponseFactory();
    try {
        logRequest(retryCount, (HttpRequestBase) httpRequest);
        httpResponse = client.execute(httpRequest);
        response = forestResponseFactory.createResponse(request, httpResponse, lifeCycleHandler, null, startDate);
    } catch (IOException e) {
        httpRequest.abort();
        ForestRetryException retryException = new ForestRetryException(
                e,  request, request.getRetryCount(), retryCount);
        try {
            request.getRetryer().canRetry(retryException);
        } catch (Throwable throwable) {
            response = forestResponseFactory.createResponse(request, httpResponse, lifeCycleHandler, throwable, startDate);
            lifeCycleHandler.handleSyncWithException(request, response, e);
            return;
        }
        response = forestResponseFactory.createResponse(request, httpResponse, lifeCycleHandler, null, startDate);
        logResponse(response);
        sendRequest(request, responseHandler, httpRequest, lifeCycleHandler, cookieStore, startDate, retryCount + 1);
        return;
    } finally {
        connectionManager.afterConnect();
        if (response == null) {
            response = forestResponseFactory.createResponse(request, httpResponse, lifeCycleHandler, null, startDate);
        }
        logResponse(response);
    }
    if (response.isError()) {
        ForestNetworkException networkException =
                new ForestNetworkException("", response.getStatusCode(), response);
        ForestRetryException retryException = new ForestRetryException(
                networkException,  request, request.getRetryCount(), retryCount);
        try {
            request.getRetryer().canRetry(retryException);
        } catch (Throwable throwable) {
            responseHandler.handleSync(httpResponse, response);
            return;
        }
        sendRequest(request, responseHandler, httpRequest, lifeCycleHandler, cookieStore, startDate, retryCount + 1);
        return;
    }

    try {
        lifeCycleHandler.handleSaveCookie(request, getCookiesFromHttpCookieStore(cookieStore));
        responseHandler.handleSync(httpResponse, response);
    } catch (Exception ex) {
        if (ex instanceof ForestRuntimeException) {
            throw ex;
        }
        else {
            throw newForestRuntimeException(ex); }}}Copy the code

2.2, AbstractOkHttp3Executor

Implement Http call executor based on OkHttp3, this class implements the HttpExecutor interface, the most core method execute.

This class contains three important member variables

protected final ForestRequest request;

private final OkHttp3ConnectionManager connectionManager;

private final OkHttp3ResponseHandler okHttp3ResponseHandler;
Copy the code

The default constructor for this class

protected AbstractOkHttp3Executor(ForestRequest request, OkHttp3ConnectionManager connectionManager, OkHttp3ResponseHandler okHttp3ResponseHandler) {
    this.request = request;
    this.connectionManager = connectionManager;
    this.okHttp3ResponseHandler = okHttp3ResponseHandler;
}
Copy the code

Core method of this class

public void execute(final LifeCycleHandler lifeCycleHandler, int retryCount)
Copy the code

Source code analysis

public void execute(final LifeCycleHandler lifeCycleHandler, int retryCount) {
    // create an OkHttpClient instance using the getClient method
    OkHttpClient okHttpClient = getClient(request, lifeCycleHandler);
    URLBuilder urlBuilder = getURLBuilder();
    String url = urlBuilder.buildUrl(request);

    //2. Construct the Request.Builder instance object and call prepareX to initialize it
    Request.Builder builder = new Request.Builder().url(url);
    prepareMethod(builder);
    prepareHeaders(builder);
    prepareBody(builder, lifeCycleHandler);

    // create Request instance object
    final Request okRequest = builder.build();
    Call call = okHttpClient.newCall(okRequest);

    / / 4, build OkHttp3ForestResponseFactory instance objects and call logRequest method request request object related log records
    final OkHttp3ForestResponseFactory factory = new OkHttp3ForestResponseFactory();
    logRequest(retryCount, okRequest, okHttpClient);
    Date startDate = new Date();
    long startTime = startDate.getTime();

    //5. Determine whether the request type is asynchronous and execute different policies
    if (request.isAsync()) {
        final OkHttp3ResponseFuture future = new OkHttp3ResponseFuture();

        Call enQueue and set the callback function to handle onFailure and onResponse
        // If no exception occurs on onFailure, the ForestResponse log is synchronized and execute is recursively called for retry.
        // In the onResponse method, logResponse is called to log the response and determine whether the ForestResponse#isSuccess method was successful
        //, otherwise retryOrDoError performs this method for retries and ultimately delegates the result to okHttp3ResponseHandler#handleFuture
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                ForestRetryException retryException = new ForestRetryException(
                        e, request, request.getRetryCount(), retryCount);
                try {
                    request.getRetryer().canRetry(retryException);
                } catch (Throwable throwable) {
                    future.failed(e);
                    ForestResponse response = factory.createResponse(request, null, lifeCycleHandler, throwable, startDate);
                    logResponse(response);
                    lifeCycleHandler.handleError(request, response, e);
                    return;
                }
                execute(lifeCycleHandler, retryCount + 1);
            }

            @Override
            public void onResponse(Call call, Response okResponse) throws IOException {
                ForestResponse response = factory.createResponse(request, okResponse, lifeCycleHandler, null, startDate);
                logResponse(response);
                Object result = null;
                if (response.isSuccess()) {
                    if(request.getOnSuccess() ! =null) {
                        result = okHttp3ResponseHandler.handleSuccess(response);
                    }
                    else {
                        result = okHttp3ResponseHandler.handleSync(okResponse, response);
                    }
                    future.completed(result);
                } else{ retryOrDoError(response, okResponse, future, lifeCycleHandler, retryCount, startTime); }}}); okHttp3ResponseHandler.handleFuture(future, startDate, factory); }The call#execute() method is used to execute the processing. If an exception is caught, recursively retry the operation
    else {
        Response okResponse = null;
        ForestResponse response = null;
        try {
            okResponse = call.execute();
        } catch (IOException e) {
            ForestRetryException retryException = new ForestRetryException(
                    e, request, request.getRetryCount(), retryCount);
            try {
                request.getRetryer().canRetry(retryException);
            } catch (Throwable throwable) {
                response = factory.createResponse(request, null, lifeCycleHandler, e, startDate);
                logResponse(response);
                lifeCycleHandler.handleSyncWithException(request, response, e);
                return;
            }
            response = factory.createResponse(request, null, lifeCycleHandler, e, startDate);
            logResponse(response);
            execute(lifeCycleHandler, retryCount + 1);
            return;
        } finally {
            if (response == null) {
                response = factory.createResponse(request, okResponse, lifeCycleHandler, null, startDate);
            }
            logResponse(response);
        }

        if (response.isError()) {
            retryOrDoError(response, okResponse, null, lifeCycleHandler, retryCount, startTime);
            return; } okHttp3ResponseHandler.handleSync(okResponse, response); }}Copy the code

3, HttpBackend

This class includes an abstract class, AbstractHttpBackend, and ultimately two implementation classes, HttpclientBackend and OkHttp3Backend.

public interface HttpBackend {

    String getName(a);

    HttpExecutor createExecutor(ForestRequest request, LifeCycleHandler lifeCycleHandler);

    void init(ForestConfiguration configuration);

    interface HttpExecutorCreator {
        HttpExecutor createExecutor(ForestConnectionManager connectionManager, ForestRequest request, LifeCycleHandler lifeCycleHandler); }}Copy the code

3.1, AbstractHttpBackend

This class includes two implementation classes, HttpclientBackend and OkHttp3Backend, which expose createExecutor methods to create requests based on ForestRequest and LifeCycleHandler. According to the type of the request, find the corresponding HttpExecutor to execute the HTTP request and process the response result from the executorCreatorMap based on the request type.

This class contains two important member variables

private volatile boolean initialized = false;

private final Map<ForestRequestType, HttpExecutorCreator> executorCreatorMap = new HashMap<>();

private final ForestConnectionManager connectionManager;

public AbstractHttpBackend(ForestConnectionManager connectionManager) {
    this.connectionManager = connectionManager;
}
Copy the code

Core method createExecutor()

According to ForestRequest access type, and then obtain corresponding from executorCreatorMap HttpExecutorCreator, then call HttpExecutorCreator. CreateExecutor.

public HttpExecutor createExecutor(ForestRequest request, LifeCycleHandler lifeCycleHandler) {
    ForestRequestType type = request.getType();
    HttpExecutorCreator httpExecutorCreator = executorCreatorMap.get(type);
    if (httpExecutorCreator == null) {
        throw new ForestRuntimeException("Http request type \"" + type.getName() + "\" is not be supported.");
    }
    HttpExecutor executor = httpExecutorCreator.createExecutor(connectionManager, request, lifeCycleHandler);
    return executor;
}
Copy the code

3.2, HttpclientBackend

The HttpClient request backend is a series of abstract methods that implement the AbstractHttpBackend class. An instance of OkHttpXXXExecutor that cannot be created inside the method is created.

3.3, OkHttp3Backend

A series of abstract methods implemented by the Abstract class AbstractHttpBackend. An instance of OkHttpXXXExecutor that cannot be configured is created inside the method.

4, HttpBackendSelector

The main responsibility of this class, by exposing the select method externally, is to specify whether to select okHttp3 or HttpClient globally through Forest.Backend. Therefore, the main function of this class is to create an HttpBackend instance that supports OKHttp3 or HttpClient through reflection based on ForestConfiguration.

static {
    BACKEND_MAP.put(HTTPCLIENT_BACKEND_NAME, HTTPCLIENT_BACKEND_CREATOR);
    BACKEND_MAP.put(OKHTTP3_BACKEND_NAME, OKHTTP3_BACKEND_CREATOR);
}

public HttpBackend select(ForestConfiguration configuration) {
    String name = configuration.getBackendName();
    if (StringUtils.isNotEmpty(name)) {
        HttpBackendCreator backendCreator = BACKEND_MAP.get(name);
        if (backendCreator == null) {
            throw new ForestRuntimeException("Http setBackend \"" + name + "\" can not be found.");
        }
        return backendCreator.create();
    }

    HttpBackend backend = null;
    backend = findOkHttp3BackendInstance();
    if(backend ! =null) {
        return backend;
    }
    backend = findHttpclientBackendInstance();
    if(backend ! =null) {
        return backend;
    }
    throw new ForestRuntimeException("Http Backed is undefined.");
}
Copy the code

HttpBackendCreator

Create an instance through classpath reflection

static class HttpBackendCreator {

    public String className;

    public HttpBackendCreator(String className) {
        this.className = className;
    }

    public HttpBackend create(a) {
        try {
            Class klass = Class.forName(className);
            return (HttpBackend) klass.newInstance();
        } catch (ClassNotFoundException e) {
            throw new ForestRuntimeException(e);
        } catch (InstantiationException e) {
            throw new ForestRuntimeException(e);
        } catch (IllegalAccessException e) {
            throw newForestRuntimeException(e); }}}Copy the code

5, ForestConfiguration

The main function of this class is similar to that of a live text object, which loads forest configuration information and acts as a data transfer link for related components.

configuration()

This method is used for initial configuration

/** * instantiates the ForestConfiguration object and initializes the default value *@returnThe newly created ForestConfiguration instance */
    public static ForestConfiguration configuration(a) {
        ForestConfiguration configuration = new ForestConfiguration();
        configuration.setId("forestConfiguration" + configuration.hashCode());
        configuration.setJsonConverterSelector(new JSONConverterSelector());
        configuration.setXmlConverter(new ForestJaxbConverter());
        configuration.setTextConverter();
        configuration.getConverterMap().put(ForestDataType.AUTO, new DefaultAutoConverter(configuration));
        configuration.getConverterMap().put(ForestDataType.BINARY, new DefaultBinaryConverter());
        setupJSONConverter(configuration);
        configuration.setTimeout(3000);
        configuration.setConnectTimeout(2000);
        configuration.setMaxConnections(500);
        configuration.setMaxRouteConnections(500);
        configuration.setRetryer(BackOffRetryer.class);
        configuration.setRetryCount(0);
        configuration.setMaxRetryInterval(0);
// configuration.setSslProtocol(SSLUtils.TLS_1_2);
        configuration.registerFilter("json", JSONFilter.class);
        configuration.registerFilter("xml", XmlFilter.class);
        configuration.setLogHandler(new DefaultLogHandler());
        RequestBodyBuilder.registerBodyBuilder(CharSequence.class, new RequestBodyBuilder.StringRequestBodyBuilder());
        RequestBodyBuilder.registerBodyBuilder(String.class, new RequestBodyBuilder.StringRequestBodyBuilder());
        RequestBodyBuilder.registerBodyBuilder(File.class, new RequestBodyBuilder.FileRequestBodyBuilder());
        RequestBodyBuilder.registerBodyBuilder(byte[].class, new RequestBodyBuilder.ByteArrayRequestBodyBuilder());
        RequestBodyBuilder.registerBodyBuilder(InputStream.class, new RequestBodyBuilder.InputStreamBodyBuilder());
        RequestBodyBuilder.registerBodyBuilder(Object.class, new RequestBodyBuilder.ObjectRequestBodyBuilder());
        return configuration;
    }
Copy the code

createInstance()

This method is used for dynamic proxy instance creation

/** * Create a dynamic proxy instance of the request interface *@paramClazz request interface class *@param<T> Request interface class generic *@returnDynamic proxy instance */
public <T> T createInstance(Class<T> clazz) {
    ProxyFactory<T> proxyFactory = getProxyFactory(clazz);
    return proxyFactory.createInstance();
}
Copy the code

6, ProxyFactory

Interface agent factory class

The core code is as follows

/ * * *@author gongjun[[email protected]]
 * @sinceThat is * / 2016-03-25
public class ProxyFactory<T> {

    private ForestConfiguration configuration;
    private Class<T> interfaceClass;

    public ProxyFactory(ForestConfiguration configuration, Class<T> interfaceClass) {
        this.configuration = configuration;
        this.interfaceClass = interfaceClass;
    }

    public Class<T> getInterfaceClass(a) {
        return interfaceClass;
    }

    public void setInterfaceClass(Class<T> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }

    public T createInstance(a) {
        T instance = (T) configuration.getInstanceCache().get(interfaceClass);
        boolean cacheEnabled = configuration.isCacheEnabled();
        if(cacheEnabled && instance ! =null) {
            return instance;
        }
        synchronized (configuration.getInstanceCache()) {
            instance = (T) configuration.getInstanceCache().get(interfaceClass);
            if(cacheEnabled && instance ! =null) {
                return instance;
            }
            InterfaceProxyHandler<T> interfaceProxyHandler = new InterfaceProxyHandler<T>(configuration, this, interfaceClass);
            instance = (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass, ForestClientProxy.class}, interfaceProxyHandler);
            if (cacheEnabled) {
                configuration.getInstanceCache().put(interfaceClass, instance);
            }
            returninstance; }}}Copy the code

7, InterfaceProxyHandler

Interface agent factory class

The constructor

In the constructor, we can see that both member methods prepareBaseInfo() and initMethods() are called

public InterfaceProxyHandler(ForestConfiguration configuration, ProxyFactory proxyFactory, Class<T> interfaceClass) {
    this.configuration = configuration;
    this.proxyFactory = proxyFactory;
    this.interfaceClass = interfaceClass;
    this.interceptorFactory = configuration.getInterceptorFactory();

    try {
        defaultMethodConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        if(! defaultMethodConstructor.isAccessible()) { defaultMethodConstructor.setAccessible(true);
        }
        defaultMethodLookup = defaultMethodConstructor.newInstance(interfaceClass, MethodHandles.Lookup.PRIVATE);
    } catch (Throwable e) {
        throw new ForestRuntimeException(e);
    }
    prepareBaseInfo();
    initMethods();
}
Copy the code

PrepareBaseInfo method

private void prepareBaseInfo(a) {
    Annotation[] annotations = interfaceClass.getAnnotations();

    for (int i = 0; i < annotations.length; i++) {
        Annotation annotation = annotations[i];
        if (annotation instanceof BaseURL) {
            BaseURL baseURLAnn = (BaseURL) annotation;
            String value = baseURLAnn.value();
            if (value == null || value.trim().length() == 0) {
                continue;
            }
            baseURL = value.trim();
            baseMetaRequest.setUrl(baseURL);
        } else {
            BaseLifeCycle baseLifeCycle = annotation.annotationType().getAnnotation(BaseLifeCycle.class);
            MethodLifeCycle methodLifeCycle = annotation.annotationType().getAnnotation(MethodLifeCycle.class);
            if(baseLifeCycle ! =null|| methodLifeCycle ! =null) {
                if(baseLifeCycle ! =null) {
                    Class<? extends BaseAnnotationLifeCycle> interceptorClass = baseLifeCycle.value();
                    if(interceptorClass ! =null) {
                        BaseAnnotationLifeCycle baseInterceptor = interceptorFactory.getInterceptor(interceptorClass);
                        baseInterceptor.onProxyHandlerInitialized(this, annotation); } } baseAnnotations.add(annotation); }}}}Copy the code

InitMethods method

Scan the interface methods of the interface class, then create an instance of ForestMethod and add it to the forestMethodMap for its member method Invoke to be used by external calls.

private void initMethods(a) {
    Method[] methods = interfaceClass.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++) {
        Method method = methods[i];
        if(method.isDefault()){
            continue;
        }
        ForestMethod forestMethod = new ForestMethod(this, configuration, method); forestMethodMap.put(method, forestMethod); }}Copy the code

Invoke method

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (method.isDefault()) {
      return invokeDefaultMethod(proxy, method, args);
    }
    if ("toString".equals(methodName) && (args == null || args.length == 0)) {
        return "{Forest Proxy Object of " + interfaceClass.getName() + "}";
    }
    if ("equals".equals(methodName) && (args ! =null && args.length == 1)) {
        Object obj = args[0];
        if (Proxy.isProxyClass(obj.getClass())) {
            InvocationHandler h1 = Proxy.getInvocationHandler(proxy);
            InvocationHandler h2 = Proxy.getInvocationHandler(obj);
            return h1.equals(h2);
        }
        return false;
    }
    // Based on the requested interface method, then look for the ForestMethod instance object from forestMethodMap, which is actually in the constructor, by scanning the interface's interface methods and initializing them
    ForestMethod forestMethod = forestMethodMap.get(method);
    return forestMethod.invoke(args);
}
Copy the code

8 ForestMethod.

The method object that is actually executed through the proxy call

/** * call method *@paramArgs The array of arguments passed in when the corresponding method of this object is called@returnThe value returned after calling the corresponding method of this object, an instance of any type */
public Object invoke(Object[] args) {
    ForestRequest request = makeRequest(args);
    MethodLifeCycleHandler<T> lifeCycleHandler = new MethodLifeCycleHandler<>(
            this, onSuccessClassGenericType);
    request.setBackend(configuration.getBackend())
            .setLifeCycleHandler(lifeCycleHandler);
    lifeCycleHandler.handleInvokeMethod(request, this, args);
    // If the return type is ForestRequest, return the request object directly
    if (ForestRequest.class.isAssignableFrom(returnClass)) {
        Type retType = getReturnType();
        if (retType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) retType;
            Type[] genTypes = parameterizedType.getActualTypeArguments();
            if (genTypes.length > 0) {
                Type targetType = genTypes[0];
                returnClass = ReflectUtils.getClassByType(targetType);
                returnType = targetType;
            } else{ returnClass = String.class; returnType = String.class; }}return request;
    }
    return request.execute();
}
Copy the code

9 ForestRequest.

Forest request object

/** * Performs the request sending process **@paramBackend HTTP backend, {backend@linkHttpBackend} Interface instance *@paramLifeCycleHandler lifeCycleHandler, {@linkLifeCycleHandler} Interface instance *@returnUpon receiving the request response, its response content is deserialized as the result of the object */
public Object execute(HttpBackend backend, LifeCycleHandler lifeCycleHandler) {
  //1. Verify the current request against the interceptor responsibility chain
  if (interceptorChain.beforeExecute(this)) {
    //2. Create an HttpExecutor instance by calling the createExecutor method of HttpBackend
    HttpExecutor executor  = backend.createExecutor(this, lifeCycleHandler);
    if(executor ! =null) {
      try {
        //3. Call the execute method
        executor.execute(lifeCycleHandler);
      } catch (ForestRuntimeException e) {
        throw e;
      } finally{ executor.close(); }}}return getMethodReturnValue();
}

/** * Performs the request sending process **@returnUpon receiving the request response, its response content is deserialized as the result of the object */
public Object execute(a) {
  return execute(getBackend(), getLifeCycleHandler());
}
Copy the code

Third, induction and summary

3.1. A flow chart

  • 1, through theForestConfigurationCall its static method#createInstance(Class<T> clazz), and then created within the methodProxyFactory<T>Proxy factory instance and invoke member methodscreateInstance().
  • 2, inProxyFactory<T>The method ofcreateInstance()Internally, by calling dynamic proxy classesInterfaceProxyHandler<T>Create interface instances for proxy interfaces in dynamic proxy mode.
  • 3, in the dynamic proxy classInterfaceProxyHandler<T>Get interface class annotations and window information, and get all interface methods and createForestMethodInstance object and add toforestMethodMapIn the.
  • 4, in the dynamic proxy classInterfaceProxyHandler<T>theinvokeIn the method, according to the passedmethodfromforestMethodMapTo obtainForestMethodInstance, and callinvokeMethods. Method internal business logic, according toforestMethodInstance creationForestRequestObject, and invoke the corresponding execute method.
  • 5, inForestRequestClass member methodsexecute, according to type (divided intookhttp3orhttpclient) Dynamic creationHttpExecutorInstance.
  • 6, forhttpclientIn theAbstractHttpclientExecutortheexecuteMethod, by delegating toHttpclientRequestSenderSend the request, and according to the request type, distinguish between asynchronous and synchronous, and according toHttpclientConnectionManagerGet different client instances and finally callexecuteMethods.
  • 7, forokttp3In theAbstractOkHttp3ExecutorExecute methodOkhttp3ConnectionManagerGet the HttpClient and call itexecuteMethods.

3.2. Self-introduction

Before it is based on apche HTTP client or OKHTTP3 package an HttpUtils similar tool class, for the system to call other application services to provide HTTP protocol interface, some projects are directly based on Spring to provide the RestTemplate to encapsulate the relevant interface. Regardless of the implementation, we actually find this kind of templated code quite common.

Until I met a colleague from another team, recommended the Forest, reviewed its introductory documentation, and quickly integrated the tool into a new application service. The biggest programming experience is: We need for each of the three services only defines an interface, through the annotation form identity request method of participation, at the same time identify the uri and HTTP requests related to configuration parameters, then the interface into your service, you can direct method interface method, it looks too simple, the code also looks more clean and tidy.

We were able to introduce them directly to other projects, which were quickly promoted, and our colleagues were able to follow suit. With the help of free time, and then download forest project source code, found that the project source code organization is quite beautiful. A small utility class that introduces a lot of design patterns makes the code beautiful.

I then went through the code of a request flow step by step, savoring the organizational programming beauty of the code, and ended up with this article to share with you.

— At 14:27 on August 6, 2021 at HW Building, Chaoyang, Beijing

【Forest官网】forest.dtflyx.com/