Es wird versucht, Abhängigkeiten zur Laufzeit mithilfe von Apache Aether dynamisch herunterzuladen und zu laden, aber Sp
Posted: 13 Jan 2025, 09:25
Ich arbeite an der Optimierung einer Spring Boot-Anwendung, indem ich Abhängigkeiten zur Laufzeit herunterlade, anstatt sie in die endgültige JAR-Datei zu packen. Obwohl ich den Download-Mechanismus erfolgreich implementiert habe, erkennt Spring Boot diese dynamisch geladenen Abhängigkeiten nicht, obwohl sie über den Java ClassLoader zugänglich sind.
Ziel:
Reduzieren Sie die endgültige JAR-Größe durch:
Ich habe online nach möglichen Lösungen gesucht, aber keine gefunden. Ich habe zuvor bei der Entwicklung von Minecraft-Plugins mit Libby etwas Ähnliches mit Core Java gehandhabt, bin mir aber nicht sicher, ob das mit Spring Boot machbar ist. Obwohl ich KI-Tools verwendet habe, um einige Abhängigkeitsprobleme zu lösen, stecke ich jetzt an diesem Punkt fest.
Meine aktuelle Implementierung
DependancyLoader.java
Booter.java
Hauptklasse
Protokoll der Startkonsole
Java konnte JDA laden (eine der Bibliotheken erfolgreich)
aber es wirft die Klasse aus, die später aus irgendeinem Grund nicht gefunden wurde.
https://paste.hapangama.com/egewawovif.properties
Pom.xml
Ziel:
Reduzieren Sie die endgültige JAR-Größe durch:
- Abhängigkeiten aus dem kompilierten JAR ausschließen
- Erforderliche Abhängigkeiten herunterladen unter Laufzeit
- Dynamisches Laden mit URLClassLoader
Ich habe online nach möglichen Lösungen gesucht, aber keine gefunden. Ich habe zuvor bei der Entwicklung von Minecraft-Plugins mit Libby etwas Ähnliches mit Core Java gehandhabt, bin mir aber nicht sicher, ob das mit Spring Boot machbar ist. Obwohl ich KI-Tools verwendet habe, um einige Abhängigkeitsprobleme zu lösen, stecke ich jetzt an diesem Punkt fest.
Meine aktuelle Implementierung
DependancyLoader.java
Code: Select all
package com.hapangama.sunlicense.boot;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.collection.DependencySelector;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
import org.eclipse.aether.util.graph.selector.ScopeDependencySelector;
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
import org.springframework.stereotype.Service;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@Service
public class DependencyLoader {
private static final Logger LOGGER = Logger.getLogger(DependencyLoader.class.getName());
private static final String DEPENDENCIES_DIR = "BOOT-INF/lib";
public static void initializeDependencies() throws Exception {
// Create libs directory if it doesn't exist
File libsDir = new File(DEPENDENCIES_DIR);
if (!libsDir.exists()) {
libsDir.mkdirs();
}
// Initialize Maven components
RepositorySystem system = Booter.newRepositorySystem();
DefaultRepositorySystemSession session = Booter.newRepositorySystemSession(system);
session.setLocalRepositoryManager(
system.newLocalRepositoryManager(
session,
new LocalRepository(libsDir.getAbsolutePath())
)
);
// Define repositories
List repositories = Arrays.asList(
new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/")
.setPolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_DAILY, RepositoryPolicy.CHECKSUM_POLICY_WARN))
.build(),
new RemoteRepository.Builder("spring-releases", "default", "https://repo.spring.io/release")
.setPolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_DAILY, RepositoryPolicy.CHECKSUM_POLICY_WARN))
.build(),
new RemoteRepository.Builder("jcenter", "default", "https://jcenter.bintray.com")
.setPolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_DAILY, RepositoryPolicy.CHECKSUM_POLICY_WARN))
.build(),
new RemoteRepository.Builder("vaadin-addons", "default", "https://maven.vaadin.com/vaadin-addons")
.setPolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_DAILY, RepositoryPolicy.CHECKSUM_POLICY_WARN))
.build()
);
// Define dependencies
List dependencies = Arrays.asList(
// Spring Boot dependencies
new Dependency(new DefaultArtifact("org.springframework.boot:spring-boot-starter-data-jpa:2.5.4"), "runtime"),
new Dependency(new DefaultArtifact("org.springframework.boot:spring-boot-starter-security:2.5.4"), "runtime"),
// Vaadin and related dependencies
new Dependency(new DefaultArtifact("com.vaadin:vaadin-spring-boot-starter:24.0.0"), "runtime"),
new Dependency(new DefaultArtifact("in.virit:viritin:2.10.1"), "runtime"),
new Dependency(new DefaultArtifact("com.github.appreciated:apexcharts:24.0.1"), "runtime"),
new Dependency(new DefaultArtifact("org.parttio:starpass-theme:1.0.4"), "runtime"),
new Dependency(new DefaultArtifact("org.vaadin.crudui:crudui:7.1.2"), "runtime"),
// Database
new Dependency(new DefaultArtifact("com.h2database:h2:2.1.214"), "runtime"),
// Utility libraries
new Dependency(new DefaultArtifact("org.modelmapper:modelmapper:3.2.0"), "runtime"),
// Discord integration
new Dependency(new DefaultArtifact("net.dv8tion:JDA:5.2.1"), "runtime")
);
// Create collection request
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRepositories(repositories);
dependencies.forEach(collectRequest::addDependency);
// Resolve dependencies
DependencyResult result = resolveDependencies(system, session, collectRequest);
// Get resolved artifact files
List artifactFiles = getArtifactFiles(result);
// Create and set up the custom ClassLoader
URL[] urls = artifactFiles.stream()
.map(file -> {
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
LOGGER.warning("Failed to convert file to URL: " + file);
return null;
}
})
.filter(Objects::nonNull)
.toArray(URL[]::new);
URLClassLoader classLoader = new URLClassLoader(urls, DependencyLoader.class.getClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
// Verify critical dependencies
verifyDependencies(classLoader);
}
private static DependencyResult resolveDependencies(RepositorySystem system,
RepositorySystemSession session,
CollectRequest collectRequest) throws Exception {
CollectResult collectResult = system.collectDependencies(session, collectRequest);
DependencyRequest dependencyRequest = new DependencyRequest(collectResult.getRoot(),
DependencyFilterUtils.classpathFilter("compile", "runtime"));
return system.resolveDependencies(session, dependencyRequest);
}
private static List getArtifactFiles(DependencyResult result) {
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
result.getRoot().accept(nlg);
return nlg.getArtifacts(false).stream()
.map(Artifact::getFile)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private static void verifyDependencies(ClassLoader classLoader) {
try {
// Verify JDA
Class.forName("net.dv8tion.jda.api.hooks.ListenerAdapter", true, classLoader);
LOGGER.info("JDA dependency loaded successfully");
// Verify Spring Boot
Class.forName("org.springframework.boot.SpringApplication", true, classLoader);
LOGGER.info("Spring Boot dependency loaded successfully");
// Add other critical dependency verifications as needed
} catch (ClassNotFoundException e) {
LOGGER.severe("Failed to verify critical dependency: " + e.getMessage());
throw new RuntimeException("Critical dependency verification failed", e);
}
}
}
Code: Select all
package com.hapangama.sunlicense.boot;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.listener.ChainedRepositoryListener;
import org.eclipse.aether.util.listener.ChainedTransferListener;
import java.io.File;
// Booter.java
public class Booter {
public static RepositorySystem newRepositorySystem() {
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
return locator.getService(RepositorySystem.class);
}
public static DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system) {
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepo = new LocalRepository("target/local-repo");
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
session.setTransferListener(new ChainedTransferListener());
session.setRepositoryListener(new ChainedRepositoryListener());
return session;
}
}
Code: Select all
package com.hapangama.sunlicense;
import com.hapangama.sunlicense.boot.DependencyLoader;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
@Theme(value = "sun")
@SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class)
public class SunLicenseApplication implements AppShellConfigurator {
public static void main(String[] args) {
try {
// Initialize dependencies before starting Spring
DependencyLoader.initializeDependencies();
// Get the context class loader that has our dependencies
ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader();
// Create Spring application
SpringApplication app = new SpringApplication(SunLicenseApplication.class);
Properties properties = new Properties();
properties.put("spring.main.allow-bean-definition-overriding", "true");
properties.put("spring.main.allow-circular-references", "true");
app.setDefaultProperties(properties);
// Important: Set both resource loader and context class loader
app.setResourceLoader(new DefaultResourceLoader(customClassLoader));
Thread.currentThread().setContextClassLoader(customClassLoader);
ConfigurableApplicationContext context = app.run(args);
if (context != null && context.isActive()) {
System.out.println("Application started successfully");
System.out.println("Active profiles: " + Arrays.toString(context.getEnvironment().getActiveProfiles()));
}
} catch (Exception e) {
System.out.println("Failed to start application");
e.printStackTrace();
System.exit(1);
}
}
}
Java konnte JDA laden (eine der Bibliotheken erfolgreich)
Code: Select all
INFO: JDA dependency loaded successfully
Code: Select all
Caused by: java.lang.ClassNotFoundException: net.dv8tion.jda.api.hooks.ListenerAdapter
Pom.xml
Code: Select all
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.3.5
com.hapangama
SunLicense
0.0.1-SNAPSHOT
SunLicense
SunLicense
17
24.5.4
Vaadin Directory
https://maven.vaadin.com/vaadin-addons
false
org.springframework.boot
spring-boot-starter-data-jpa
provided
org.springframework.boot
spring-boot-starter-security
provided
com.vaadin
vaadin-spring-boot-starter
provided
com.h2database
h2
provided
org.projectlombok
lombok
true