DEV Community

paohaijiao
paohaijiao

Posted on Originally published at paohaijiao.hashnode.dev

Deep Dive into @JCurlCommand: The Annotation and Its Variable Substitution Grammar

@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);
}
Enter fullscreen mode Exit fullscreen mode

The ${name} Variable Grammar

JQuickCurl embeds variables inside the curl string using ${name}. At execution time the proxy:

  1. Collects the keys you put into JQuickCurlReq.
  2. Copies them into the request context (JContext).
  3. 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
Enter fullscreen mode Exit fullscreen mode

Query Strings

@JCurlCommand("curl -X GET 'https://api.example.com/search?q=${query}&page=${page}&size=${size}'")
String search(JQuickCurlReq request);
Enter fullscreen mode Exit fullscreen mode
JQuickCurlReq req = new JQuickCurlReq();
req.put("query", "http client");
req.put("page", 2);
req.put("size", 20);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode
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"));
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
String payload = "{\"customer\":\"Ada\",\"amount\":99.5,\"items\":[1,2,3]}";
JQuickCurlReq req = new JQuickCurlReq();
req.put("payload", payload);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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.
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

java #springboot #httpclient #opensource #java-library

Top comments (0)