Java如何实现批量打印条形码

 更新时间:2025年05月15日 09:01:52   作者:Katie。  
这篇文章主要为大家详细介绍了如何基于 ZXing 库生成条形码位图,并利用 Java 打印服务(PrinterJob)将它们以网格方式批量打印到 A4 标签纸或标签纸格式,需要的小伙伴可以参考一下

一、项目背景详细介绍

在仓储管理、商品追溯、物流配送等场景中,条形码(Barcode)作为物品唯一标识被广泛使用。为提高效率,经常需要将一批数据转为条形码并打印在标签纸或 A4 页面上,满足打包扫描、入库出库等作业流程。

传统做法可能依赖专有打标签软件,灵活性差且难以集成到定制化系统。使用 Java 开发一套 批量生成并打印条形码 的工具,可以:

灵活集成:嵌入现有仓储或 ERP 系统,直接从数据库或 CSV 导入数据;

定制标签:支持调整条形码大小、分辨率、文字位置;

自动化批量:一键批量生成、分页排版、提交给打印机;

零第三方依赖:仅需开源库 ZXing 和 Java 自带打印 API。

本项目将演示如何基于 ZXing 库生成条形码位图,利用 Java 打印服务(PrinterJob)将它们以网格方式批量打印到 A4 标签纸或标签纸格式(如 3×10 布局)。

二、项目需求详细介绍

2.1 功能需求

1.批量数据导入

支持从文本文件(CSV)、数据库或内存列表读取多条待编码内容;

2.条形码生成

  • 使用 ZXing 生成 Code128 或 EAN-13 条形码;
  • 支持指定宽度、高度、边距及是否显示文本;

3.分页排版

  • 在 A4 纸或标签纸上按指定行列(如 2 列×5 行)自动排版;
  • 支持页眉页脚或批次号打印;

4.打印预览(可选)

弹出打印对话框预览分页及打印机设置;

5.提交打印

通过 PrinterJob 接口,支持选择打印机及打印范围;

6.错误处理与日志

捕获生成失败、打印失败,记录日志并跳过继续;

2.2 非功能需求

易用性:一行命令或简单 GUI 即可完成;

配置化:页面尺寸、行列数、条码样式通过配置文件管理;

跨平台:仅依赖 Java SE 与 ZXing,可在 Windows、Linux、macOS 上运行;

可扩展:后续可支持二维码(QR Code)等多种格式;

三、相关技术详细介绍

1.ZXing (com.google.zxing)

  • 主流开源条码/二维码生成与解析库;
  • 支持多种码制:Code128, EAN13, QR_CODE 等;

2.Java2D (java.awt.Graphics2D)

在 BufferedImage 上绘制条码位图;

3.PrinterJob (java.awt.print)

  • 标准打印 API:PrinterJob、PageFormat、Printable;
  • 支持页面设置与打印对话框;

4.配置管理

使用 java.util.Properties 或 Spring @ConfigurationProperties 加载标签布局;

5.日志框架

SLF4J + Logback 记录运行状态;

四、实现思路详细介绍

1.加载配置与数据

  • 从 config.properties 读取:页面宽度、高度、行数、列数、条形码尺寸、边距等;
  • 从 CSV 或数据库加载待打印的编码字符串列表;

2.生成条形码图像

  • 对每条内容调用 ZXing 的 MultiFormatWriter.encode(),生成 BitMatrix;
  • 将 BitMatrix 转换为 BufferedImage,并在底部添加文本标签(可选);

3.分页排版

实现 Printable 接口:

  • 在 print(Graphics g, PageFormat pf, int pageIndex) 中,根据 pageIndex 计算当前页的数据子列表;
  • 对每行每列绘制对应条码图像,使用 g.drawImage();
  • 如有文字批次号或条码文本,使用 Graphics2D.drawString();
  • 返回 PAGE_EXISTS 或 NO_SUCH_PAGE;

4.打印提交

PrinterJob job = PrinterJob.getPrinterJob(); job.setPrintable(printable, pageFormat); if(job.printDialog()) job.print();

5.日志与异常

条码生成或打印异常时,捕获并记录,跳过当前码或重试;

五、完整实现代码

