二维码在现代应用中无处不在,从登录授权、支付场景到商品溯源,都离不开二维码的生成与识别。在Java生态中,ZXing(Zebra Crossing)是最经典的开源条码处理库,它不仅支持二维码(QR Code)的生成与解析,还覆盖了一维码的多种格式。本文将完整讲解如何基于ZXing实现二维码生成与扫码识别功能,并给出可以直接运行的代码示例和常见的进阶技巧。

一、准备工作与依赖引入
在使用ZXing之前,需要先在项目中引入相关依赖。ZXing的核心模块是core,它负责编码解码的核心算法;如果需要将二维码输出为图片,通常还会引入javase模块,它提供了MatrixToImageWriter这样的工具类,可以把编码结果BitMatrix转换为BufferedImage或直接写入文件。
以Maven项目为例,在pom.xml中添加以下依赖即可。如果只是做服务端生成二维码,两个依赖都引入是最省事的做法,避免在运行时遇到ClassNotFoundException。
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.2</version>
</dependency>需要说明的是,javase模块内部依赖了jai-imageio等组件,在极简环境下如果只想用核心功能,可以只引core,然后自己实现位矩阵转图片的逻辑。但绝大多数场景下,直接使用MatrixToImageWriter更简单可靠,它内部处理了黑白像素的映射、图片格式适配等细节。
二、二维码生成的完整实现
生成二维码的核心流程分为三步:构建内容参数、执行编码得到位矩阵、将位矩阵输出为图片。ZXing通过MultiFormatWriter或QRCodeWriter执行编码,编码结果是一个BitMatrix对象,其中true表示黑色模块,false表示白色模块。
下面的示例演示了如何生成一个指定尺寸、指定字符集的二维码,并输出为PNG图片。这里特别要注意Hints参数的配置,其中EncodeHintType.CHARACTER_SET设置为UTF-8是为了正确编码中文内容,MARGIN控制二维码四周的白边宽度,默认值是4个模块宽度,如果设为0可能会导致扫码设备难以识别。
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class QrCodeGenerator {
public static void generate(String content, int width, int height, String filePath) {
try {
Map<EncodeHintType, Object> hints = new HashMap<>();
// 设置字符集,保证中文内容不乱码
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 设置边距,单位是模块数
hints.put(EncodeHintType.MARGIN, 2);
// 设置容错等级,Level H 允许约30%的面积被遮挡
hints.put(EncodeHintType.ERROR_CORRECTION, "H");
QRCodeWriter writer = new QRCodeWriter();
BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
Path path = new File(filePath).toPath();
MatrixToImageWriter.writeToPath(matrix, "PNG", path);
System.out.println("二维码已生成:" + filePath);
} catch (WriterException | IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
generate("https://www.ipipp.com/article/qr-demo", 300, 300, "qrcode.png");
}
}在Web项目中,通常不需要把图片写到磁盘,而是直接把二维码以图片流的形式响应给浏览器。这时可以先调用MatrixToImageWriter.toBufferedImage(matrix)得到BufferedImage,再通过ImageIO写入HttpServletResponse的输出流。这种方式避免了服务器产生临时文件,更适合高并发的场景。
关于容错等级的选择需要多做一句说明。QR Code规范定义了L、M、Q、H四个容错等级,分别允许约7%、15%、25%、30%的破损面积。等级越高,二维码存储同样内容所需的模块数越多,图形也越密集。如果计划在二维码中央叠加Logo,就必须使用H等级,否则Logo遮挡的部分可能导致解码失败。
三、扫码识别的实现思路
扫码识别是编码的逆过程,核心是使用MultiFormatReader对图片进行解码,得到一个Result对象,其中包含了识别出的文本内容、条码格式以及文本在图片中的位置信息。下面的代码演示了从本地图片文件中识别二维码的完整实现。
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class QrCodeReader {
public static String decode(String filePath) {
try {
BufferedImage image = ImageIO.read(new File(filePath));
if (image == null) {
throw new IllegalArgumentException("无法读取图片:" + filePath);
}
// 转换为灰度亮度源,再进行二值化处理
BinaryBitmap bitmap = new BinaryBitmap(
new HybridBinarizer(new BufferedImageLuminanceSource(image)));
Map<DecodeHintType, Object> hints = new HashMap<>();
// 尝试识别为二维码
hints.put(DecodeHintType.POSSIBLE_FORMATS,
Collections.singletonList(com.google.zxing.BarcodeFormat.QR_CODE));
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
MultiFormatReader reader = new MultiFormatReader();
Result result = reader.decode(bitmap, hints);
return result.getText();
} catch (Exception e) {
System.out.println("识别失败:" + e.getMessage());
return null;
}
}
public static void main(String[] args) {
String text = decode("qrcode.png");
System.out.println("识别结果:" + text);
}
}解码过程中最关键的一步是HybridBinarizer的二值化处理。原始图片是彩色的,而二维码识别算法只关心黑白模块的分布,HybridBinarizer会根据局部区域的亮度自适应地计算阈值,比全局阈值的方式对光照不均的图片有更好的容忍度。这也是为什么同样的二维码,ZXing在有些模糊照片上仍能识别成功的原因。
对于识别失败的图片,可以尝试两个优化手段:一是先用tryHarder提示,即把DecodeHintType.TRY_HARDER加入hints,它会启用更激进的模式搜索算法,代价是耗时增加;二是对图片做预处理,例如缩放、裁剪、锐化后再送入解码器。实际项目中,扫描界面拍到的照片往往角度倾斜、有反光,适当的多轮重试策略能显著提高识别成功率。
四、进阶技巧与常见问题
第一个常见需求是生成带Logo的二维码。实现思路很简单:先按H容错等级生成标准二维码,得到BufferedImage后,用Graphics2D在图片中央绘制Logo图片即可。由于H等级允许30%的破损,一般Logo尺寸控制在二维码宽度的五分之一左右是安全的。需要注意的是,Logo不要使用带透明通道边缘模糊的图片,模糊边缘会干扰周围模块的识别。
第二个常见问题是内容长度限制。QR Code的版本从1到40,版本越高模块数越多,最大容量在数字模式下约7089个字符,但在UTF-8编码的混合模式下,容量会明显下降。如果发现内容过长导致WriterException,可以考虑先压缩内容再编码,或者改用短链接服务,把长URL缩短后再生成二维码。
第三个问题是线程安全。QRCodeWriter和MultiFormatReader本身是无状态的,可以在多线程环境中复用,但Hints的Map对象如果被并发修改就会有风险,建议每个线程构建自己的hints实例,或者使用不可变集合。在高并发生成场景下,还可以把生成的BitMatrix缓存起来,相同内容直接复用,减少重复编码的开销。
最后简单对比一下ZXing与Hutool等封装库。Hutool的QrCodeUtil底层实际上就是调用ZXing,只是提供了更简洁的API,例如QrCodeUtil.generate(url, 300, 300, pngFile)一行就能完成生成,还内置了Logo叠加功能。如果项目已经引入了Hutool,直接用它会更省事;如果对依赖体积敏感,或者需要深度定制二值化、解码策略,那么直接使用ZXing原生API是更灵活的选择。无论采用哪种方式,理解BitMatrix、容错等级和Hints这些底层概念,都能帮助你在遇到问题时快速定位原因,构建出稳定可靠的二维码功能。