记一次InputStream重复读取的问题

背景:在做微信支付时,需要将证书放到数据库中再读入系统,因而需要将wxpay这个类库下的部分内容进行了重写,原代码如下:

 KeyStore keyStore = KeyStore.getInstance("PKCS12"); 
 FileInputStream instream = new FileInputStream(new File(configure.getCertLocalPath()))
 keyStore.load(instream, configure.getCertPassword().toCharArray());

原本 证书是在远程服务器,每次都是类库动态读入。但是因为种种原因无法将从系统上传的证书直接放到远程服务器中,所以打算将证书写入数据库。在系统系统的时候,加载微信支付配置

这也就有了接下来的写法:

 private void init(Configure configure) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream instream = configure.getInputStream();
keyStore.load(instream, configure.getCertPassword().toCharArray());
...

通过局部变量Configure,将从数据库读到的二进制文件类放到Configure中,然后直接获取流。

本来以为一切妥当。但是在调试的时候发现一个问题:本地支付若干笔,只有第一次是成功的,而后续的支付全部报错。错误信息直指DerInputStream#getLength方法,报错的代码是:

static int getLength(int var0, InputStream var1) throws IOException {
        if (var0 == -1) {
            throw new IOException("Short read of DER length");
        } else {
            String var4 = "DerInputStream.getLength(): ";
            int var2;
            if ((var0 & 128) == 0) {
                var2 = var0;
            } else {
                int var3 = var0 & 127;
                if (var3 == 0) {
                    return -1;
                    ...

没错,错误信息就是Short read of DER length
通过跟代码发现,这个var0恰是读取上文我们给出的instream字段的结果。

可为什么第一次支付就是可以的,后面的就报错了呢,我只改了这一处呀,会不会是流数据的问题?
带着这个疑问,我决定debug跟一下代码。当系统启动,进行第一次支付时,我断点查看上文init方法中instream字段的内容:

  • buf字段是有内容的
  • pos 值为0
  • mark 值为0
  • count值为2376

第一次支付未报任何问题,然后进行第二次支付,继续断点查看instream字段的内容:

  • buf字段是有内容的
  • pos 值为2376
  • mark 值为0
  • count值为2376

然后就报错了。

至此,发现了两次方法调用中一些细微的差异:流的pos位置变了,流的初始位置从0到与count值一致(应该是到了流的末尾了)。因为以前接触过NIO#buffer,就类比了下,pos就像指针,在流的读取过程中,一直往后移动直至末尾,即把流内容读完。

那pos位置已经到末尾了,能不能向NIO#buffer一样重置pos呢?
带着这个疑问,去查询inputStream的api,发现InputStream#reset方法似乎可以,但是紧接着查看方法定义,发现此路行不通:

public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

所以,从这一点也就解释了,为什么inputStream只能读取一次了!

解决方案

那么,我们能不能找个中间变量,将输入流存到中间变量中,然后使用的时候直接使用中间变量。
这也就有了下面的代码:

将输入流转为字节数组输出流

private ByteArrayOutputStream parse(InputStream input){
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = input.read(buffer)) > -1 ) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

然后在使用的时候,Configure对象存储这个ByteArrayOutPutStream对象字段

private void init(Configure configure) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {

        KeyStore keyStore = KeyStore.getInstance("PKCS12");
//      FileInputStream instream = new FileInputStream(new File(configure.getCertLocalPath()));//加载本地的证书进行https加密传输
//这里使用字节数组输出流
        InputStream instream = new ByteArrayInputStream(configure.getOutputStream().toByteArray());
        try {
            keyStore.load(instream, configure.getCertPassword().toCharArray());//设置证书密码
            ...

至此 关于微信支付的这个输入流问题解决了。

你可能感兴趣的:(Java)