I use xjar[https://github.com/core-lib/xjar]] to encrypt the jars. Since the way I use spring boot is lib separation, all the jars are in the lib directory Image

Spring boot default startup class is org. Springframework.. The boot loader. Launch. PropertiesLauncher, xjar extends to him, in the code is as follows:

public class XBootClassLoader extends LaunchedClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }
    private final XBootURLHandler xBootURLHandler;
    private final Object urlClassPath;
    private final Method getResource;
    private final Method getCodeSourceURL;
    private final Method getCodeSigners;

    private static volatile boolean registered = false;



    public XBootClassLoader(URL[] urls, ClassLoader parent, XDecryptor xDecryptor, XKey xKey) throws Exception {
        super(true, urls, parent);
        System.out.println("XBootClassLoader initialized with URLs:");
        Arrays.stream(urls).forEach(url -> System.out.println(" - " + url));
        this.xBootURLHandler = new XBootURLHandler(xDecryptor, xKey, this);
        this.urlClassPath = XReflection.field(URLClassLoader.class, "ucp").get(this).value();
        this.getResource = XReflection.method(urlClassPath.getClass(), "getResource", String.class).method();
        this.getCodeSourceURL = XReflection.method(getResource.getReturnType(), "getCodeSourceURL").method();
        this.getCodeSigners = XReflection.method(getResource.getReturnType(), "getCodeSigners").method();
    }

    @Override
    public URL findResource(String name) {
        URL url = super.findResource(name);
        if (url == null) {
            return null;
        }
        try {
            return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), xBootURLHandler);
        } catch (MalformedURLException e) {
            return url;
        }
    }

    @Override
    public Enumeration<URL> findResources(String name) throws IOException {
        Enumeration<URL> enumeration = super.findResources(name);
        if (enumeration == null) {
            return null;
        }
        return new XBootEnumeration(enumeration);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            System.out.println("Loading class: " + name);
            return super.findClass(name);
        } catch (ClassFormatError e) {
            String path = name.replace('.', '/').concat(".class");
            URL url = findResource(path);
            if (url == null) {
                throw new ClassNotFoundException(name, e);
            }
            try (InputStream in = url.openStream()) {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                XKit.transfer(in, bos);
                byte[] bytes = bos.toByteArray();
                Object resource = getResource.invoke(urlClassPath, path);
                URL codeSourceURL = (URL) getCodeSourceURL.invoke(resource);
                CodeSigner[] codeSigners = (CodeSigner[]) getCodeSigners.invoke(resource);
                CodeSource codeSource = new CodeSource(codeSourceURL, codeSigners);
                return defineClass(name, bytes, 0, bytes.length, codeSource);
            } catch (Throwable t) {
                throw new ClassNotFoundException(name, t);
            }
        }
    }

    private class XBootEnumeration implements Enumeration<URL> {

        private final Enumeration<URL> enumeration;

        XBootEnumeration(Enumeration<URL> enumeration) {
            this.enumeration = enumeration;
        }

        @Override
        public boolean hasMoreElements() {
            return enumeration.hasMoreElements();
        }

        @Override
        public URL nextElement() {
            URL url = enumeration.nextElement();
            if (url == null) {
                return null;
            }
            try {
                return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), xBootURLHandler);
            } catch (MalformedURLException e) {
                return url;
            }
        }

    }

}

One of the more important XBootURLHandler, XBootURLConnection respectively are defined as follows:

public class XBootURLHandler extends Handler implements XConstants {

    private final XDecryptor xDecryptor;
    private final XKey xKey;
    private final Set<String> indexes;

    public XBootURLHandler(XDecryptor xDecryptor, XKey xKey, ClassLoader classLoader) throws Exception {
        this.xDecryptor = xDecryptor;
        this.xKey = xKey;
        this.indexes = new LinkedHashSet<>();
        Enumeration<URL> resources = classLoader.getResources(XJAR_INF_DIR + XJAR_INF_IDX);
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            String url = resource.toString();
            String classpath = url.substring(0, url.lastIndexOf("!/") + 2);
            InputStream in = resource.openStream();
            InputStreamReader isr = new InputStreamReader(in);
            LineNumberReader lnr = new LineNumberReader(isr);
            String name;
            while ((name = lnr.readLine()) != null) indexes.add(classpath + name);
        }
    }

    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        URLConnection urlConnection = super.openConnection(url);
        return indexes.contains(url.toString())
                && urlConnection instanceof JarURLConnection
                ? new XBootURLConnection((JarURLConnection) urlConnection, xDecryptor, xKey)
                : urlConnection;
    }

}
public class XBootURLConnection extends JarURLConnection {

    private final JarURLConnection jarURLConnection;
    private final XDecryptor xDecryptor;
    private final XKey xKey;

    public XBootURLConnection(JarURLConnection jarURLConnection, XDecryptor xDecryptor, XKey xKey) throws MalformedURLException {
        super(jarURLConnection.getURL());
        this.jarURLConnection = jarURLConnection;
        this.xDecryptor = xDecryptor;
        this.xKey = xKey;
    }

    @Override
    public void connect() throws IOException {
        jarURLConnection.connect();
    }