import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.oned.Code128Writer;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.print.*;
import java.io.*;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class BatchBarcodePrinter implements Printable {
    private static final Logger logger = LoggerFactory.getLogger(BatchBarcodePrinter.class);
 
    private List<String> codes;         // 待打印内容
    private Properties config;          // 布局及条码配置
    private List<BufferedImage> images; // 生成的条码图像列表
    private int cols, rows;             // 每页列数和行数
    private int imgWidth, imgHeight;    // 条码图像尺寸
    private int marginX, marginY;       // 页面边距
    private int gapX, gapY;             // 图像间隔
 
    public BatchBarcodePrinter(List<String> codes, Properties cfg) throws Exception {
        this.codes = codes;
        this.config = cfg;
        loadConfig();
        generateImages();
    }
 
    // 读取配置
    private void loadConfig() {
        cols       = Integer.parseInt(config.getProperty("page.cols", "2"));
        rows       = Integer.parseInt(config.getProperty("page.rows", "5"));
        imgWidth   = Integer.parseInt(config.getProperty("barcode.width", "200"));
        imgHeight  = Integer.parseInt(config.getProperty("barcode.height", "80"));
        marginX    = Integer.parseInt(config.getProperty("page.marginX", "50"));
        marginY    = Integer.parseInt(config.getProperty("page.marginY", "50"));
        gapX       = Integer.parseInt(config.getProperty("page.gapX", "20"));
        gapY       = Integer.parseInt(config.getProperty("page.gapY", "30"));
    }
 
    // 生成所有条码图像
    private void generateImages() {
        images = new ArrayList<>();
        Code128Writer writer = new Code128Writer();
        for (String code : codes) {
            try {
                BitMatrix bm = writer.encode(code, BarcodeFormat.CODE_128, imgWidth, imgHeight);
                BufferedImage img = new BufferedImage(imgWidth, imgHeight + 20, BufferedImage.TYPE_INT_RGB);
                Graphics2D g = img.createGraphics();
                g.setColor(Color.WHITE);
                g.fillRect(0, 0, img.getWidth(), img.getHeight());
                // 绘制条码
                for (int x = 0; x < imgWidth; x++) {
                    for (int y = 0; y < imgHeight; y++) {
                        g.setColor(bm.get(x, y) ? Color.BLACK : Color.WHITE);
                        g.fillRect(x, y, 1, 1);
                    }
                }
                // 绘制文本
                g.setColor(Color.BLACK);
                g.setFont(new Font("Monospaced", Font.PLAIN, 14));
                FontMetrics fm = g.getFontMetrics();
                int tx = (imgWidth - fm.stringWidth(code)) / 2;
                g.drawString(code, tx, imgHeight + fm.getAscent());
                g.dispose();
                images.add(img);
            } catch (Exception e) {
                logger.error("生成条码失败: {}", code, e);
            }
        }
    }
 
    @Override
    public int print(Graphics graphics, PageFormat pf, int pageIndex) {
        int perPage = cols * rows;
        int start = pageIndex * perPage;
        if (start >= images.size()) return NO_SUCH_PAGE;
        Graphics2D g = (Graphics2D) graphics;
        g.translate(pf.getImageableX(), pf.getImageableY());
        int x0 = marginX, y0 = marginY;
        // 绘制本页条码
        for (int i = 0; i < perPage; i++) {
            int idx = start + i;
            if (idx >= images.size()) break;
            int row = i / cols, col = i % cols;
            int x = x0 + col * (imgWidth + gapX);
            int y = y0 + row * (imgHeight + gapY + 20);
            g.drawImage(images.get(idx), x, y, null);
        }
        return PAGE_EXISTS;
    }
 
    // 启动打印
    public void printAll() throws PrinterException {
        PrinterJob job = PrinterJob.getPrinterJob();
        job.setPrintable(this);
        if (job.printDialog()) {
            job.print();
        }
    }
 
    public static void main(String[] args) throws Exception {
        // 1. 加载待打印数据
        List<String> codes = new ArrayList<>();
        try (BufferedReader br = new BufferedReader(new FileReader("codes.csv"))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (!line.trim().isEmpty()) codes.add(line.trim());
            }
        }
        // 2. 加载配置
        Properties cfg = new Properties();
        try (InputStream in = new FileInputStream("config.properties")) {
            cfg.load(in);
        }
        // 3. 批量打印
        BatchBarcodePrinter printer = new BatchBarcodePrinter(codes, cfg);
        printer.printAll();
    }
}

六、代码详细解读

1.配置加载

从 config.properties 中读取:page.cols, page.rows(每页行列数)、barcode.width, barcode.height(条码图像尺寸)、页边距与间隔等,便于灵活调整布局。

2.条码生成

使用 ZXing 的 Code128Writer 生成 BitMatrix,逐像素渲染到 BufferedImage,并在下方绘制文本。

3.实现 Printable

print(...) 根据 pageIndex 计算本页起始索引,并按行列遍历绘制对应条码图像,返回 PAGE_EXISTS 或 NO_SUCH_PAGE 控制分页。

4.打印提交

PrinterJob 弹出打印对话框,用户可选择打印机与份数,调用 print() 发起。

5.主方法流程

  • 从 codes.csv 读取待打印条码列表;
  • 从 config.properties 加载布局与尺寸;
  • 构造 BatchBarcodePrinter 并调用 printAll()。

七、项目详细总结

本项目展示了如何用 Java + ZXing + PrinterJob 实现 批量打印条形码:

数据灵活:支持从文件或数据库导入任意数量的编码字符串;

