@JCurlCommand is the heart of JQuickCurl's annotation mode. One annotation turns a static curl string into a dynamic, typed HTTP method. This post covers the annotation's full surface and — more importantly — the ${...} variable grammar that makes one command reusable across thousands of runtime states.
The Annotation, Decoded
@JCurlCommand targets methods and is retained at runtime so the proxy layer can read it on every invocation:
In practice you will use value() on every method; the other attributes give you a declarative place to document the response contract for a command. The empty constructor proxy flow rejects methods annotated with execute = false, so treat execute as "this annotation is a real executable command".
public interface HealthApi {
@JCurlCommand(
value = "curl -X GET https://httpbin.org/status/200"
)
String ping(JQuickCurlReq request);
}
The ${name} Variable Grammar
JQuickCurl embeds variables inside the curl string using ${name}. At execution time the proxy:
- Collects the keys you
putintoJQuickCurlReq. - Copies them into the request context (
JContext). - Replaces every
${name}occurrence with the corresponding runtime value.
Because substitution happens before the HTTP call, you can place placeholders anywhere syntax allows — URL path, query string, header value, auth pair, or request body.
URLs and Paths
public interface GithubLikeApi {
@JCurlCommand("curl -X GET 'https://api.example.com/repos/${owner}/${repo}/issues/${issueId}'")
String getIssue(JQuickCurlReq request);
}
// Usage
JQuickCurlReq req = new JQuickCurlReq();
req.put("owner", "dromara");
req.put("repo", "jquick-curl");
req.put("issueId", 42);
String issue = proxy.getIssue(req); // GET /repos/dromara/jquick-curl/issues/42
Query Strings
@JCurlCommand("curl -X GET 'https://api.example.com/search?q=${query}&page=${page}&size=${size}'")
String search(JQuickCurlReq request);
JQuickCurlReq req = new JQuickCurlReq();
req.put("query", "http client");
req.put("page", 2);
req.put("size", 20);
Headers and Authentication Pairs
Variables are not limited to URLs — credentials belong there too, so secrets never sit in source code:
public interface AuthApi {
@JCurlCommand("curl -X GET 'https://api.example.com/me' "
+ "-H 'Authorization: Bearer ${token}'")
String currentUser(JQuickCurlReq request);
@JCurlCommand("curl -u '${user}:${password}' https://api.example.com/account")
String account(JQuickCurlReq request);
}
JQuickCurlReq req = new JQuickCurlReq();
req.put("token", System.getenv("API_TOKEN")); // never hard-code secrets
req.put("user", "demo");
req.put("password", System.getenv("API_PASSWORD"));
Request Bodies
When the JSON body must change per call, build it in Java and substitute it:
@JCurlCommand("curl -X POST 'https://api.example.com/orders' "
+ "-H 'Content-Type: application/json' "
+ "-d '${payload}'")
String createOrder(JQuickCurlReq request);
String payload = "{\"customer\":\"Ada\",\"amount\":99.5,\"items\":[1,2,3]}";
JQuickCurlReq req = new JQuickCurlReq();
req.put("payload", payload);
Careful: the body is inserted verbatim, so the JSON must already be valid (quotes, braces, commas) before substitution.
Method Parameters as Variables (Alternative Style)
Besides the JQuickCurlReq map, JQuickCurl can substitute ${name} from method parameter names when a method declares extra parameters — the placeholder must equal the Java parameter's name. This gives you compiler-visible signatures for static call sites:
public interface OrderApi {
@JCurlCommand("curl -X GET 'https://api.example.com/orders/${orderId}'")
String getOrder(JQuickCurlReq request);
}
For dynamic multi-value cases the map style is far more flexible, which is why the rest of this series standardizes on JQuickCurlReq.
Quoting Rules and Escaping Cheat-Sheet
| What you write | Meaning |
|---|---|
'https://…/${id}' |
Single-quoted value with a variable — preferred style |
"Authorization: Bearer ${token}" |
Double-quoted value; you must escape as \" inside a Java annotation |
-d '{"a":1}' |
Body with double quotes → escape them in Java: "{\"a\":1}"
|
${key} with a missing key |
Left unresolved — the request context simply has no such entry |
If a response surprises you, first check whether the placeholder key actually made it into JQuickCurlReq before the call (see Post 17's troubleshooting checklist).
Runnable Demo
import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
public interface EchoApi {
@JCurlCommand("curl -X GET 'https://httpbin.org/anything/${channel}?trace=${traceId}' "
+ "-H 'X-Scope: ${scope}'")
String call(JQuickCurlReq request);
}
class VariableDemo {
public static void main(String[] args) {
EchoApi api = JCurlInvoker.createProxy(EchoApi.class);
JQuickCurlReq req = new JQuickCurlReq();
req.put("channel", "mobile");
req.put("traceId", "trc-9911");
req.put("scope", "readonly");
System.out.println(api.call(req));
// httpbin echoes url, headers, args — showing every variable resolved.
}
}
Summary
@JCurlCommand provides the executable surface (the curl string) and a small set of contract attributes, while ${name} placeholders supply the dynamism. Variables work anywhere in the command — path, query, headers, auth, and bodies — and their values come from the same JQuickCurlReq map that every method in this series already uses. Combined with the proxy pattern, one annotated command now serves an unlimited number of runtime requests.
Repository: dromara/jquick-curl. Post 6 applies this knowledge to the full REST alphabet — JSON GET, POST, PUT, and DELETE in annotation mode.
Top comments (0)