Concept‑Download: A Spring Library for Annotation‑Driven File Download
Concept‑Download is a Spring library that lets developers replace verbose, multi‑step file‑download code with a single @Download annotation, automatically handling source loading, optional ZIP compression, and response writing for both MVC and WebFlux via a modular reactive handler chain.
This article introduces a Java library that simplifies file‑download implementations in Spring MVC and WebFlux by using a single annotation.
Typical download scenario : exporting QR‑code images of devices as a zip file. The usual implementation requires dozens of steps – querying devices, downloading each image, checking cache, compressing, and writing the response – resulting in hundreds of lines of code.
Goal : provide a declarative way where the controller only returns the data to be downloaded (e.g., a list of objects, a file path, a URL) and the library handles the rest.
Usage example :
@Download(source = "classpath:/download/README.txt")
@GetMapping("/classpath")
public void classpath() { }or for a zip of device QR codes:
@Download(filename = "二维码.zip")
@GetMapping("/download")
public List
download() {
return deviceService.all();
}
public class Device {
private String name;
@SourceObject
private String qrCodeUrl;
@SourceName
public String getQrCodeName() { return name + ".png"; }
}Design ideas
The library is built on reactive programming but works for both WebMVC and WebFlux. It abstracts the download process into a chain of DownloadHandler steps, each operating on a shared DownloadContext .
Key interfaces :
/** Download handler */
public interface DownloadHandler extends OrderProvider {
Mono
handle(DownloadContext context, DownloadHandlerChain chain);
}
public interface DownloadHandlerChain {
Mono
next(DownloadContext context);
}Source abstraction : any downloadable object is wrapped as a Source . Implementations such as FileSource and HttpSource are created by SourceFactory implementations.
public interface SourceFactory extends OrderProvider {
boolean support(Object source, DownloadContext context);
Source create(Object source, DownloadContext context);
}Loading : SourceLoader can load resources concurrently (thread‑pool, coroutine, etc.).
public interface SourceLoader {
Mono
load(Source source, DownloadContext context);
}Compression : SourceCompressor defines the compression format (e.g., zip) and performs the actual archiving.
public interface SourceCompressor extends OrderProvider {
String getFormat();
default boolean support(String format, DownloadContext ctx) { return format.equalsIgnoreCase(getFormat()); }
Compression compress(Source source, DownloadWriter writer, DownloadContext ctx);
}Response writing : The library abstracts the HTTP response with DownloadResponse to support both HttpServletResponse (WebMVC) and ServerHttpResponse (WebFlux). An example for WebFlux:
@Getter
public class ReactiveDownloadResponse implements DownloadResponse {
private final ServerHttpResponse response;
private OutputStream os;
private Mono
mono;
public ReactiveDownloadResponse(ServerHttpResponse response) { this.response = response; }
@Override
public Mono
write(Consumer
consumer) {
if (os == null) {
mono = response.writeWith(Flux.create(sink -> {
try {
os = new FluxSinkOutputStream(sink, response);
consumer.accept(os);
} catch (Throwable e) { sink.error(e); }
}));
} else {
consumer.accept(os);
}
return mono;
}
@SneakyThrows
@Override
public void flush() { if (os != null) os.flush(); }
@AllArgsConstructor
public static class FluxSinkOutputStream extends OutputStream {
private FluxSink
sink;
private ServerHttpResponse response;
@Override public void write(byte[] b) { writeSink(b); }
@Override public void write(byte[] b, int off, int len) {
byte[] bytes = new byte[len];
System.arraycopy(b, off, bytes, 0, len);
writeSink(bytes);
}
@Override public void write(int b) { writeSink((byte) b); }
@Override public void flush() { sink.complete(); }
public void writeSink(byte... bytes) {
DataBuffer buffer = response.bufferFactory().wrap(bytes);
sink.next(buffer);
DataBufferUtils.release(buffer);
}
}
}Event system : To keep the architecture extensible, the library publishes DownloadEvent s via DownloadEventPublisher and allows listeners to react to various stages (initialization, loading, compression, writing, termination).
Logging : Several listeners produce detailed logs for each step, progress updates, and total time spent, which helped uncover many bugs during development.
Other pitfalls : In WebFlux the response writing returns Mono.empty() , so downstream handlers are not invoked automatically. The author solved this by moving context cleanup to doAfterTerminate .
Overall, the library demonstrates how to combine annotations, reactive streams, and a modular handler chain to turn a complex download workflow into a few declarative lines of code.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
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.