Spring Boot打包JAR或WAR后,Mybatis的Mapper无法扫描

https://github.com/mybatis/mybatis-3/issues/325

https://github.com/StripesFramework/stripes/issues/35

MyBatis扫描通过VFS来实现

在Spring Boot中,由于是嵌套Jar,导致Mybatis默认的VFS实现DefaultVFS无法扫描嵌套Jar中的类。

解决办法,实现自定义的VFS,参考DefaultVFS增加对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;

}

}

你可能感兴趣的:(Spring Boot打包JAR或WAR后,Mybatis的Mapper无法扫描)