配置驱动:行列数、图像大小、页边距等通过外部配置调整;

圖形生成:ZXing 可靠生成高质量 Code128 条码,并可扩展到 QR Code;

分页打印:标准 Java 打印 API 自动分页,兼容任意打印机;

零商业依赖:仅用开源组件即可完成企业级标签打印需求。

适合物流、仓储、制造业等需要批量生产标签的场景,也可作为 Java 图形与打印 API 教学案例。

八、项目常见问题及解答

Q1:条码图像模糊或尺寸不符?

A:确认 imgWidth/imgHeight 与实际打印机 DPI 配合,必要时增加分辨率或调整页面缩放。

Q2:打印出现空白页?

A:检查 print() 返回值逻辑,确保当 start >= images.size() 时返回 NO_SUCH_PAGE,否则会多打印空页。

Q3:条码下方文字不居中?

A:文本绘制使用 FontMetrics 计算字符串宽度,确保 x = (imgWidth - textWidth)/2。

Q4:需要打印二维码?

A:将 Code128Writer 换为 QRCodeWriter,并设置 BitMatrix 尺寸,文本可放到旁边或不显示。

九、扩展方向与性能优化

大批量流式生成:对超万条数据,分批生成图像并实时打印,避免一次性占用大量内存;

自定义标签纸尺寸:支持常见标签纸(如 4×8 标签)模板配置,精确匹配打印机纸张;

并发打印:多线程调用不同 PrinterJob 实例同时发送到多台打印机;

网络打印集成:支持通过 IPP 协议或打印服务器推送打印作业;

RTF/Word 导出:在 Word 文档或 RTF 中嵌入条码图像,扩展报表输出;

GUI 预览与编辑:用 Swing 或 JavaFX 提供可视化布局工具,让用户拖拽调整标签位置。

到此这篇关于Java如何实现批量打印条形码的文章就介绍到这了,更多相关Java打印条形码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring中@Configuration注解和@Component注解的区别详解

    Spring中@Configuration注解和@Component注解的区别详解

    这篇文章主要介绍了Spring中@Configuration注解和@Component注解的区别详解,@Configuration 和 @Component 到底有何区别呢?我先通过如下一个案例,在不分析源码的情况下,小伙伴们先来直观感受一下这两个之间的区别,需要的朋友可以参考下
    2023-09-09
  • Java实现文件上传到服务器本地并通过url访问的方法步骤

    Java实现文件上传到服务器本地并通过url访问的方法步骤

    最近项目中使用到了文件上传到服务器的功能,下面这篇文章主要给大家介绍了关于Java实现文件上传到服务器本地并通过url访问的方法步骤,文中通过图文以及实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • PowerJob的TimingStrategyHandler工作流程源码解读

    PowerJob的TimingStrategyHandler工作流程源码解读

    这篇文章主要为大家介绍了PowerJob的TimingStrategyHandler工作流程源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Java中String的split切割字符串方法实例及扩展

    Java中String的split切割字符串方法实例及扩展

    最近在项目中遇到一个小问题,一个字符串分割成一个数组,下面这篇文章主要给大家介绍了关于Java中String的split切割字符串方法的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • 解决Maven中的依赖导包问题(组合技巧)

    解决Maven中的依赖导包问题(组合技巧)

    自从我开始接触了以spring为框架的项目学习后,这个maven导包老是出现问题,每次在这个上面花费好多时间,于是乎打算写一个秘籍出来,这篇文章主要介绍了解决Maven中的依赖导包问题,需要的朋友可以参考下
    2023-11-11
  • SpringBoot选择自有bean优先加载实现方法

    SpringBoot选择自有bean优先加载实现方法

    在一些需求中,可能存在某些场景,比如先加载自己的bean,然后自己的bean做一些DB操作,初始化配置问题,然后后面的bean基于这个配置文件,继续做其他的业务逻辑。因此有了本文的这个题目
    2023-03-03
  • Java微信公众平台开发(5) 文本及图文消息回复的实现

    Java微信公众平台开发(5) 文本及图文消息回复的实现

    这篇文章主要为大家详细介绍了Java微信公众平台开发第五步,回文本及图文消息回复的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • java关键字final用法知识点

    java关键字final用法知识点

    在本篇文章里小编给大家分享的是关于java关键字final用法知识点以及相关实例内容,有需要的朋友们可以学习下。
    2019-09-09
  • Java多线程中的死锁详解

    Java多线程中的死锁详解

    这篇文章主要介绍了Java多线程中的死锁详解,死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果不提前预防或外界干扰,这些线程将无法执行下去,需要的朋友可以参考下
    2023-08-08
  • Java对字符串进行加密解密

    Java对字符串进行加密解密

    这篇文章主要为大家详细介绍了Java字符串加密解密,对用户输入的每个字符的值进行加密解密,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06

最新评论