习惯了使用Spring的IoC开发JavaEE应用之后,总想着在JavaFX开发中使用IoC管理应用中的单例对象,这里记录一下构建JavaFX.IoC实现Bean管理和依赖注入的过程。
1. IoC.需求
实际上关于JavaFX整合SpringBoot是有开源项目实现过的,之前也介绍过,但总感觉太重了,而且打包之后的体积会大很多,其实构建一个JavaFX的简答IoC功能不是很复杂。
在JavaFX开发中,用到单例对象的场景其实也不少,举一个简单的例子:
在上面这个例子中,菜单栏和快捷键基本上会用到相同的事件处理器(暂且称之为Action管理器)。而且这个Action管理器是无状态的,这种情况下将Action管理器声明为单例是很合适的。
使用时,直接在菜单栏和快捷键的Controller中注入Action管理器对象。
2. IoC.使用
上面例子中,我需要定义一个Action管理器Bean,并且将这个Bean注入到菜单栏和快捷键的Controller对象中。
所以这三个对象都需要注册为Bean,在项目启动时使用IoC提供的registerFromConstructor方法在IoC容器中注册相应类型的单例对象:
registerFromConstructor(Actions.class); registerFromConstructor(MenuBarCTL.class); registerFromConstructor(ShortcutBarCTL.class);
通过一个``注解标注Controller需要注入相应的Bean对象。
public class MenuBarCTL implements Initializable { private final Actions actions; @Injector public MenuBarCTL(Actions actions) { this.actions = actions; } }
或
public class ShortcutBarCTL implements Initializable { private final Actions actions; private final BeanFactory beanFactory; @Injector public ShortcutBarCTL(BeanFactory beanFactory, Actions actions) { this.beanFactory = beanFactory; this.actions = actions; } }
3. Ioc.实现
这里实现Ioc比较简单,只是通过构造函数,或绑定对象,或使用已存在对象的某个属性值注册Bean,实现过程具体看下面完整的代码,这里不再阐述:
public abstract class BeanFactory { /** * 已经创建Bean实例的集合 */ private final Map<Class<?>, Object> instances; /** * 通过获取其他对象注入创建Bean集合(通过获取已创建的bean的某个属性注入到需要创建的Bean中) */ private final Map<Class<?>, Supplier<Object>> instancesLazyFromGetter; public BeanFactory() { instances = new ConcurrentHashMap<>(); instancesLazyFromGetter = new ConcurrentHashMap<>(); } /** * 重写注册对应的Bean */ protected abstract void register() throws BeanInstantiationException; /** * 通过构造函数注册Bean * * @param t * @param <T> registerFromConstructor(Bean.class) */ public <T> void registerFromConstructor(Class<T> t) throws BeanInstantiationException { registerFromInstance(t, createBeanFromConstructor(t)); } /** * 通过对象注册Bean * * @param t * @param instance * @param <T> registerFromConstructor(Bean.class, new Bean()) */ public <T> void registerFromInstance(Class<T> t, Object instance) { instances.put(t, instance); } /** * 通过Getter延迟拉取对象注册成Bean(Getter获取的对象可能是延迟构建的,所以需要延迟注册) * * @param t * @param getter * @param <T> registerFromConstructor(Bean.class, otherBean::getBean) */ public <T> void registerFromGetter(Class<T> t, Supplier<T> getter) { instancesLazyFromGetter.put(t, (Supplier<Object>) getter); } /** * 通过Getter延迟拉取对象注册成Bean(Getter获取的对象可能是延迟构建的,所以需要延迟注册) * * @param t * @param s * @param getter * @param <T> * @param <S> registerFromConstructor(Bean.class, OtherBean.class, otherBean -> * otherBean::getBean) */ public <T, S> void registerFromGetter(Class<T> t, Class<S> s, Function<S, T> getter) { instancesLazyFromGetter.put(t, () -> getter.apply(getBean(s))); } /** * 获取单例对象 * * @param t 获取对象的类 * @param <T> * @return */ public <T> T getBean(Class<T> t) { Supplier<?> getter = instancesLazyFromGetter.get(t); return (T) (Objects.nonNull(getter) ? getter.get() : instances.get(t)); } /** * 通过构造函数创建Bean(非单例) * * @param t * @param <T> * @return * @throws BeanInstantiationException */ public <T> T createBean(Class<T> t) { try { return createBeanFromConstructor(t); } catch (BeanInstantiationException e) { throw new RuntimeException(e); } } /** * 通过构造函数创建Bean * * @param t * @param <T> * @return * @throws BeanInstantiationException */ protected <T> T createBeanFromConstructor(Class<T> t) throws BeanInstantiationException { Constructor<T>[] cons = (Constructor<T>[]) t.getConstructors(); // 可以构造对象的构造函数(优先使用@Injector的构造函数,没有则使用无参构造函数) Constructor<T> constructor = null; for (Constructor<T> c : cons) { if (c.getParameterCount() == 0) { constructor = c; } else if (c.isAnnotationPresent(Injector.class)) { constructor = c; break; } } if (Objects.isNull(constructor)) { throw new BeanInstantiationException(t.getName() + "没有使用@Injector注解构造函数,也没有无参的构造函数"); } try { return constructor.newInstance(Arrays.stream(constructor.getParameterTypes()).map(this::getBean).toArray()); } catch (Exception e) { throw new BeanInstantiationException(e.getMessage(), e); } } }
4. IoC.JavaFX.联动
上面的IoC实现实际上只是一个普通的Bean单例管理容器,想要在JavaFX使用这个Bean容器还要利用JavaFX的FXMLLoader来实现Controller的Bean注入。
首先,在JavaFX项目启动时,构建IoC.Bean管理容器:
public void run(Application app, Stage stage) throws BeanInstantiationException { Parent root = CTL.fxLoad(new IDEBeanFactory(app, stage), ApplicationCTL.class); stage.setScene(new Scene(root)); stage.setMaximized(true); stage.show(); }
通过new IDEBeanFactory
创建IoC.Bean容器。
然后,在Bean容器中按顺序注册Bean对象,注意这里需要按依赖顺序注册Bean,且不支持循环依赖:
@Override protected void register() throws BeanInstantiationException { registerFromInstance(BeanFactory.class, this); registerFromInstance(BuilderFactory.class, new CTLBuilderFactory(this)); registerFromInstance(Application.class, app); registerFromInstance(Stage.class, stage); registerFromConstructor(Actions.class); registerFromConstructor(ApplicationCTL.class); registerFromConstructor(MenuBarCTL.class); registerFromConstructor(ShortcutBarCTL.class); registerFromConstructor(DescriptionCTL.class); }
重写BeanFactory的register()方法,完成Bean的注册。
最后,FXMLLoader加载FXML绑定Controller的时候,将Controller的构建委托给BeanFactory:
public static Parent fxLoad(BeanFactory bf, Class<?> ct) { URL resource = fxURL(ct); FXMLLoader fxLoader = new FXMLLoader(); fxLoader.setLocation(resource); fxLoader.setResources(I18n.getResources()); fxLoader.setBuilderFactory(bf.getBean(BuilderFactory.class)); fxLoader.setControllerFactory(bf::getBean); try { return fxLoader.load(); } catch (IOException e) { throw new RuntimeException("fxLoader加载失败:" + resource.getFile(), e); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)