diff --git a/docs/performance/optimize-app.md b/docs/performance/optimize-app.md index ac164870c..56e088509 100644 --- a/docs/performance/optimize-app.md +++ b/docs/performance/optimize-app.md @@ -13,6 +13,7 @@ * `nop.auth.login.allow-create-default-user`不设置或者设置为false,启动时就不会检查数据库中是否至少存在一个用户。 * `nop.web.auto-load-dynamic-file`不设置或者设置为false, 启动时就不会自动将xjs翻译为js文件。 * `nop.graphql.eager-init-biz-object`设置为false, 启动时不会立刻解析meta文件等创建BizObject,而是访问到某个对象时再延迟创建 +* `nop.ioc.app-beans-container.concurrent-start`设置为true, 则启动时在线程池上执行bean容器的初始化操作,不阻塞主线程 ## 优化应用运行时性能 * `nop.debug` 设置为false, 就不会在dump目录下产生输出 diff --git a/nop-api-core/src/main/java/io/nop/api/core/ioc/IBeanContainer.java b/nop-api-core/src/main/java/io/nop/api/core/ioc/IBeanContainer.java index d7319ef79..98b6c0af6 100644 --- a/nop-api-core/src/main/java/io/nop/api/core/ioc/IBeanContainer.java +++ b/nop-api-core/src/main/java/io/nop/api/core/ioc/IBeanContainer.java @@ -17,6 +17,13 @@ public interface IBeanContainer extends IBeanProvider { void start(); + /** + * 异步启动时强制等待启动完毕 + */ + default void awaitStartFinished(){ + + } + void stop(); void restart(); diff --git a/nop-biz/src/main/resources/_vfs/nop/biz/beans/biz-defaults.beans.xml b/nop-biz/src/main/resources/_vfs/nop/biz/beans/biz-defaults.beans.xml index dfbb85e8e..0252a0d49 100644 --- a/nop-biz/src/main/resources/_vfs/nop/biz/beans/biz-defaults.beans.xml +++ b/nop-biz/src/main/resources/_vfs/nop/biz/beans/biz-defaults.beans.xml @@ -74,9 +74,9 @@ - + diff --git a/nop-commons/src/main/java/io/nop/commons/CommonConfigs.java b/nop-commons/src/main/java/io/nop/commons/CommonConfigs.java index f528444ad..0d639e677 100644 --- a/nop-commons/src/main/java/io/nop/commons/CommonConfigs.java +++ b/nop-commons/src/main/java/io/nop/commons/CommonConfigs.java @@ -24,7 +24,7 @@ public interface CommonConfigs { @Description("全局工作线程池的大小") IConfigReference CFG_CONCURRENT_GLOBAL_WORKER_MAX_POOL_SIZE = varRef(s_loc, - "nop.commons.concurrent.global-worker.maxPoolSize", Integer.class, 50); + "nop.commons.concurrent.global-worker.maxPoolSize", Integer.class, 30); @Description("限制StringHelper.repeat函数的参数值小于指定值,不会过大") IConfigReference CFG_UTILS_STRING_MAX_REPEAT_LEN = varRef(s_loc, "nop.commons.util.string-max-repeat-len", diff --git a/nop-demo/nop-quarkus-demo/src/main/resources/META-INF/native-image/io.nop.demo/nop-quarkus-demo/reflect-config.json b/nop-demo/nop-quarkus-demo/src/main/resources/META-INF/native-image/io.nop.demo/nop-quarkus-demo/reflect-config.json index 1bb59991f..effd02ba9 100644 --- a/nop-demo/nop-quarkus-demo/src/main/resources/META-INF/native-image/io.nop.demo/nop-quarkus-demo/reflect-config.json +++ b/nop-demo/nop-quarkus-demo/src/main/resources/META-INF/native-image/io.nop.demo/nop-quarkus-demo/reflect-config.json @@ -6558,6 +6558,29 @@ "methods": [], "name": "io.nop.integration.qrcode.ZxingQrcodeService" }, + { + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allPublicConstructors": true, + "allPublicFields": true, + "allPublicMethods": true, + "fields": [ + { + "name": "beanId" + }, + { + "name": "created" + }, + { + "name": "depends" + }, + { + "name": "threadId" + } + ], + "methods": [], + "name": "io.nop.ioc.impl.BeanContainerImpl$BeanCreation" + }, { "allDeclaredConstructors": true, "allDeclaredMethods": true, diff --git a/nop-demo/nop-quarkus-demo/src/main/resources/application.yaml b/nop-demo/nop-quarkus-demo/src/main/resources/application.yaml index b8c5cccc8..4e6f292ed 100644 --- a/nop-demo/nop-quarkus-demo/src/main/resources/application.yaml +++ b/nop-demo/nop-quarkus-demo/src/main/resources/application.yaml @@ -75,7 +75,7 @@ nop: auth: login: - allow-create-default-user: true # 如果用户表为空,则自动创建缺省账户nop, 密码nop-test + allow-create-default-user: false # 如果用户表为空,则自动创建缺省账户nop, 密码nop-test datasource: driver-class-name: org.h2.Driver @@ -84,11 +84,11 @@ nop: password: web: - validate-page-model: true + validate-page-model: false auto-load-dynamic-file: false orm: - init-database-schema: true + init-database-schema: false db-differ: auto-upgrade-database: false @@ -97,6 +97,9 @@ nop: schema-introspection: enabled: true + ioc: + app-beans-container: + concurrent-start: true # devservices 会启用testcontainers来管理测试数据库 quarkus: diff --git a/nop-demo/nop-quarkus-demo/src/main/resources/nop-vfs-index.txt b/nop-demo/nop-quarkus-demo/src/main/resources/nop-vfs-index.txt index 61c2a41fb..c8ce25630 100644 --- a/nop-demo/nop-quarkus-demo/src/main/resources/nop-vfs-index.txt +++ b/nop-demo/nop-quarkus-demo/src/main/resources/nop-vfs-index.txt @@ -296,6 +296,7 @@ /nop/core/registry/dao-wf.register-model.xml /nop/core/registry/dialect.register-model.xml /nop/core/registry/dict.register-model.xml +/nop/core/registry/fsm.register-model.xml /nop/core/registry/gateway.register-model.xml /nop/core/registry/graph-designer.register-model.xml /nop/core/registry/imp.register-model.xml @@ -433,9 +434,7 @@ /nop/dyn/model/NopDynPage/_NopDynPage.xbiz /nop/dyn/model/NopDynPage/_NopDynPage.xmeta /nop/dyn/model/NopDynPatch/NopDynPatch.xbiz -/nop/dyn/model/NopDynPatch/NopDynPatch.xmeta /nop/dyn/model/NopDynPatch/_NopDynPatch.xbiz -/nop/dyn/model/NopDynPatch/_NopDynPatch.xmeta /nop/dyn/model/NopDynPatchFile/NopDynPatchFile.xbiz /nop/dyn/model/NopDynPatchFile/NopDynPatchFile.xmeta /nop/dyn/model/NopDynPatchFile/_NopDynPatchFile.xbiz @@ -510,11 +509,6 @@ /nop/dyn/pages/NopDynPage/main.page.yaml /nop/dyn/pages/NopDynPage/picker.page.yaml /nop/dyn/pages/NopDynPage/ref-module.page.yaml -/nop/dyn/pages/NopDynPatch/NopDynPatch.lib.xjs -/nop/dyn/pages/NopDynPatch/NopDynPatch.view.xml -/nop/dyn/pages/NopDynPatch/_gen/_NopDynPatch.view.xml -/nop/dyn/pages/NopDynPatch/main.page.yaml -/nop/dyn/pages/NopDynPatch/picker.page.yaml /nop/dyn/pages/NopDynPatchFile/NopDynPatchFile.lib.xjs /nop/dyn/pages/NopDynPatchFile/NopDynPatchFile.view.xml /nop/dyn/pages/NopDynPatchFile/_gen/_NopDynPatchFile.view.xml diff --git a/nop-ioc/src/main/java/io/nop/ioc/IocConfigs.java b/nop-ioc/src/main/java/io/nop/ioc/IocConfigs.java index 9f92ff1c7..d2fe89498 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/IocConfigs.java +++ b/nop-ioc/src/main/java/io/nop/ioc/IocConfigs.java @@ -18,6 +18,9 @@ public interface IocConfigs { IConfigReference CFG_IOC_APP_BEANS_CONTAINER_START_MODE = AppConfig .varRef(s_loc, "nop.ioc.app-beans-container.start-mode", String.class, null); + @Description("是否初始化IoC容器") + IConfigReference CFG_IOC_APP_BEANS_CONCURRENT_START = AppConfig.varRef(s_loc, "nop.ioc.app-beans-container.concurrent-start", Boolean.class, false); + @Description("是否自动装载所有模块下的beans/app-*.beans.xml文件") IConfigReference CFG_IOC_APP_BEANS_FILE_ENABLED = AppConfig.varRef(s_loc, "nop.ioc.app-beans-file.enabled", Boolean.class, true); diff --git a/nop-ioc/src/main/java/io/nop/ioc/impl/BeanContainerImpl.java b/nop-ioc/src/main/java/io/nop/ioc/impl/BeanContainerImpl.java index 9d39b4b27..342df002c 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/impl/BeanContainerImpl.java +++ b/nop-ioc/src/main/java/io/nop/ioc/impl/BeanContainerImpl.java @@ -14,9 +14,14 @@ import io.nop.api.core.exceptions.NopException; import io.nop.api.core.ioc.BeanContainerStartMode; import io.nop.api.core.ioc.IBeanContainer; +import io.nop.api.core.util.FutureHelper; +import io.nop.api.core.util.ICancellable; +import io.nop.commons.concurrent.executor.GlobalExecutors; import io.nop.commons.lang.IClassLoader; +import io.nop.commons.lang.impl.Cancellable; import io.nop.commons.util.ClassHelper; import io.nop.core.lang.xml.XNode; +import io.nop.core.model.graph.TaskExecutionGraph; import io.nop.ioc.api.BeanScopeContext; import io.nop.ioc.api.IBeanContainerImplementor; import io.nop.ioc.api.IBeanDefinition; @@ -35,6 +40,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import static io.nop.ioc.IocConstants.PRODUCER_BEAN_PREFIX; @@ -62,7 +68,7 @@ public class BeanContainerImpl implements IBeanContainerImplementor { private final List orderedBeans; private volatile boolean running; - private boolean started; + private volatile boolean started; private final Map, BeanTypeMapping> beansByType = new ConcurrentHashMap<>(); private final Map, List> beansByAnnotation = new ConcurrentHashMap<>(); @@ -77,6 +83,12 @@ public class BeanContainerImpl implements IBeanContainerImplementor { private BeanContainerStartMode startMode = BeanContainerStartMode.DEFAULT; private Map aliases; + private Cancellable cancellable; + + private boolean concurrentStart; + + private CompletableFuture startFuture; + public BeanContainerImpl(String id, Map enabledBeans, Collection optionalBeans, Map aliases, IBeanContainer parentContainer) { @@ -138,6 +150,10 @@ public void setClassIntrospection(IBeanClassIntrospection classIntrospection) { this.classIntrospection = classIntrospection; } + public void setConcurrentStart(boolean concurrentStart) { + this.concurrentStart = concurrentStart; + } + public String getId() { return id; } @@ -356,12 +372,17 @@ private Object getBean0(BeanDefinition beanDef, boolean onlyProducer, boolean in if (includeCreating && beanScope != null) { Object bean = beanScope.get(beanDef.getId()); if (bean != null) { - bean = beanDef.getBeanInstance(bean, onlyProducer); - if (bean == null) - throw new NopException(ERR_IOC_PRODUCER_BEAN_NOT_INITED).param(ARG_BEAN, beanDef) - .param(ARG_BEAN_NAME, beanDef.getId()); + Object createdBean = beanDef.getBeanInstance(bean, onlyProducer); + if (createdBean == null) { + // 如果是并行启动,则允许等待一段时间 + if (concurrentStart && !started) + createdBean = ((ProducedBeanInstance) bean).awaitGetBean(10000); + if (createdBean == null) + throw new NopException(ERR_IOC_PRODUCER_BEAN_NOT_INITED).param(ARG_BEAN, beanDef) + .param(ARG_BEAN_NAME, beanDef.getId()); + } - return bean; + return createdBean; } } @@ -378,6 +399,7 @@ private Object getBean0(BeanDefinition beanDef, boolean onlyProducer, boolean in if (bean == null) { LOG.info("nop.new-bean:{}", beanDef); bean = beanDef.newObject(beanScope, this); + LOG.trace("nop.new-bean-completed:{}", beanDef); if (isStarted() && beanDef.hasDelayMethod()) { beanDef.runDelayMethod(bean, beanScope, this); } @@ -439,35 +461,129 @@ public void start() { if (running) throw new NopException(ERR_IOC_CONTAINER_ALREADY_STARTED).param(ARG_CONTAINER_ID, id); + startFuture = null; LOG.info("nop.ioc.start-container:containerId={}", getId()); running = true; singletonScope = new BeanScopeImpl(ApiConstants.BEAN_SCOPE_SINGLETON, XLang.newEvalScope(), this); try { + List startBeans = new ArrayList<>(); + for (BeanDefinition bean : orderedBeans) { if (bean.isSingleton()) { if (startMode == BeanContainerStartMode.ALL_LAZY) { // 只创建具有delayMethod的bean if (bean.hasDelayMethod() && !bean.isLazyInit() || bean.isIocForceInit()) { - getBean0(bean, true, true); + startBean(startBeans, bean); } } else if (startMode == BeanContainerStartMode.ALL_EAGER || !bean.isLazyInit()) { - getBean0(bean, true, true); + startBean(startBeans, bean); } } } - runDelayMethod(); - } catch (Exception e) { - try { - stop(); - } catch (Exception e2) { - LOG.error("nop.err.ioc.stop-failed", e2); + if (concurrentStart) { + startFuture = asyncStartBeans(startBeans).whenComplete((ret, err) -> { + if (err != null) { + handleStartError(err); + } else { + runDelayMethod(); + started = true; + } + LOG.info("nop.ioc.async-start-finished:{}",getId()); + }); + } else { + runDelayMethod(); + started = true; + LOG.info("nop.ioc.start-finished:{}",getId()); } + } catch (Exception e) { + // dumpCreations(); + handleStartError(e); throw e; } - started = true; + } + + void handleStartError(Throwable e) { + LOG.error("nop.err.ioc.start-failed", e); + try { + stop(); + } catch (Exception e2) { + LOG.error("nop.err.ioc.stop-failed", e2); + } + } + + @Override + public void awaitStartFinished() { + if (startFuture != null) + FutureHelper.syncGet(startFuture); + } + + /* + @DataBean + public static class BeanCreation { + final String threadName; + final String beanId; + final Set depends; + final boolean created; + + public BeanCreation(String threadName, String beanId, Set depends, boolean created) { + this.threadName = threadName; + this.beanId = beanId; + this.depends = depends; + this.created = created; + } + + public String toString() { + return StringHelper.rightPad(threadName,25,' ') + " " + beanId + (created ? depends : ""); + } + + public String getThreadName() { + return threadName; + } + + public String getBeanId() { + return beanId; + } + + public Set getDepends() { + return depends; + } + + public boolean isCreated() { + return created; + } + } + + Queue beanCreations = new LinkedTransferQueue<>(); + + void dumpCreations() { + System.out.println(StringHelper.join(beanCreations,"\r\n")); + }*/ + + CompletableFuture asyncStartBeans(List startBeans) { + TaskExecutionGraph graph = new TaskExecutionGraph("ioc-container-start"); + for (BeanDefinition bean : startBeans) { + graph.addTaskWithDepends(bean.getId(), + () -> { + // beanCreations.add(new BeanCreation(Thread.currentThread().getName(), bean.getId(), graph.getDepends(bean.getId()), false)); + getBean0(bean, true, false); + // beanCreations.add(new BeanCreation(Thread.currentThread().getName(), bean.getId(), graph.getDepends(bean.getId()), true)); + }, + bean.getDependBeanIds()); + } + graph.analyze(); + this.cancellable = new Cancellable(); + return graph.runOnExecutor(GlobalExecutors.globalWorker(), this.cancellable); + } + + void startBean(List startBeans, BeanDefinition bean) { + if (concurrentStart) { + startBeans.add(bean); + } else { + getBean0(bean, true, false); + } } void runDelayMethod() { @@ -479,6 +595,7 @@ void runDelayMethod() { beanDef.runDelayMethod(instance, beanScope, this); } } + LOG.info("nop.ioc.run-delay-method-finished"); } @Override @@ -486,6 +603,10 @@ public void stop() { LOG.info("nop.ioc.stop-container:containerId={}", getId()); running = false; started = false; + if (cancellable != null) { + cancellable.cancel(ICancellable.CANCEL_REASON_STOP); + cancellable = null; + } if (singletonScope != null) singletonScope.close(); BeanScopeContext.instance().onContainerStop(this); diff --git a/nop-ioc/src/main/java/io/nop/ioc/impl/BeanDefinition.java b/nop-ioc/src/main/java/io/nop/ioc/impl/BeanDefinition.java index f9909642c..70be98a97 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/impl/BeanDefinition.java +++ b/nop-ioc/src/main/java/io/nop/ioc/impl/BeanDefinition.java @@ -89,6 +89,8 @@ public class BeanDefinition implements IBeanDefinition { private boolean intercepted; private boolean removed; + private List dependBeanIds; + private Set nextBeans = Collections.emptySet(); /** @@ -135,6 +137,14 @@ public void setConstructorAutowired(boolean constructorAutowired) { this.constructorAutowired = constructorAutowired; } + public List getDependBeanIds() { + return dependBeanIds; + } + + public void setDependBeanIds(List dependBeanIds) { + this.dependBeanIds = dependBeanIds; + } + public boolean isRemoved() { return removed; } diff --git a/nop-ioc/src/main/java/io/nop/ioc/impl/BeanTopologySorter.java b/nop-ioc/src/main/java/io/nop/ioc/impl/BeanTopologySorter.java index e9bf200ab..f47de61e8 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/impl/BeanTopologySorter.java +++ b/nop-ioc/src/main/java/io/nop/ioc/impl/BeanTopologySorter.java @@ -105,6 +105,10 @@ private List sortBeans(List beans, Set l } } + for(BeanDefinition bean: beans){ + bean.setDependBeanIds(graph.getSourceVertexes(bean.getId())); + } + if (LOG.isTraceEnabled()) LOG.trace(graph.toDot(node -> node, "beans")); diff --git a/nop-ioc/src/main/java/io/nop/ioc/impl/ProducedBeanInstance.java b/nop-ioc/src/main/java/io/nop/ioc/impl/ProducedBeanInstance.java index 6f27418cd..7dad5e6fe 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/impl/ProducedBeanInstance.java +++ b/nop-ioc/src/main/java/io/nop/ioc/impl/ProducedBeanInstance.java @@ -7,15 +7,23 @@ */ package io.nop.ioc.impl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.lang.reflect.InvocationHandler; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * 如果存在beanMethod,则createdBean为factoryBean, bean为对factoryBean.beanMethod()的返回结果进行aopProxy处理后的最终对象 * 如果不存在beanMethod,则createdBean则为根据bean的class设置所创建的InvocationHandler对象,而bean为经过aopProxy处理的结果 */ public class ProducedBeanInstance { + static final Logger LOG = LoggerFactory.getLogger(ProducedBeanInstance.class); + private final Object createdBean; private Object bean; + private final CountDownLatch latch = new CountDownLatch(1); public ProducedBeanInstance(Object createdBean) { this.createdBean = createdBean; @@ -35,5 +43,17 @@ public synchronized Object getBean() { public synchronized void setBean(Object bean) { this.bean = bean; + latch.countDown(); + } + + public Object awaitGetBean(int timeout) { + try { + boolean b = latch.await(timeout, TimeUnit.MILLISECONDS); + if(!b) + LOG.debug("nop.ioc.await-get-bean-not-ready: bean={}, timeout={}", createdBean, timeout); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return bean; } } diff --git a/nop-ioc/src/main/java/io/nop/ioc/loader/AppBeanContainerLoader.java b/nop-ioc/src/main/java/io/nop/ioc/loader/AppBeanContainerLoader.java index 0a1b80f14..95d0ac2ed 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/loader/AppBeanContainerLoader.java +++ b/nop-ioc/src/main/java/io/nop/ioc/loader/AppBeanContainerLoader.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.function.Predicate; +import static io.nop.ioc.IocConfigs.CFG_IOC_APP_BEANS_CONCURRENT_START; import static io.nop.ioc.IocConfigs.CFG_IOC_APP_BEANS_CONTAINER_START_MODE; import static io.nop.ioc.IocConfigs.CFG_IOC_APP_BEANS_FILES; import static io.nop.ioc.IocConfigs.CFG_IOC_APP_BEANS_FILE_ENABLED; @@ -74,6 +75,7 @@ public IBeanContainerImplementor loadFromResource(String name, IResource resourc public IBeanContainerImplementor loadAppContainer(IBeanContainer parentContainer) { BeanContainerBuilder builder = new BeanContainerBuilder(classLoader, introspection, parentContainer); + builder.concurrentStart(CFG_IOC_APP_BEANS_CONCURRENT_START.get()); BeanContainerStartMode startMode = BeanContainerStartMode .fromText(CFG_IOC_APP_BEANS_CONTAINER_START_MODE.get()); diff --git a/nop-ioc/src/main/java/io/nop/ioc/loader/BeanContainerBuilder.java b/nop-ioc/src/main/java/io/nop/ioc/loader/BeanContainerBuilder.java index 99c4e7fc3..fa2e0bd60 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/loader/BeanContainerBuilder.java +++ b/nop-ioc/src/main/java/io/nop/ioc/loader/BeanContainerBuilder.java @@ -66,6 +66,7 @@ public class BeanContainerBuilder implements IBeanContainerBuilder { private final IBeanContainer parentContainer; private BeanContainerStartMode startMode; + private boolean concurrentStart; public BeanContainerBuilder(IClassLoader classLoader, IBeanClassIntrospection introspection, IBeanContainer parentContainer) { @@ -88,6 +89,11 @@ public IBeanContainerBuilder startMode(BeanContainerStartMode startMode) { return this; } + public IBeanContainerBuilder concurrentStart(boolean concurrentStart) { + this.concurrentStart = concurrentStart; + return this; + } + @Override public IBeanContainerBuilder addResource(IResource resource) { BeansModel beans = (BeansModel) new DslModelParser(IocConstants.XDEF_BEANS).parseFromResource(resource); @@ -344,6 +350,8 @@ public IBeanContainerImplementor build(String containerId) { enabledBeans, optionalBeans, aliases, parentContainer); if (startMode != null) container.setStartMode(startMode); + container.setConcurrentStart(concurrentStart); + container.setClassIntrospection(this.introspection); return container; } } \ No newline at end of file diff --git a/nop-ioc/src/main/java/io/nop/ioc/loader/IBeanContainerBuilder.java b/nop-ioc/src/main/java/io/nop/ioc/loader/IBeanContainerBuilder.java index 17cff2c42..131315bbf 100644 --- a/nop-ioc/src/main/java/io/nop/ioc/loader/IBeanContainerBuilder.java +++ b/nop-ioc/src/main/java/io/nop/ioc/loader/IBeanContainerBuilder.java @@ -26,6 +26,8 @@ public interface IBeanContainerBuilder { IBeanContainerBuilder startMode(BeanContainerStartMode startMode); + IBeanContainerBuilder concurrentStart(boolean concurrentStart); + IBeanContainerBuilder registerBean(String beanName, Class beanClass, Function supplier, Consumer customizer); diff --git a/nop-quarkus/nop-quarkus-core-starter/src/main/java/io/nop/quarkus/core/dao/AgroalDataSourceFactoryBean.java b/nop-quarkus/nop-quarkus-core-starter/src/main/java/io/nop/quarkus/core/dao/AgroalDataSourceFactoryBean.java index 333f34ed1..5ea54e075 100644 --- a/nop-quarkus/nop-quarkus-core-starter/src/main/java/io/nop/quarkus/core/dao/AgroalDataSourceFactoryBean.java +++ b/nop-quarkus/nop-quarkus-core-starter/src/main/java/io/nop/quarkus/core/dao/AgroalDataSourceFactoryBean.java @@ -199,7 +199,8 @@ private void exportMetrics(MeterRegistry metricsFactory, String dataSourceName, @PreDestroy public void destroy() { - dataSource.close(); + if (dataSource != null) + dataSource.close(); } @BeanMethod diff --git a/nop-xlang/src/test/java/io/nop/xlang/xdsl/TestGenericDslParser.java b/nop-xlang/src/test/java/io/nop/xlang/xdsl/TestGenericDslParser.java index 1b43d151d..9e2a7d17a 100644 --- a/nop-xlang/src/test/java/io/nop/xlang/xdsl/TestGenericDslParser.java +++ b/nop-xlang/src/test/java/io/nop/xlang/xdsl/TestGenericDslParser.java @@ -84,6 +84,15 @@ public void testParseError() { public void testFilter() { XNode node = XNodeParser.instance().parseFromText(null, ""); TreeBean bean = (TreeBean) new DslModelParser().parseFromNode(node); - System.out.println(JsonTool.serialize(bean,true)); + System.out.println(JsonTool.serialize(bean, true)); + } + + @Test + public void testXGen() { + String text = ""; + XNode node = XNodeParser.instance().parseFromText(null, text); + DynamicObject result = new GenericDslParser().parseFromNode(node); + System.out.println(JsonTool.serialize(result, true)); + assertEquals("true", result.prop_get("ui:default")); } } \ No newline at end of file