Skip to content

Commit

Permalink
增加nop.ioc.bean-depends-graph.allow-cycle。平台内部的bean定义已经梳理了依赖关系,通过ignor…
Browse files Browse the repository at this point in the history
…e-depends来比较忽略依赖方向,从而打破循环依赖
  • Loading branch information
entropy-cloud committed Jul 20, 2024
1 parent 3883d53 commit a8b2e30
Show file tree
Hide file tree
Showing 23 changed files with 264 additions and 42 deletions.
31 changes: 31 additions & 0 deletions docs/dev-guide/ioc.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,34 @@ NopIoC的JUnit支持提供了如下功能:
5. 支持在测试用例中通过@Inject来注入bean

关于Nop平台中自动化支持的详细介绍,可以参见 [autotest.md](autotest.md)

### 3.11 问题诊断

#### 检查循环依赖
缺省情况下NopIoC允许bean的定义出现循环依赖,比如A注入B,B注入C,C又注入A等。
如果设置 `nop.ioc.bean-depends-graph.allow-cycle` 为false,则启动时会检查bean的引用关系,发现循环依赖会报错。

可以通过`@IgnoreDepends`注解或者ioc配置中的`ioc:ignore-depends`属性来打破循环

```java
public class SysSequenceGenerator implements ISequenceGenerator {
private IOrmTemplate ormTemplate;

@Inject
@IgnoreDepends
public void setOrmTemplate(IOrmTemplate ormTemplate) {
this.ormTemplate = ormTemplate;
}
}
```

