习惯了使用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);
    }
}
 
                     
                    
                 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号