    @Override
    public int getConnectTimeout() {
        return jarURLConnection.getConnectTimeout();
    }

    @Override
    public void setConnectTimeout(int timeout) {
        jarURLConnection.setConnectTimeout(timeout);
    }

    @Override
    public int getReadTimeout() {
        return jarURLConnection.getReadTimeout();
    }

    @Override
    public void setReadTimeout(int timeout) {
        jarURLConnection.setReadTimeout(timeout);
    }

    @Override
    public URL getURL() {
        return jarURLConnection.getURL();
    }

    @Override
    public int getContentLength() {
        return jarURLConnection.getContentLength();
    }

    @Override
    public long getContentLengthLong() {
        return jarURLConnection.getContentLengthLong();
    }

    @Override
    public String getContentType() {
        return jarURLConnection.getContentType();
    }

    @Override
    public String getContentEncoding() {
        return jarURLConnection.getContentEncoding();
    }

    @Override
    public long getExpiration() {
        return jarURLConnection.getExpiration();
    }

    @Override
    public long getDate() {
        return jarURLConnection.getDate();
    }

    @Override
    public long getLastModified() {
        return jarURLConnection.getLastModified();
    }

    @Override
    public String getHeaderField(String name) {
        return jarURLConnection.getHeaderField(name);
    }

    @Override
    public Map<String, List<String>> getHeaderFields() {
        return jarURLConnection.getHeaderFields();
    }

    @Override
    public int getHeaderFieldInt(String name, int Default) {
        return jarURLConnection.getHeaderFieldInt(name, Default);
    }

    @Override
    public long getHeaderFieldLong(String name, long Default) {
        return jarURLConnection.getHeaderFieldLong(name, Default);
    }

    @Override
    public long getHeaderFieldDate(String name, long Default) {
        return jarURLConnection.getHeaderFieldDate(name, Default);
    }

    @Override
    public String getHeaderFieldKey(int n) {
        return jarURLConnection.getHeaderFieldKey(n);
    }

    @Override
    public String getHeaderField(int n) {
        return jarURLConnection.getHeaderField(n);
    }

    @Override
    public Object getContent() throws IOException {
        return jarURLConnection.getContent();
    }

    @Override
    public Object getContent(Class[] classes) throws IOException {
        return jarURLConnection.getContent(classes);
    }

    @Override
    public Permission getPermission() throws IOException {
        return jarURLConnection.getPermission();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        InputStream in = jarURLConnection.getInputStream();
        //class解密
        return xDecryptor.decrypt(xKey, in);
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        OutputStream out = jarURLConnection.getOutputStream();
        System.out.println("getOutputStream......");
        return null;
    }

    @Override
    public String toString() {
        return jarURLConnection.toString();
    }

    @Override
    public boolean getDoInput() {
        return jarURLConnection.getDoInput();
    }

    @Override
    public void setDoInput(boolean doInput) {
        jarURLConnection.setDoInput(doInput);
    }

    @Override
    public boolean getDoOutput() {
        return jarURLConnection.getDoOutput();
    }

    @Override
    public void setDoOutput(boolean doOutput) {
        jarURLConnection.setDoOutput(doOutput);
    }

    @Override
    public boolean getAllowUserInteraction() {
        return jarURLConnection.getAllowUserInteraction();
    }

    @Override
    public void setAllowUserInteraction(boolean allowUserInteraction) {
        jarURLConnection.setAllowUserInteraction(allowUserInteraction);
    }

    @Override
    public boolean getUseCaches() {
        return jarURLConnection.getUseCaches();
    }

    @Override
    public void setUseCaches(boolean useCaches) {
        jarURLConnection.setUseCaches(useCaches);
    }

    @Override
    public long getIfModifiedSince() {
        return jarURLConnection.getIfModifiedSince();
    }

    @Override
    public void setIfModifiedSince(long ifModifiedSince) {
        jarURLConnection.setIfModifiedSince(ifModifiedSince);
    }

    @Override
    public boolean getDefaultUseCaches() {
        return jarURLConnection.getDefaultUseCaches();
    }

    @Override
    public void setDefaultUseCaches(boolean defaultUseCaches) {
        jarURLConnection.setDefaultUseCaches(defaultUseCaches);
    }

    @Override
    public void setRequestProperty(String key, String value) {
        jarURLConnection.setRequestProperty(key, value);
    }

    @Override
    public void addRequestProperty(String key, String value) {
        jarURLConnection.addRequestProperty(key, value);
    }

    @Override
    public String getRequestProperty(String key) {
        return jarURLConnection.getRequestProperty(key);
    }

    @Override
    public Map<String, List<String>> getRequestProperties() {
        return jarURLConnection.getRequestProperties();
    }

    @Override
    public URL getJarFileURL() {
        return jarURLConnection.getJarFileURL();
    }

    @Override
    public String getEntryName() {
        return jarURLConnection.getEntryName();
    }

    @Override
    public JarFile getJarFile() throws IOException {
        return jarURLConnection.getJarFile();
    }

    @Override
    public Manifest getManifest() throws IOException {
        return jarURLConnection.getManifest();
    }

