https://github.com/mybatis/mybatis-3/issues/325
https://github.com/StripesFramework/stripes/issues/35
MyBatis掃描通過VFS來實(shí)現(xiàn)
在Spring?Boot中,由于是嵌套Jar膝昆,導(dǎo)致Mybatis默認(rèn)的VFS實(shí)現(xiàn)DefaultVFS無法掃描嵌套Jar中的類娃弓。
解決辦法,實(shí)現(xiàn)自定義的VFS廓潜,參考DefaultVFS增加對(duì)Spring?Boot嵌套JAR的處理。
package?com.qiyun.mybatis;
import?java.io.BufferedReader;
import?java.io.File;
import?java.io.FileNotFoundException;
import?java.io.IOException;
import?java.io.InputStream;
import?java.io.InputStreamReader;
import?java.io.UnsupportedEncodingException;
import?java.net.MalformedURLException;
import?java.net.URL;
import?java.net.URLEncoder;
import?java.util.ArrayList;
import?java.util.Arrays;
import?java.util.List;
import?java.util.jar.JarEntry;
import?java.util.jar.JarInputStream;
import?org.apache.ibatis.io.VFS;
import?org.apache.ibatis.logging.Log;
import?org.apache.ibatis.logging.LogFactory;
/**
*?Modified?DefaultVFS?for?handling?nested?jar.
*
*?https://github.com/mybatis/mybatis-3/issues/325
*?https://github.com/StripesFramework/stripes/issues/35
*
*?@version?1.0
*?@since?1.0
*/
public?class?SpringBootExecutableJarVFS?extends?VFS?{
private?static?final?Log?log?=?LogFactory.getLog(SpringBootExecutableJarVFS.class);
/**?The?magic?header?that?indicates?a?JAR?(ZIP)?file.?*/
private?static?final?byte[]?JAR_MAGIC?=?{?'P',?'K',?3,?4?};
@Override
public?boolean?isValid()?{
return?true;
}
@Override
public?List?list(URL?url,?String?path)?throws?IOException?{
InputStream?is?=?null;
try?{
List?resources?=?new?ArrayList();
//?First,?try?to?find?the?URL?of?a?JAR?file?containing?the?requested?resource.?If?a?JAR
//?file?is?found,?then?we'll?list?child?resources?by?reading?the?JAR.
URL?jarUrl?=?findJarForResource(url);
if?(jarUrl?!=?null)?{
is?=?jarUrl.openStream();
if?(log.isDebugEnabled())?{
log.debug("Listing?"?+?url);
}
resources?=?listResources(new?JarInputStream(is),?path);
}?else?{
List?children?=?new?ArrayList();
try?{
if?(isJar(url))?{
//?Some?versions?of?JBoss?VFS?might?give?a?JAR?stream?even?if?the?resource
//?referenced?by?the?URL?isn't?actually?a?JAR
is?=?url.openStream();
JarInputStream?jarInput?=?new?JarInputStream(is);
if?(log.isDebugEnabled())?{
log.debug("Listing?"?+?url);
}
for?(JarEntry?entry;?(entry?=?jarInput.getNextJarEntry())?!=?null;)?{
if?(log.isDebugEnabled())?{
log.debug("Jar?entry:?"?+?entry.getName());
}
children.add(entry.getName());
}
jarInput.close();
}?else?{
/*
*?Some?servlet?containers?allow?reading?from?directory?resources?like?a
*?text?file,?listing?the?child?resources?one?per?line.?However,?there?is?no
*?way?to?differentiate?between?directory?and?file?resources?just?by?reading
*?them.?To?work?around?that,?as?each?line?is?read,?try?to?look?it?up?via
*?the?class?loader?as?a?child?of?the?current?resource.?If?any?line?fails
*?then?we?assume?the?current?resource?is?not?a?directory.
*/
is?=?url.openStream();
BufferedReader?reader?=?new?BufferedReader(new?InputStreamReader(is));
List?lines?=?new?ArrayList();
for?(String?line;?(line?=?reader.readLine())?!=?null;)?{
if?(log.isDebugEnabled())?{
log.debug("Reader?entry:?"?+?line);
}
lines.add(line);
if?(getResources(path?+?"/"?+?line).isEmpty())?{
lines.clear();
break;
}
}
if?(!lines.isEmpty())?{
if?(log.isDebugEnabled())?{
log.debug("Listing?"?+?url);
}
children.addAll(lines);
}
}
}?catch?(FileNotFoundException?e)?{
/*
*?For?file?URLs?the?openStream()?call?might?fail,?depending?on?the?servlet
*?container,?because?directories?can't?be?opened?for?reading.?If?that?happens,
*?then?list?the?directory?directly?instead.
*/
if?("file".equals(url.getProtocol()))?{
File?file?=?new?File(url.getFile());
if?(log.isDebugEnabled())?{
log.debug("Listing?directory?"?+?file.getAbsolutePath());
}
if?(file.isDirectory())?{
if?(log.isDebugEnabled())?{
log.debug("Listing?"?+?url);
}
children?=?Arrays.asList(file.list());
}
}?else?{
//?No?idea?where?the?exception?came?from?so?rethrow?it
throw?e;
}
}
//?The?URL?prefix?to?use?when?recursively?listing?child?resources
String?prefix?=?url.toExternalForm();
if?(!prefix.endsWith("/"))?{
prefix?=?prefix?+?"/";
}
//?Iterate?over?immediate?children,?adding?files?and?recursing?into?directories
for?(String?child?:?children)?{
String?resourcePath?=?path?+?"/"?+?child;
resources.add(resourcePath);
URL?childUrl?=?new?URL(prefix?+?child);
resources.addAll(list(childUrl,?resourcePath));
}
}
return?resources;
}?finally?{
if?(is?!=?null)?{
try?{
is.close();
}?catch?(Exception?e)?{
//?Ignore
}
}
}
}
/**
*?List?the?names?of?the?entries?in?the?given?{@link?JarInputStream}?that?begin?with?the
*?specified?{@code?path}.?Entries?will?match?with?or?without?a?leading?slash.
*
*?@param?jar?The?JAR?input?stream
*?@param?path?The?leading?path?to?match
*?@return?The?names?of?all?the?matching?entries
*?@throws?IOException?If?I/O?errors?occur
*/
protected?List?listResources(JarInputStream?jar,?String?path)?throws?IOException?{
//?Include?the?leading?and?trailing?slash?when?matching?names
if?(!path.startsWith("/"))?{
path?=?"/"?+?path;
}
if?(!path.endsWith("/"))?{
path?=?path?+?"/";
}
//?Iterate?over?the?entries?and?collect?those?that?begin?with?the?requested?path
List?resources?=?new?ArrayList();
for?(JarEntry?entry;?(entry?=?jar.getNextJarEntry())?!=?null;)?{
if?(!entry.isDirectory())?{
//?Add?leading?slash?if?it's?missing
String?name?=?entry.getName();
if?(!name.startsWith("/"))?{
name?=?"/"?+?name;
}
//?Check?file?name
if?(name.startsWith(path))?{
if?(log.isDebugEnabled())?{
log.debug("Found?resource:?"?+?name);
}
//?Trim?leading?slash
resources.add(name.substring(1));
}
}
}
return?resources;
}
/**
*?Attempts?to?deconstruct?the?given?URL?to?find?a?JAR?file?containing?the?resource?referenced
*?by?the?URL.?That?is,?assuming?the?URL?references?a?JAR?entry,?this?method?will?return?a?URL
*?that?references?the?JAR?file?containing?the?entry.?If?the?JAR?cannot?be?located,?then?this
*?method?returns?null.
*
*?@param?url?The?URL?of?the?JAR?entry.
*?@return?The?URL?of?the?JAR?file,?if?one?is?found.?Null?if?not.
*?@throws?MalformedURLException
*/
protected?URL?findJarForResource(URL?url)?throws?MalformedURLException?{
if?(log.isDebugEnabled())?{
log.debug("Find?JAR?URL:?"?+?url);
}
if?(isNestedJar(url))?{
//?Retain?jar:?protocol?as?a?workaround?for?#325
if?(log.isDebugEnabled())?{
log.debug("It?is?a?nested?JAR:?"?+?url);
}
}?else?{
//?If?the?file?part?of?the?URL?is?itself?a?URL,?then?that?URL?probably?points?to?the?JAR
try?{
for?(;;)?{
url?=?new?URL(url.getFile());
if?(log.isDebugEnabled())?{
log.debug("Inner?URL:?"?+?url);
}
}
}?catch?(MalformedURLException?e)?{
//?This?will?happen?at?some?point?and?serves?as?a?break?in?the?loop
}
}
//?Look?for?the?.jar?extension?and?chop?off?everything?after?that
StringBuilder?jarUrl?=?new?StringBuilder(url.toExternalForm());
int?index?=?jarUrl.lastIndexOf(".jar");
if?(index?>=?0)?{
jarUrl.setLength(index?+?4);
if?(log.isDebugEnabled())?{
log.debug("Extracted?JAR?URL:?"?+?jarUrl);
}
}?else?{
if?(log.isDebugEnabled())?{
log.debug("Not?a?JAR:?"?+?jarUrl);
}
return?null;
}
//?Try?to?open?and?test?it
try?{
URL?testUrl?=?new?URL(jarUrl.toString());
if?(isJar(testUrl))?{
return?testUrl;
}?else?{
//?WebLogic?fix:?check?if?the?URL's?file?exists?in?the?filesystem.
if?(log.isDebugEnabled())?{
log.debug("Not?a?JAR:?"?+?jarUrl);
}
jarUrl.replace(0,?jarUrl.length(),?testUrl.getFile());
File?file?=?new?File(jarUrl.toString());
//?File?name?might?be?URL-encoded
if?(!file.exists())?{
try?{
file?=?new?File(URLEncoder.encode(jarUrl.toString(),?"UTF-8"));
}?catch?(UnsupportedEncodingException?e)?{
throw?new?RuntimeException("Unsupported?encoding???UTF-8???That's?unpossible.");
}
}
if?(file.exists())?{
if?(log.isDebugEnabled())?{
log.debug("Trying?real?file:?"?+?file.getAbsolutePath());
}
testUrl?=?file.toURI().toURL();
if?(isJar(testUrl))?{
return?testUrl;
}
}
}
}?catch?(MalformedURLException?e)?{
log.warn("Invalid?JAR?URL:?"?+?jarUrl);
}
if?(log.isDebugEnabled())?{
log.debug("Not?a?JAR:?"?+?jarUrl);
}
return?null;
}
protected?boolean?isNestedJar(URL?url)?{
if?(!"jar".equals(url.getProtocol()))
return?false;
String?urlStr?=?url.toExternalForm();
int?indexOfWar?=?urlStr.indexOf(".war!/");
int?indexOfJar?=?urlStr.indexOf(".jar!/");
if?(indexOfWar?!=?-1)?{?//?Executable?War
return?indexOfWar?!=?urlStr.lastIndexOf(".jar!/");
}?else?{?//?Executable?Jar
return?indexOfJar?!=?urlStr.lastIndexOf(".jar!/");
}
}
/**
*?Converts?a?Java?package?name?to?a?path?that?can?be?looked?up?with?a?call?to
*?{@link?ClassLoader#getResources(String)}.
*
*?@param?packageName?The?Java?package?name?to?convert?to?a?path
*/
protected?String?getPackagePath(String?packageName)?{
return?packageName?==?null???null?:?packageName.replace('.',?'/');
}
/**
*?Returns?true?if?the?resource?located?at?the?given?URL?is?a?JAR?file.
*
*?@param?url?The?URL?of?the?resource?to?test.
*/
protected?boolean?isJar(URL?url)?{
return?isJar(url,?new?byte[JAR_MAGIC.length]);
}
/**
*?Returns?true?if?the?resource?located?at?the?given?URL?is?a?JAR?file.
*
*?@param?url?The?URL?of?the?resource?to?test.
*?@param?buffer?A?buffer?into?which?the?first?few?bytes?of?the?resource?are?read.?The?buffer
*????????????must?be?at?least?the?size?of?{@link?#JAR_MAGIC}.?(The?same?buffer?may?be?reused
*????????????for?multiple?calls?as?an?optimization.)
*/
protected?boolean?isJar(URL?url,?byte[]?buffer)?{
InputStream?is?=?null;
try?{
is?=?url.openStream();
is.read(buffer,?0,?JAR_MAGIC.length);
if?(Arrays.equals(buffer,?JAR_MAGIC))?{
if?(log.isDebugEnabled())?{
log.debug("Found?JAR:?"?+?url);
}
return?true;
}
}?catch?(Exception?e)?{
//?Failure?to?read?the?stream?means?this?is?not?a?JAR
}?finally?{
if?(is?!=?null)?{
try?{
is.close();
}?catch?(Exception?e)?{
//?Ignore
}
}
}
return?false;
}
}