java在生成驗證碼時,經常要用到ImageIO類,今天在一台windows 2008 server上部署好項目之後,項目怎麼都刷不出來驗證碼,後台可以捕捉到的異常,裡面包含有Can’t create output stream!
在我開始寫這篇解決問題的辦法時,我還沒有解決問題。
項目已經成功運行了很多個版本,在windows server 2003上運行OK。 在windows7上運行也OK。但偏偏到了windows server 2008上卻拉不出來驗證碼,真是引起了我極大的興趣!
主要的異常信息如下:
ERROR 2015-11-25 10:25:44,061 com.honzh.socket.server.communicate.biz.CodeBiz: Can't create output stream!
javax.imageio.IIOException: Can't create output stream!
at javax.imageio.ImageIO.write(Unknown Source)
Caused by: javax.imageio.IIOException: Can't create cache file!
at javax.imageio.ImageIO.createImageOutputStream(Unknown Source)
... 11 more
Caused by: java.io.IOException: 系統找不到指定的路徑。
at java.io.WinNTFileSystem.createFileExclusively(Native Method)
at java.io.File.checkAndCreate(Unknown Source)
at java.io.File.createTempFile0(Unknown Source)
at java.io.File.access$100(Unknown Source)
at java.io.File$1.createTempFile(Unknown Source)
at sun.misc.IOUtils.createTempFile(Unknown Source)
at javax.imageio.stream.FileCacheImageOutputStream.(Unknown Source)
at com.sun.imageio.spi.OutputStreamImageOutputStreamSpi.createOutputStreamInstance(Unknown Source)
... 12 more
透過這個異常信息,我開始追本溯源,當然就是翻看源碼了。
先看ImageIO.write內容,可以定位到是createImageOutputStream拋出了IIOException異常。
public static boolean write(RenderedImage im,
String formatName,
OutputStream output) throws IOException {
if (output == null) {
throw new IllegalArgumentException(output == null!);
}
ImageOutputStream stream = null;
try {
stream = createImageOutputStream(output);
} catch (IOException e) {
throw new IIOException(Can't create output stream!, e);
}
boolean val;
try {
val = write(im, formatName, stream);
} finally {
stream.close();
}
return val;
}
再看createImageOutputStream方法,可以定位到ImageOutputStreamSpi類的createOutputStreamInstance方法
try {
return spi.createOutputStreamInstance(output,
usecache,
getCacheDirectory());
} catch (IOException e) {
throw new IIOException(Can't create cache file!, e);
}
然後,我們定位到OutputStreamImageOutputStreamSpi的createOutputStreamInstance方法,OutputStreamImageOutputStreamSpi繼承了ImageOutputStreamSpi類
public ImageOutputStream createOutputStreamInstance(Object output,
boolean useCache,
File cacheDir)
throws IOException {
if (output instanceof OutputStream) {
OutputStream os = (OutputStream)output;
if (useCache) {
return new FileCacheImageOutputStream(os, cacheDir);
} else {
return new MemoryCacheImageOutputStream(os);
}
} else {
throw new IllegalArgumentException();
}
}
OK,關鍵的地方來了,我們繼續挖,直到挖到FileCacheImageOutputStream構造方法。
public FileCacheImageOutputStream(OutputStream stream, File cacheDir)
throws IOException {
if (stream == null) {
throw new IllegalArgumentException(stream == null!);
}
if ((cacheDir != null) && !(cacheDir.isDirectory())) {
throw new IllegalArgumentException(Not a directory!);
}
this.stream = stream;
this.cacheFile =
sun.misc.IOUtils.createTempFile(imageio, .tmp, cacheDir);
this.cache = new RandomAccessFile(cacheFile, rw);
this.closeAction = StreamCloser.createCloseAction(this);
StreamCloser.addToQueue(closeAction);
}
到這裡,我想你需要看一看該方法的javadoc了。
Constructs a FileCacheImageOutputStream that will write to a given outputStream.
A temporary file is used as a cache. If cacheDiris non-null and is a directory, the file will be created there. If it is null, the system-dependent default temporary-file directory will be used (see the documentation for File.createTempFile for details).
讓我們去看File.createTempFile方法,這時候就需要上java api幫助文檔了!
createTempFile
public static File createTempFile(String prefix,
String suffix,
File directory)
throws IOException在指定目錄中創建一個新的空文件,使用給定的前綴和後綴字符串生成其名稱。如果此方法成功返回,則可以保證:由返回的抽象路徑名表示的文件在此方法被調用之前不存在。
此方法及其所有變體都不會在虛擬機的當前調用中再次返回相同的抽象路徑名。
此方法只提供了臨時文件的部分功能。要安排自動刪除此方法創建的文件,可使用 deleteOnExit() 方法。
prefix 參數至少必須是三個字節長。建議前綴使用一個短的、有意義的字符串,比如 “hjb” 或 “mail”。suffix 參數可以為 null,在這種情況下,將使用後綴 “.tmp”。要創建新文件,可能首先要調整前綴和後綴,使其滿足底層平台的限制。如果前綴太長,則將它截斷,但前三個字符將始終保留。如果後綴太長,則將它截斷,但如果它以句點字符 (‘.’) 開始,則該句點以及後跟的前三個字符將始終保留。進行了這些調整後,通過連接前綴、五個或更多個內部生成的字符以及後綴,便生成了新文件的名稱。
如果 directory 參數為 null,則使用與系統有關的默認臨時文件目錄。默認臨時文件目錄由系統屬性 java.io.tmpdir 指定。在 UNIX 系統上,此屬性的默認值通常是 “/tmp” 或 “/var/tmp”;在 Microsoft Windows 系統上,該值通常是 “C:WINNTTEMP”。在調用 Java 虛擬機時,可為此系統屬性提供不同的值,但不保證使用程序更改此屬性會對此方法使用的臨時目錄產生影響。
參數:
prefix - 用於生成文件名的前綴字符串;必須至少是三字符長
suffix - 用於生成文件名的後綴字符串;可以為 null,在這種情況下,將使用後綴 “.tmp”
directory - 將創建的文件所在的目錄;如果使用默認臨時文件目錄,則該參數為 null
返回:
表示新建空文件的抽象路徑名
拋出:
IllegalArgumentException - 如果 prefix 參數包含的字符少於三個
IOException - 如果無法創建文件
SecurityException - 如果存在安全管理器,且其 SecurityManager.checkWrite(java.lang.String) 方法不允許創建文件
注意,這裡告訴我們去看一下windows的C:WINNTTEMP目錄。
WINNT是啥玩意,我反正是不太清楚,問問度娘:
Microsoft Windows NT(New Technology)是Microsoft在1993年推出的面向工作站、網絡服務器和大型計算機的網絡操作系統,也可做PC操作系統。它與通信服務緊密集成,基於OS/2 NT基礎編制。OS/2由微軟和IBM聯合研制,分為微軟的Microsoft OS/2 NT與IBM的IBM OS/2。協作後來不歡而散,IBM繼續向市場提供先前的OS/2版本,微軟則把自己的OS/2 NT的名稱改為Windows NT,即第一代的Windows NT 3.1。
大概可能是以上的意思。
然後,我對比了一下win7和windows server 2008的
很遺憾,沒有找到我想要的,不高興!
再回過頭來看看,發現這句關鍵字:
默認臨時文件目錄由系統屬性 java.io.tmpdir 指定
寫個程序測試下
public class Test {
public static void main(String[] args) {
System.out.println(System.getProperty(java.io.tmpdir));
}
}
順著目錄找下來,windows 2008的大概目錄應該是C:UsersAdministratorAppDataLocal,但是也找不下去,沒有找到2。
先新建一個2目錄試試,結果發現驗證碼可以輸出了!