UniHttp – A Declarative HTTP Interface Integration Framework for Java Backend Projects
This article introduces UniHttp, a declarative HTTP client framework that simplifies third‑party API integration in Java backend applications by replacing manual HttpClient/OkHttp code with annotated interfaces, provides quick‑start instructions, detailed annotation usage, lifecycle hooks, custom channel integration examples, and configuration tips.
Preface
In enterprise projects, using traditional programmatic HTTP clients such as HttpClient or OkHttp to call third‑party APIs often leads to scattered, hard‑to‑maintain code, especially when different developers implement their own wrappers.
If you face this problem, UniHttp is the solution.
1. Introduction
UniHttp is a declarative HTTP interface integration framework that lets you call remote HTTP services as if invoking local methods. It automatically handles request construction, parameter binding, and response deserialization, similar to configuring a Spring MVC controller but in reverse.
The framework focuses on high cohesion and readability while abstracting away low‑level HTTP details; under the hood it still uses OkHttp.
2. Quick Start
2.1 Add Dependency
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>uniapi-http</artifactId>
<version>0.0.4</version>
</dependency>2.2 Define Interface
Create an interface and annotate it with @HttpApi , specifying the base URL, then annotate each method with request‑type annotations.
Example methods:
GET http://localhost:8080/getUser
POST http://localhost:8080/addUser
Method return types correspond to the response body and are automatically deserialized with Fastjson.
@HttpApi(url = "http://localhost:8080")
interface UserHttpApi {
@GetHttpInterface("/getUser")
BaseRsp
getUser(@QueryPar("name") String param, @HeaderPar("userId") Integer id);
@PostHttpInterface("/addUser")
BaseRsp
addUser(@BodyJsonPar Add4DTO req);
}Parameter Annotations
@QueryPar – puts the value into the URL query string.
@HeaderPar – puts the value into request headers.
@BodyJsonPar – serializes the value as JSON in the request body (Content‑Type: application/json).
Generated HTTP Requests
GET http://localhost:8080/getUser?name=param
Header:
userId: id POST http://localhost:8080/addUser
Header:
Content-Type: application/json
Body:
{"id":1,"name":"jay"}2.3 Package Scanning
Use @UniAPIScan("com.xxx.demo.api") on a Spring configuration class to automatically create proxy beans for all interfaces annotated with @HttpApi .
@UniAPIScan("com.xxx.demo.api")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}2.4 Dependency Injection
@Service
class UserAppService {
@Autowired
private UserHttpApi userHttpApi;
public void doSomething() {
userHttpApi.getUser("jay", 3);
}
}3. Detailed Annotation Guide
3.1 @HttpApi
Marks an interface as a remote HTTP API; you can set the base URL and custom processor.
3.2 @HttpInterface and Shortcut Annotations
Use @PostHttpInterface , @GetHttpInterface , @PutHttpInterface , @DeleteHttpInterface to specify the HTTP method and path.
3.3 Parameter Annotations
@QueryPar – query parameters (supports primitive, collection, object, Map).
@PathPar – URL path variables (primitive only).
@HeaderPar – request headers (primitive, object, Map).
@CookiePar – cookies (String, Map, Cookie object, List<Cookie>).
@BodyJsonPar – JSON body (object, collection, Map, primitive).
@BodyFormPar – form‑urlencoded body.
@BodyMultiPartPar – multipart/form‑data (supports files).
@BodyBinaryPar – binary body (InputStream, File, InputStreamSource).
3.4 Raw HttpResponse
You can return HttpResponse<T> to access status code, headers, cookies, and the deserialized body.
3.5 File Download Responses
Supported return types: HttpBinaryResponse , HttpFileResponse , HttpInputStreamResponse .
3.6 HttpApiProcessor Lifecycle Hooks
The processor provides five hooks:
postBeforeHttpMetadata // before request is built
postSendingHttpRequest // when request is sent
postAfterHttpResponseBodyString // after receiving raw body string
postAfterHttpResponseBodyResult // after deserialization
postAfterMethodReturnValue // after method return (AOP‑like)Each hook receives the relevant metadata, method invocation, and can modify or log the request/response.
3.7 Custom Http Client Configuration
@Configuration
public class CustomConfiguration {
@Bean
public OkHttpClient myOkHttpClient() {
return new OkHttpClient.Builder()
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(20, 10, TimeUnit.MINUTES))
.build();
}
}4. Enterprise‑Level Channel Integration Example
Scenario: a weather service requires a token and sessionId obtained from a pre‑call, a signature generated with a public key, and an appId added to every request.
4.1 Configuration (application.yml)
channel:
mtuan:
url: http://127.0.0.1:8999
appId: UUU-asd-01
publicKey: fajdkf9492304jklfahqq4.2 Custom Annotation
@HttpApi(processor = MTuanHttpApiProcessor.class)
public @interface MTuanHttpApi {
@AliasFor(annotation = HttpApi.class)
String url() default "${channel.mtuan.url}";
String appId() default "${channel.mtuan.appId}";
}4.3 Define API Interface
@MTuanHttpApi
public interface WeatherApi {
@GetHttpInterface("/getCityByName")
BaseRsp
getCityWeather(@QueryPar("city") String cityName);
@PostHttpInterface("/getToken")
HttpResponse
> getToken(@HeaderPar("appId") String appId,
@HeaderPar("publicKey") String publicKey);
}4.4 Implement Processor
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor
{
@Value("${channel.mtuan.publicKey}") private String publicKey;
@Value("${channel.mtuan.appId}") private String appId;
@Autowired private Environment environment;
@Autowired private WeatherApi weatherApi;
@Override
public HttpMetadata postBeforeHttpMetadata(HttpMetadata meta, HttpApiMethodInvocation
inv) {
// add appId query param
String resolvedAppId = environment.resolvePlaceholders(inv.getProxyApiAnnotation().appId());
meta.putQueryParam("appId", resolvedAppId);
// generate signature and add to header
String sign = createSignKey(meta.getHttpUrl().getQueryParam(), meta.getBody());
meta.putHeader("sign", sign);
return meta;
}
private String createSignKey(Map
query, HttpBody body) {
String queryStr = query.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(";"));
String bodyStr = "";
if (body instanceof HttpBodyJSON) bodyStr = body.toStringBody();
else if (body instanceof HttpBodyFormData) bodyStr = body.toStringBody();
else if (body instanceof HttpBodyMultipart) bodyStr = body.toStringBody();
String raw = publicKey + queryStr + bodyStr;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return new String(md.digest(raw.getBytes()));
} catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); }
}
@Override
public HttpResponse
postSendHttpRequest(HttpSender sender, HttpMetadata meta) {
// obtain token and sessionId once per request
HttpResponse
tokenResp = weatherApi.getToken(appId, publicKey);
String token = tokenResp.getBodyResult();
String sessionId = tokenResp.getHeader("sessionId");
meta.addCookie(new Cookie("token", token));
meta.addCookie(new Cookie("sessionId", sessionId));
return sender.sendHttpRequest(meta);
}
@Override
public Object postAfterHttpResponseBodyResult(Object bodyResult, HttpResponse
rsp, Method method, HttpMetadata meta) {
if (bodyResult instanceof BaseRsp) {
((BaseRsp) bodyResult).setCode(999);
}
return bodyResult;
}
}5. Conclusion
The full source code is available at https://github.com/burukeYou/UniAPI . See the uniapi-test-http module for concrete usage examples.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.