ImageIO.read返回null问题排查:ImageResizer应用代码求助
解决ImageIO.read返回null的问题及代码优化建议
代码中的直接问题(大概率触发报错)
你的rotate方法里存在一个致命问题:MultipartFile的InputStream只能读取一次。你先调用ImageMetadataReader.readMetadata(org.inputStream)读取了流的内容,此时流的指针已经移到末尾,后续再调用ImageIO.read(org.inputStream)时,流里已经没有数据了,自然返回null。
其他可能的排查方向
- 格式支持问题:默认ImageIO只支持JPG、PNG、GIF等常见格式,如果用户上传的是WebP、HEIC、AVIF这类格式,ImageIO无法识别就会返回null。
- 文件损坏/不完整:部分图片可能传输过程中丢失字节,或者本身是损坏的文件,导致ImageIO无法解析。
- 文件名与实际格式不匹配:你通过文件名后缀获取格式,但如果用户上传的文件后缀错误(比如把WebP文件改名为jpg),后续处理也会出问题。
- 元数据读取异常影响流:虽然用了
runCatching包裹元数据读取,但如果读取过程中流已经被部分消费,后续读取图片时也会不完整。
优化方案及代码调整
- 先将MultipartFile转为字节数组,复用数据
把MultipartFile的内容一次性读到字节数组里,之后用ByteArrayInputStream来多次读取,避免流被耗尽的问题。 - 检测图片实际格式,不依赖文件名
用流检测图片真实格式,避免后缀误导。 - 增加日志记录
在关键步骤记录日志,比如异常信息、图片格式、大小等,方便后续排查问题。 - 添加格式支持插件(可选)
如果需要支持WebP、HEIC等格式,可以引入对应的ImageIO插件。
优化后的代码示例:
private const val TARGET_WIDTH = 800 fun resize(image: MultipartFile): ByteArray? { val imageBytes = image.bytes.takeIf { it.isNotEmpty() } ?: return null // 检测图片真实格式,替代依赖文件名后缀 val format = detectImageFormat(imageBytes) ?: image.originalFilename?.substringAfterLast(".", "") ?: "jpg" val inputImage = rotate(imageBytes) if (inputImage.width <= TARGET_WIDTH) { return imageBytes } val targetHeight = (TARGET_WIDTH / inputImage.ratio).toInt() val asBufferedImage = Thumbnails .of(inputImage) .size(TARGET_WIDTH, targetHeight) .asBufferedImage() return toByteArray(asBufferedImage, format) } private val BufferedImage.ratio: Double get() = 1.0 * width / height private fun detectImageFormat(imageBytes: ByteArray): String? { return ImageIO.getImageReadersBySuffix("") .asSequence() .firstOrNull { reader -> ByteArrayInputStream(imageBytes).use { input -> reader.canReadRaster(input) } }?.formatName?.lowercase() } private fun toByteArray(outputImage: BufferedImage, format: String): ByteArray { return ByteArrayOutputStream().use { outputStream -> val success = ImageIO.write(outputImage, format, outputStream) if (!success) { throw IllegalStateException("无法将图片转换为$format格式") } outputStream.toByteArray() } } private fun rotate(imageBytes: ByteArray): BufferedImage { val orientation = runCatching { ByteArrayInputStream(imageBytes).use { input -> ImageMetadataReader .readMetadata(input) .getFirstDirectoryOfType(ExifIFD0Directory::class.java) .getInt(ExifIFD0Directory.TAG_ORIENTATION) } }.getOrDefault(1) val degree = when (orientation) { 5, 6 -> 90.0 3, 4 -> 180.0 7, 8 -> 270.0 else -> 0.0 } val orgImage = ByteArrayInputStream(imageBytes).use { ImageIO.read(it) } ?: throw IllegalArgumentException("无法解析图片流") return if (degree == 0.0) { orgImage // 无需旋转直接返回,避免创建新的BufferedImage } else { // 根据旋转角度调整图片宽高,避免黑边 val (newWidth, newHeight) = if (degree == 90.0 || degree == 270.0) { orgImage.height to orgImage.width } else { orgImage.width to orgImage.height } BufferedImage(newWidth, newHeight, orgImage.type).apply { createGraphics().use { graphics -> (graphics as Graphics2D).apply { setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR) rotate(Math.toRadians(degree), newWidth / 2.0, newHeight / 2.0) // 根据旋转角度调整绘制位置,避免内容偏移 val x = if (degree == 90.0 || degree == 270.0) (newWidth - orgImage.width) / 2.0 else 0.0 val y = if (degree == 90.0 || degree == 270.0) (newHeight - orgImage.height) / 2.0 else 0.0 drawImage(orgImage, x.toInt(), y.toInt(), null) } } } } }
额外排查建议
- 在生产环境中添加日志,记录出现问题时的图片文件名、大小、上传时间等信息,哪怕拿不到原图,也能缩小排查范围。
- 尝试模拟各种异常场景:上传空文件、损坏的图片、不支持的格式文件,看是否能复现问题。
- 给
ImageIO.read的结果增加非空判断,抛出明确的异常或返回友好提示,避免空指针扩散。
内容的提问来源于stack exchange,提问作者CML7
相关产品推荐
相关产品推荐

