习惯了使用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);
}
}
标签:Contoller,JavaFX,param,public,Bean,registerFromConstructor,IoC,class
From: https://www.cnblogs.com/michong2022/p/17034434.html