```xml
<bean id="nopOrmSessionFactory" class="io.nop.orm.factory.OrmSessionFactoryBean"
ioc:bean-method="getObject" ioc:default="true">

<property name="daoListeners">
<ioc:collect-beans by-type="io.nop.orm.IOrmDaoListener" ioc:ignore-depends="true"/>
</property>
...
</bean>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) 2017-2024 Nop Platform. All rights reserved.
* Author: [email protected]
* Blog: https://www.zhihu.com/people/canonical-entropy
* Gitee: https://gitee.com/canonical-entropy/nop-entropy
* Github: https://github.com/entropy-cloud/nop-entropy
*/
package io.nop.api.core.annotations.ioc;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreDepends {
boolean value() default true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public void setEdges(List<EdgeBean> edges) {
}

@PropMeta(propId = 3)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Map<String, Object> getAttrs() {
return super.getAttrs();
}
Expand Down
3 changes: 2 additions & 1 deletion nop-biz/src/main/java/io/nop/biz/dict/ObjDictLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package io.nop.biz.dict;

import io.nop.api.core.annotations.ioc.IgnoreDepends;
import io.nop.api.core.beans.DictBean;
import io.nop.api.core.exceptions.NopException;
import io.nop.api.core.util.FutureHelper;
Expand All @@ -21,7 +22,6 @@
import io.nop.core.dict.IDictLoader;
import io.nop.core.dict.IDictProvider;
import io.nop.graphql.core.GraphQLConstants;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Inject;
Expand All @@ -32,6 +32,7 @@
public class ObjDictLoader implements IDictLoader {
private IBizObjectManager bizObjectManager;

@IgnoreDepends
@Inject
public void setBizObjectManager(IBizObjectManager bizObjectManager) {
this.bizObjectManager = bizObjectManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@

<bean id="nopBizObjectManager" class="io.nop.biz.impl.BizObjectManager" ioc:delay-method="delayInit">
<property name="bizModelBeans">
<ioc:collect-beans by-annotation="io.nop.api.core.annotations.biz.BizModel"
<!-- BizModel和BizObjectManager会循环依赖,这里打破循环 -->
<ioc:collect-beans by-annotation="io.nop.api.core.annotations.biz.BizModel" ioc:ignore-depends="true"
only-concrete-classes="true"/>
</property>
<property name="bizInitializers">
<ioc:collect-beans only-concrete-classes="true" by-type="io.nop.graphql.core.biz.IGraphQLBizInitializer"/>
<ioc:collect-beans only-concrete-classes="true" ioc:ignore-depends="true"
by-type="io.nop.graphql.core.biz.IGraphQLBizInitializer"/>
</property>

<property name="schemaInitializers">
Expand All @@ -47,7 +49,7 @@
</property>

<property name="actionDecoratorCollectors">
<ioc:collect-beans only-concrete-classes="true"
<ioc:collect-beans only-concrete-classes="true" ioc:ignore-depends="true"
by-type="io.nop.biz.decorator.IActionDecoratorCollector"/>
</property>
</bean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ public String toStringUnordered() {
return "graph(" + "vertices: " + vertexMap.keySet() + ", edges: " + edges + ")";
}

public GraphBean toGraphBean(Function<V,String> vertexIdGetter) {
public String toDot(IGraphvizAdapter<V> adapter, String name) {
return GraphvizHelper.toDot(adapter, this, true, name);
}

public GraphBean toGraphBean(Function<V, String> vertexIdGetter) {
GraphBean dto = new GraphBean();
List<GraphBean.VertexBean> vertices = new ArrayList<>(vertexMap.size());
for (V v : vertexMap.keySet()) {
Expand Down Expand Up @@ -230,7 +234,7 @@ public boolean removeVertex(V v) {
return false;
}

_removeEdges(info,v);
_removeEdges(info, v);
return true;
}

Expand Down Expand Up @@ -278,7 +282,7 @@ private void removeMinorityVertices(Collection<V> collection) {
continue;
}

_removeEdges(info,v);
_removeEdges(info, v);
}
vertexMap.keySet().removeAll(collection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
package io.nop.core.model.graph;

import io.nop.commons.mutable.MutableInt;
import io.nop.commons.util.CollectionHelper;
import io.nop.commons.util.StringHelper;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

Expand Down Expand Up @@ -133,4 +137,39 @@ public Set<V> findCycles() {

return countMap.keySet();
}

public List<V> findOneCycle() {
List<V> visited = new ArrayList<>();
for (V node : countMap.keySet()) {
visited.clear();
if (containsCycle(node, visited))
break;
}
return visited;
}

public String displayOneCycle() {
List<V> cycle = findOneCycle();
cycle = CollectionHelper.reverseList(cycle);
return StringHelper.join(cycle, "->");
}

private boolean containsCycle(V node, List<V> visited) {
int index = visited.indexOf(node);
if (index >= 0) {
// 仅保留最小化的部分
for (int i = 0; i < index; i++) {
visited.remove(0);
}
visited.add(node);
return true;
}
visited.add(node);
for (V target : graph.getTargetVertexes(node)) {
if (containsCycle(target, visited))
return true;
}
visited.remove(visited.size() - 1);
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package io.nop.dao.txn.impl;

import io.nop.api.core.annotations.ioc.IgnoreDepends;
import io.nop.api.core.exceptions.NopException;
import io.nop.api.core.util.Guard;
import io.nop.commons.util.StringHelper;
Expand Down Expand Up @@ -137,6 +138,7 @@ public ITransactionListener getDefaultListener() {
}

@Inject
@IgnoreDepends
public void setDefaultListener(@Named("nopDefaultTransactionListener") @Nullable ITransactionListener defaultListener) {
this.defaultListener = defaultListener;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38075,6 +38075,16 @@
"io.nop.dyn.dao.entity.NopDynModule"
]
},
{
"name": "batchLoadModules",
"parameterTypes": [
"java.util.Collection"
]
},
{
"name": "destroy",
"parameterTypes": []
},
{
"name": "genModuleBizModels",
"parameterTypes": [
Expand Down Expand Up @@ -38121,6 +38131,12 @@
"name": "generateForAllModules",
"parameterTypes": []
},
{
"name": "generateForAllModules",
"parameterTypes": [
"io.nop.dyn.service.codegen.InMemoryCodeCache"
]
},
{
"name": "generateForApp",
"parameterTypes": [
Expand All @@ -38133,10 +38149,24 @@
"io.nop.dyn.dao.entity.NopDynModule"
]
},
{
"name": "getCodeCache",
"parameterTypes": []
},
{
"name": "init",
"parameterTypes": []
},
{
"name": "initializeTenant",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "isUseTenant",
"parameterTypes": []
},
{
"name": "reloadModel",
"parameterTypes": []
Expand Down Expand Up @@ -46593,6 +46623,10 @@
"name": "getValue",
"parameterTypes": []
},
{
"name": "isIocIgnoreDepends",
"parameterTypes": []
},
{
"name": "isIocSkipIfEmpty",
"parameterTypes": []
Expand All @@ -46607,6 +46641,12 @@
"io.nop.core.lang.json.IJsonHandler"
]
},
{
"name": "setIocIgnoreDepends",
"parameterTypes": [
"boolean"
]
},
{
"name": "setIocSkipIfEmpty",
"parameterTypes": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@
/nop/report/demo/base/09-套打.xpt.xlsx
/nop/report/demo/base/10-导出Excel公式.xpt.xlsx
/nop/report/demo/base/11-打印条码和二维码.xpt.xlsx
/nop/report/demo/base/12-动态Sheet和动态列.xlsx
/nop/report/demo/base/12-动态Sheet和动态列.xpt.xlsx
/nop/report/demo/ext/report-with-params.xpt.xlsx
/nop/report/demo/pages/demo.page.yaml
/nop/report/demo/pages/report-with-params.page.yaml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="io.nop.file.quarkus.web.QuarkusFileService" ioc:type="@bean:id" ioc:default="true"/>
<!-- <bean id="io.nop.file.quarkus.web.QuarkusFileService" ioc:type="@bean:id" ioc:default="true"/>-->
</beans>
23 changes: 13 additions & 10 deletions nop-ioc/src/main/java/io/nop/ioc/IocConfigs.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,43 @@ public interface IocConfigs {
SourceLocation s_loc = SourceLocation.fromClass(IocConfigs.class);
@Description("应用Ioc容器的启动模式")
IConfigReference<String> CFG_IOC_APP_BEANS_CONTAINER_START_MODE = AppConfig
.varRef(s_loc,"nop.ioc.app-beans-container.start-mode", String.class, null);
.varRef(s_loc, "nop.ioc.app-beans-container.start-mode", String.class, null);

@Description("是否自动装载所有模块下的beans/app-*.beans.xml文件")
IConfigReference<Boolean> CFG_IOC_APP_BEANS_FILE_ENABLED = AppConfig.varRef(s_loc,"nop.ioc.app-beans-file.enabled",
IConfigReference<Boolean> CFG_IOC_APP_BEANS_FILE_ENABLED = AppConfig.varRef(s_loc, "nop.ioc.app-beans-file.enabled",
Boolean.class, true);

@Description("缺省情况下所有模块下的beans/app-*.beans.xml文件都被被装载,这里可以设置过滤条件,从而只装载一部分bean配置")
IConfigReference<String> CFG_IOC_APP_BEANS_FILE_PATTERN = AppConfig.varRef(s_loc,"nop.ioc.app-beans-file.pattern",
IConfigReference<String> CFG_IOC_APP_BEANS_FILE_PATTERN = AppConfig.varRef(s_loc, "nop.ioc.app-beans-file.pattern",
String.class, null);

@Description("缺省情况下所有模块下的beans/app-*.beans.xml文件都被被装载,这里可以设置过滤条件,从而只装载一部分bean配置")
IConfigReference<String> CFG_IOC_APP_BEANS_FILE_SKIP_PATTERN = AppConfig
.varRef(s_loc,"nop.ioc.app-beans-file.skip-pattern", String.class, null);
.varRef(s_loc, "nop.ioc.app-beans-file.skip-pattern", String.class, null);

@Description("是否启用自动配置机制。如果启用,则自动装载/nop/auto-config/*.beans中指定的所有beans.xml文件")
IConfigReference<Boolean> CFG_IOC_AUTO_CONFIG_ENABLED = AppConfig.varRef(s_loc,"nop.ioc.auto-config.enabled",
IConfigReference<Boolean> CFG_IOC_AUTO_CONFIG_ENABLED = AppConfig.varRef(s_loc, "nop.ioc.auto-config.enabled",
Boolean.class, true);

@Description("是否启用beans文件优化机制。如果启用,则首先检查是否存在/nop/main/beans/merged-app.beans.xml文件,如果存在,则跳过所有其他的beans文件解析")
IConfigReference<Boolean> CFG_IOC_MERGED_BEANS_FILE_ENABLED = AppConfig.varRef(s_loc,"nop.ioc.merged-beans-file.enabled",
IConfigReference<Boolean> CFG_IOC_MERGED_BEANS_FILE_ENABLED = AppConfig.varRef(s_loc, "nop.ioc.merged-beans-file.enabled",
Boolean.class, true);

@Description("是否启用AOP机制")
IConfigReference<Boolean> CFG_IOC_AOP_ENABLED = AppConfig.varRef(s_loc,"nop.ioc.aop.enabled", Boolean.class, true);
IConfigReference<Boolean> CFG_IOC_AOP_ENABLED = AppConfig.varRef(s_loc, "nop.ioc.aop.enabled", Boolean.class, true);

@Description("如果不为空,且启用auto-config的情况下,则只启用指定的xxx.beans配置")
IConfigReference<String> CFG_IOC_AUTO_CONFIG_PATTERN = AppConfig.varRef(s_loc,"nop.ioc.auto-config.pattern", String.class,
IConfigReference<String> CFG_IOC_AUTO_CONFIG_PATTERN = AppConfig.varRef(s_loc, "nop.ioc.auto-config.pattern", String.class,
null);

@Description("如果不为空,且启用auto-config的情况下,则排除指定的xxx.beans配置")
IConfigReference<String> CFG_IOC_AUTO_CONFIG_SKIP_PATTERN = AppConfig.varRef(s_loc,"nop.ioc.auto-config.skip-pattern",
IConfigReference<String> CFG_IOC_AUTO_CONFIG_SKIP_PATTERN = AppConfig.varRef(s_loc, "nop.ioc.auto-config.skip-pattern",
String.class, null);

@Description("增加app容器的配置")
IConfigReference<String> CFG_IOC_APP_BEANS_FILES = AppConfig.varRef(s_loc,"nop.ioc.app-beans.files", String.class, null);
IConfigReference<String> CFG_IOC_APP_BEANS_FILES = AppConfig.varRef(s_loc, "nop.ioc.app-beans.files", String.class, null);

@Description("允许bean的定义存在循环依赖关系")
IConfigReference<Boolean> CFG_IOC_BEAN_DEPENDS_GRAPH_ALLOW_CYCLE =
AppConfig.varRef(s_loc, "nop.ioc.bean-depends-graph.allow-cycle", Boolean.class, true);
}
12 changes: 9 additions & 3 deletions nop-ioc/src/main/java/io/nop/ioc/IocErrors.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public interface IocErrors {
String ARG_BEFORE = "before";
String ARG_AFTER = "after";

String ARG_BEAN_DEPENDS_CYCLE = "beanDependsCycle";

String ARG_METHOD_NAME = "methodName";

String ARG_BEAN_REF = "beanRef";
Expand Down Expand Up @@ -209,11 +211,15 @@ public interface IocErrors {
ErrorCode ERR_IOC_AOP_CLASS_NO_CONSTRUCTOR = define("nop.err.ioc.aop-class-no-constructor",
"bean[{beanName}]对应的AOP代理类没有参数个数为{argCount}的构造函数", ARG_BEAN_NAME, ARG_ARG_COUNT);

ErrorCode ERR_IOC_CONTAINER_NOT_STARTED = define("bean容器[{containerId}]没有启动,无法访问其中定义的bean", ARG_CONTAINER_ID);
ErrorCode ERR_IOC_CONTAINER_NOT_STARTED = define("nop.err.ioc.container-not-started",
"bean容器[{containerId}]没有启动,无法访问其中定义的bean", ARG_CONTAINER_ID);

ErrorCode ERR_IOC_UNKNOWN_IOC_BEFORE = define("bean[{beanName}]的ioc:before指定的依赖[{before}]没有被定义", ARG_BEAN_NAME,
ErrorCode ERR_IOC_UNKNOWN_IOC_BEFORE = define("nop.err.ioc.unknown-ioc-before", "bean[{beanName}]的ioc:before指定的依赖[{before}]没有被定义", ARG_BEAN_NAME,
ARG_BEFORE, ARG_BEAN);

ErrorCode ERR_IOC_UNKNOWN_IOC_AFTER = define("bean[{beanName}]的ioc:after指定的依赖[{after}]没有被定义", ARG_BEAN_NAME,
ErrorCode ERR_IOC_UNKNOWN_IOC_AFTER = define("nop.err.ioc.unknown-ioc-after", "bean[{beanName}]的ioc:after指定的依赖[{after}]没有被定义", ARG_BEAN_NAME,
ARG_AFTER, ARG_BEAN);

ErrorCode ERR_IOC_BEAN_DEPENDS_GRAPH_CONTAINS_CYCLE = define("nop.err.ioc.bean-depends-graph-contains-loop",
"bean依赖关系不允许包含循环依赖。如果确实存在,可以在必要的地方配置节点上标注ioc:ignore-depends", ARG_BEAN_DEPENDS_CYCLE);
}
Loading

0 comments on commit a8b2e30

Please sign in to comment.