You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在其他Java应用中启动无main方法的Spring Boot+Wicket8.0应用并实现插件化加载?

Got it, let's break down how to build this architecture step by step. I’ve worked on similar plugin-based systems before, so I’ll walk you through each component to replicate that Minecraft-style plugin loading while integrating Spring Boot, Spring Security, and Wicket 8.


1. 整体 Architecture Overview

Here's how the pieces fit together:

  • Core Application: Acts as the plugin container, running Spring Boot + Spring Security + Wicket 8. It handles scanning, loading, and managing plugins.
  • Plugins: Independent JAR files in the /plugins directory, each with a plugin.yml metadata file. Plugins must extend a base class and implement lifecycle methods (onEnable/onDisable).
  • Bootstrap: A way to start the core app without a dedicated main method (using Spring's application context).

2. Define the Plugin Base Class (Like Bukkit's JavaPlugin)

First, create a base abstract class that all plugins must extend. This enforces the lifecycle methods and gives plugins access to the Spring context.

import org.springframework.context.ApplicationContext;

public abstract class WicketPlugin {
    private ApplicationContext applicationContext;

    // Called when the plugin is loaded and ready
    public abstract void onEnable();

    // Called when the core app is shutting down
    public abstract void onDisable();

    // Inject Spring context (managed by core app)
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    // Helper to access Spring beans from the plugin
    protected ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

3. Build the Plugin Loader (Minecraft-Style Scanning)

Create a Spring-managed component that scans the /plugins directory, reads plugin.yml from each JAR, loads the plugin class, and triggers its lifecycle.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component
public class PluginLoader {
    private static final Logger log = LoggerFactory.getLogger(PluginLoader.class);
    private static final String PLUGIN_DIR = "./plugins";
    private final List<WicketPlugin> loadedPlugins = new ArrayList<>();
    private final ApplicationContext applicationContext;

    public PluginLoader(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    // Scan and load all valid plugins
    public void loadPlugins() {
        File pluginDir = new File(PLUGIN_DIR);
        if (!pluginDir.exists() && !pluginDir.mkdirs()) {
            log.error("Failed to create /plugins directory");
            return;
        }

        File[] pluginJars = pluginDir.listFiles((dir, name) -> name.endsWith(".jar"));
        if (pluginJars == null || pluginJars.length == 0) {
            log.info("No plugins found in {}", PLUGIN_DIR);
            return;
        }

        for (File jarFile : pluginJars) {
            loadSinglePlugin(jarFile);
        }
    }

    // Load a single plugin JAR
    private void loadSinglePlugin(File jarFile) {
        try (URLClassLoader classLoader = new URLClassLoader(new URL[]{jarFile.toURI().toURL()}, getClass().getClassLoader())) {
            // Read plugin.yml from the JAR root
            InputStream yamlStream = classLoader.getResourceAsStream("plugin.yml");
            if (yamlStream == null) {
                log.error("Missing plugin.yml in {}", jarFile.getName());
                return;
            }

            Yaml yaml = new Yaml();
            Map<String, Object> pluginMeta = yaml.load(yamlStream);
            String mainClassName = (String) pluginMeta.get("main");

            // Validate and instantiate the plugin class
            Class<?> pluginClass = classLoader.loadClass(mainClassName);
            if (!WicketPlugin.class.isAssignableFrom(pluginClass)) {
                log.error("Main class {} does not extend WicketPlugin", mainClassName);
                return;
            }

            WicketPlugin plugin = (WicketPlugin) pluginClass.getDeclaredConstructor().newInstance();
            plugin.setApplicationContext(applicationContext);
            plugin.onEnable();
            loadedPlugins.add(plugin);

            log.info("Loaded plugin: {} v{}", pluginMeta.getOrDefault("name", jarFile.getName()), pluginMeta.get("version"));
        } catch (Exception e) {
            log.error("Failed to load plugin {}", jarFile.getName(), e);
        }
    }

    // Clean up plugins on shutdown
    public void unloadPlugins() {
        loadedPlugins.forEach(plugin -> {
            try {
                plugin.onDisable();
            } catch (Exception e) {
                log.error("Error disabling plugin", e);
            }
        });
        loadedPlugins.clear();
    }
}

4. Integrate Spring Boot + Wicket 8.0

Configure Wicket to work with Spring, and set up the core app to start Wicket and plugins.

Wicket Spring Application Config

import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.springframework.context.ApplicationContext;

public class WicketWebApp extends WebApplication {
    private final ApplicationContext springContext;

    public WicketWebApp(ApplicationContext springContext) {
        this.springContext = springContext;
    }

    @Override
    public Class<? extends Page> getHomePage() {
        return HomePage.class; // Replace with your home page
    }

    @Override
    protected void init() {
        super.init();
        // Enable Spring dependency injection for Wicket components
        getComponentInstantiationListeners().add(new SpringComponentInjector(this, springContext));
        // Mount pages (plugins can add more pages later)
        mountPage("/login", LoginPage.class);
    }
}

Core Spring Boot Config (No Main Method)

import org.apache.wicket.protocol.http.WicketServlet;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@SpringBootConfiguration
@EnableAutoConfiguration
@EnableWebSecurity
@EnableAspectJAutoProxy
public class CoreApp {
    private final PluginLoader pluginLoader;

    public CoreApp(PluginLoader pluginLoader) {
        this.pluginLoader = pluginLoader;
    }

    @Bean
    public WebApplication wicketWebApp(org.springframework.context.ApplicationContext springContext) {
        return new WicketWebApp(springContext);
    }

    @Bean
    public ServletRegistrationBean<WicketServlet> wicketServletRegistration(WebApplication wicketApp) {
        WicketServlet servlet = new WicketServlet();
        servlet.setApplication(wicketApp);
        ServletRegistrationBean<WicketServlet> registration = new ServletRegistrationBean<>(servlet, "/*");
        registration.setLoadOnStartup(1);
        return registration;
    }

    // Load plugins after Spring context is ready
    @PostConstruct
    public void startPlugins() {
        pluginLoader.loadPlugins();
    }

    // Unload plugins before shutting down
    @PreDestroy
    public void stopPlugins() {
        pluginLoader.unloadPlugins();
    }
}

5. Spring Security Integration

Add a security config to protect your Wicket pages:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .permitAll()
            .and()
            .logout()
                .permitAll();
    }

    // Example in-memory user store (replace with your auth system)
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

6. Plugin Development Guide

Plugin Jar Requirements

  1. Root file: plugin.yml (required)
    name: ExamplePlugin
    version: 1.0.0
    main: com.example.plugin.ExamplePluginMain
    
  2. Main class: Must extend WicketPlugin
    import org.apache.wicket.Page;
    import org.apache.wicket.protocol.http.WebApplication;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class ExamplePluginMain extends WicketPlugin {
        private static final Logger log = LoggerFactory.getLogger(ExamplePluginMain.class);
    
        @Override
        public void onEnable() {
            // Access Spring context and mount a custom page
            WebApplication wicketApp = getApplicationContext().getBean(WebApplication.class);
            wicketApp.mountPage("/example", ExamplePluginPage.class);
            log.info("ExamplePlugin enabled!");
        }
    
        @Override
        public void onDisable() {
            log.info("ExamplePlugin disabled!");
        }
    }
    
  3. Package the plugin: Build a JAR with plugin.yml in the root, and all your plugin classes/resources. Drop it into the /plugins directory of the core app.

7. Start the Core App Without a Main Method

Create a bootstrap class to initialize the Spring context from another application:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AppBootstrap {
    public static void startCoreApp() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CoreApp.class);
        context.registerShutdownHook(); // Clean up on JVM exit
    }
}

Call AppBootstrap.startCoreApp() from your external application to launch the entire system.


8. Required Dependencies (pom.xml)

Add these to your core app's pom:

<!-- Spring Boot -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Wicket 8 -->
<dependency>
    <groupId>org.apache.wicket</groupId>
    <artifactId>wicket-spring-boot-starter</artifactId>
    <version>8.14.0</version>
</dependency>
<dependency>
    <groupId>org.apache.wicket</groupId>
    <artifactId>wicket-spring</artifactId>
    <version>8.14.0</version>
</dependency>

<!-- YAML parsing for plugin.yml -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>2.0</version>
</dependency>

内容的提问来源于stack exchange,提问作者ANGRY PENGUIN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:25:51