    @Override
    public JarEntry getJarEntry() throws IOException {
        return jarURLConnection.getJarEntry();
    }

    @Override
    public Attributes getAttributes() throws IOException {
        return jarURLConnection.getAttributes();
    }

    @Override
    public Attributes getMainAttributes() throws IOException {
        return jarURLConnection.getMainAttributes();
    }

    @Override
    public Certificate[] getCertificates() throws IOException {
        return jarURLConnection.getCertificates();
    }

If the encryption class at start-up time will go in the start-up jar org.springframework.boot.loader.net.protocol.jar.JarUrlClassLoader#loadClass method, XBootClas will call at this time findClass in sLoader loads the class, and since the class is already encrypted, it raises a ClassFormatError exception. The following code handles the class:

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            System.out.println("Loading class: " + name);
            return super.findClass(name);
        } catch (ClassFormatError e) {
            String path = name.replace('.', '/').concat(".class");
            URL url = findResource(path);
            if (url == null) {
                throw new ClassNotFoundException(name, e);
            }
            //System.out.println("查找资源:" + name);
            try (InputStream in = url.openStream()) {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                XKit.transfer(in, bos);
                byte[] bytes = bos.toByteArray();
                Object resource = getResource.invoke(urlClassPath, path);
                URL codeSourceURL = (URL) getCodeSourceURL.invoke(resource);
                CodeSigner[] codeSigners = (CodeSigner[]) getCodeSigners.invoke(resource);
                CodeSource codeSource = new CodeSource(codeSourceURL, codeSigners);
                return defineClass(name, bytes, 0, bytes.length, codeSource);
            } catch (Throwable t) {
                throw new ClassNotFoundException(name, t);
            }
        }
    }

    @Override
    public URL findResource(String name) {
        URL url = super.findResource(name);
        if (url == null) {
            return null;
        }
        try {
            return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), xBootURLHandler);
        } catch (MalformedURLException e) {
            return url;
        }
    }   

To add a custom URL URLStreamHandler, namely xBootURLHandler, eventually in the org. Springframework. Core. The classreading. SimpleMetadataReader# getC The lassReader method calls resource.getInputStream()

    private static ClassReader getClassReader(Resource resource) throws IOException {
        try (InputStream is = resource.getInputStream()) {
            try {
                return new ClassReader(is);
            }
            catch (IllegalArgumentException ex) {
                throw new ClassFormatException("ASM ClassReader failed to parse class file - " +
                        "probably due to a new Java class file version that is not supported yet. " +
                        "Consider compiling with a lower '-target' or upgrade your framework version. " +
                        "Affected class: " + resource, ex);
            }
        }
    }

Then call org. Springframework. Core. IO. UrlResource# getInputStream

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection con = this.url.openConnection();
        customizeConnection(con);
        try {
            return con.getInputStream();
        }
        catch (IOException ex) {
            // Close the HTTP connection (if applicable).
            if (con instanceof HttpURLConnection httpConn) {
                httpConn.disconnect();
            }
            throw ex;
        }
    }

In this method, if it is a class in the main program, a custom XBootURLHandler is created for it and then called

    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        URLConnection urlConnection = super.openConnection(url);
        return indexes.contains(url.toString())
                && urlConnection instanceof JarURLConnection
                ? new XBootURLConnection((JarURLConnection) urlConnection, xDecryptor, xKey)
                : urlConnection;
    }

Call the decryption method in the XBootURLConnection to decrypt the class. The above method is valid for the class in the main program. If the jar in the lib directory is used, Won't go cn. Iocoder. Yudao. Loader. Guide. The module. The boot. XBootClassLoader# findClass method, can lead to getClassReader

    private static ClassReader getClassReader(Resource resource) throws IOException {
        try (InputStream is = resource.getInputStream()) {
            try {
                return new ClassReader(is);
            }
            catch (IllegalArgumentException ex) {
                throw new ClassFormatException("ASM ClassReader failed to parse class file - " +
                        "probably due to a new Java class file version that is not supported yet. " +
                        "Consider compiling with a lower '-target' or upgrade your framework version. " +
                        "Affected class: " + resource, ex);
            }
        }
    }

Method throws an exception directly, refer to: https://github.com/spring-projects/spring-framework/issues/27691 Set spring. Classformat. Ignore is true, and there is not much, because a jar of lib directory under the class + mapper. Do XML encryption, the load mapper. The XML directly when error lead to project operation to stop, quit, so I want to here Do you know how to create a custom URLStreamHandler for class loading in a jar

Comment From: philwebb

This issue should be closed as a duplicate of https://github.com/spring-projects/spring-boot/issues/44029

Comment From: fendo8888

What's going on? Turn this off, this is not an xjar problem, I'm asking how about in spring How do I load a custom URLStreamHandler for jars in the lib directory

Comment From: philwebb

I'm asking how about in spring How do I load a custom URLStreamHandler for jars in the lib directory

I'm sorry, but I don't understand the question or how it relates to a Spring Boot project that isn't using xjar. Perhaps you can provide a sample project that isn't using xjar that reproduces the problem?