不使用Spring实现@Bean等效功能及Bean声明方法问询
我来给你几个不依赖Spring实现类似Bean声明和依赖注入的方案,都是实际项目里常用且足够优雅的思路:
这是最直接的方式,完全不用任何框架,自己控制所有实例的创建和依赖传递,核心是用构造函数注入依赖(比字段注入更清晰,还方便单元测试)。
首先把原来的Spring配置转换成普通的配置类,负责创建Bean实例:
// 类似Spring的@Configuration,负责实例化所有需要的组件 public class AppConfig { private final String apiBaseUrl; // 把配置参数通过构造函数传入,避免硬编码 public AppConfig(String apiBaseUrl) { this.apiBaseUrl = apiBaseUrl; } // 对应原来的@Bean方法,创建MyApi实例 public MyApi createMyApi() { return Feign.builder().target(MyApi.class, apiBaseUrl); } // 创建AnotherClass,同时把MyApi依赖传进去 public AnotherClass createAnotherClass(MyApi myApi) { return new AnotherClass(myApi); } }
然后在应用启动的入口类里完成所有初始化:
public class AppBootstrap { public static void main(String[] args) { // 初始化配置 AppConfig config = new AppConfig("https://your-target-api.com"); // 创建Bean实例,按依赖顺序来 MyApi myApi = config.createMyApi(); AnotherClass anotherClass = config.createAnotherClass(myApi); // 之后就可以正常使用anotherClass了 anotherClass.executeApiCall(); } }
最后修改AnotherClass,用构造函数接收依赖,去掉框架依赖:
public class AnotherClass { private final MyApi myApi; // 构造函数注入,依赖关系一目了然 public AnotherClass(MyApi myApi) { this.myApi = myApi; } public void executeApiCall() { myApi.someEndpoint(); } }
这个方案的好处是完全无依赖,代码逻辑透明,没有Spring那种“魔法”,而且单元测试时可以很轻松地传入Mock的MyApi实例。
如果你的项目里需要管理很多Bean,手动传递依赖会很繁琐,那可以自己写一个超轻量的DI容器,模拟Spring的核心功能——管理Bean的生命周期和依赖注入。
比如写一个简单的容器类:
import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; public class SimpleContainer { // 存储Bean的提供者(用于创建实例) private final Map<Class<?>, Supplier<?>> beanSuppliers = new HashMap<>(); // 存储单例Bean的实例 private final Map<Class<?>, Object> singletonBeans = new HashMap<>(); // 注册单例Bean的创建逻辑 public <T> void registerSingleton(Class<T> beanType, Supplier<T> supplier) { beanSuppliers.put(beanType, supplier); } // 获取Bean实例,单例会缓存起来 @SuppressWarnings("unchecked") public <T> T getBean(Class<T> beanType) { // 先看有没有缓存的单例 if (singletonBeans.containsKey(beanType)) { return (T) singletonBeans.get(beanType); } // 没有的话用提供者创建实例 Supplier<?> supplier = beanSuppliers.get(beanType); if (supplier == null) { throw new IllegalArgumentException("No bean registered for type: " + beanType.getName()); } T bean = (T) supplier.get(); singletonBeans.put(beanType, bean); return bean; } }
使用的时候就像这样:
public class AppBootstrap { public static void main(String[] args) { SimpleContainer container = new SimpleContainer(); // 注册MyApi的单例创建逻辑 container.registerSingleton(MyApi.class, () -> Feign.builder().target(MyApi.class, "https://your-target-api.com")); // 注册AnotherClass,依赖MyApi直接从容器获取 container.registerSingleton(AnotherClass.class, () -> new AnotherClass(container.getBean(MyApi.class))); // 获取实例使用 AnotherClass anotherClass = container.getBean(AnotherClass.class); anotherClass.executeApiCall(); } }
这个容器虽然简单,但已经能处理大部分场景,还可以扩展支持原型Bean(每次获取都创建新实例)、依赖自动解析等功能,完全可以根据自己的需求定制。
如果你觉得单例是最适合你的场景,但原来的实现不够优雅,可以把单例的创建逻辑封装到静态工厂类里,而不是直接在MyApi里写单例代码,这样更解耦,也方便测试和配置。
比如写一个工厂类:
public class MyApiFactory { private static MyApi instance; private static String apiUrl; // 初始化方法,在应用启动时调用一次 public static void init(String targetUrl) { if (instance != null) { throw new IllegalStateException("MyApiFactory has already been initialized"); } apiUrl = targetUrl; instance = Feign.builder().target(MyApi.class, apiUrl); } // 获取实例的方法 public static MyApi getInstance() { if (instance == null) { throw new IllegalStateException("MyApiFactory not initialized yet"); } return instance; } // 用于单元测试的重置方法 public static void reset() { instance = null; apiUrl = null; } }
使用的时候:
public class AppBootstrap { public static void main(String[] args) { // 先初始化工厂 MyApiFactory.init("https://your-target-api.com"); // 创建AnotherClass时传入工厂提供的实例 AnotherClass anotherClass = new AnotherClass(MyApiFactory.getInstance()); anotherClass.executeApiCall(); } }
AnotherClass还是用构造函数注入,保持依赖清晰。这个方案比传统的饿汉/懒汉单例更优雅,因为把创建逻辑集中在工厂里,还能控制初始化时机,测试时可以调用reset()方法重新初始化。
总结一下:如果项目规模小,推荐用手动依赖注入;如果Bean较多,用极简DI容器;如果就是想用单例,用静态工厂优化实现。这三个方案都不需要Spring,而且都遵循了依赖注入的核心思想——依赖倒置,代码清晰可维护。
内容的提问来源于stack exchange,提问作者Fundhor

