在系统中,总是会遇到需要判断引用的场景,如自定义方法模块,方法A里面调用了方法B,方法B里调用了方法A,这样自己就会造成循环引用。甚至于方法A里面调用了方法B,方法B里调用了方法C,方法C里面调用了方法A。这样的乃至更深层次的循环调用,那该怎么办呢,我这里设计了一个数据结构,来解决这个问题。
GlobalReferenced
/**
* @author LaiYongBin
* @date 创建于 2022/4/14 20:54
* @apiNote 公用的引用数据结构
*/
public class GlobalReferenced {
/**
* 存储本地公共变量
*/
private static final ThreadLocal<Map<String, GlobalReferenced>> THREAD_LOCAL = new ThreadLocal<>();
/**
* 以list作为参数
*/
private static boolean isItReferenceList(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
for (GlobalReferencedDto gl : globalReferencedDtoList) {
if (gl.getReferencedList() == null) {
gl.setReferencedList(new ArrayList<>(0));
}
}
return isItReference(globalReferencedDtoList, nowId);
}
private static List<String> isItReferenceList(List<GlobalReferencedDto> globalReferencedDtoList, String[] ids) throws PaException {
return isItReference(globalReferencedDtoList, ids);
}
/**
* 以csv作为参数
*/
private static boolean isItReferenceCsv(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
List<String> referencedList = new ArrayList<>();
String referencedCsv = globalReferencedDto.getReferencedCsv();
if (StringUtils.isNotBlank(referencedCsv)) {
for (String referenced : referencedCsv.split(",")) {
referencedList.add(referenced.trim());
}
}
globalReferencedDto.setReferencedList(referencedList);
}
return isItReference(globalReferencedDtoList, nowId);
}
/**
* 以csv作为参数
*/
private static List<String> isItReferenceCsv(List<GlobalReferencedDto> globalReferencedDtoList, String[] ids) throws PaException {
for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
List<String> referencedList = new ArrayList<>();
String referencedCsv = globalReferencedDto.getReferencedCsv();
if (StringUtils.isNotBlank(referencedCsv)) {
for (String referenced : referencedCsv.split(",")) {
referencedList.add(referenced.trim());
}
}
globalReferencedDto.setReferencedList(referencedList);
}
return isItReference(globalReferencedDtoList, ids);
}
/**
* 以数组作为参数
*/
private static boolean isItReferenceArray(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
List<String> referencedList = new ArrayList<>();
String[] referencedArray = globalReferencedDto.getReferencedArray();
if (referencedArray != null) {
for (String referenced : referencedArray) {
referencedList.add(referenced.trim());
}
}
globalReferencedDto.setReferencedList(referencedList);
}
return isItReference(globalReferencedDtoList, nowId);
}
private static List<String> isItReferenceArray(List<GlobalReferencedDto> globalReferencedDtoList, String[] ids) throws PaException {
for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
List<String> referencedList = new ArrayList<>();
String[] referencedArray = globalReferencedDto.getReferencedArray();
if (referencedArray != null) {
for (String referenced : referencedArray) {
referencedList.add(referenced.trim());
}
}
globalReferencedDto.setReferencedList(referencedList);
}
return isItReference(globalReferencedDtoList, ids);
}
/**
* 判断某个id是否被引用, 如果被引用,则返回true, 否则返回false, 若是循环引用,则抛出异常
*
* @param globalReferencedDtoList GlobalReferencedDtoList是所有pojo的list, 里面有id属性, List里面需要包括判断的id的pojo
* GlobalReferencedDto除了id属性外,还有一个referenceIdCsv属性, 属性值是它引用的id的csv字符串
* 我使用的是split后遍历添加,这里的逻辑可以根据自己的来自行修改
* @param nowId 需要判断是否被引用的id
*/
private static boolean isItReference(List<GlobalReferencedDto> globalReferencedDtoList, String nowId) throws PaException {
try {
for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
putValue(globalReferencedDto.getId(), new GlobalReferenced(globalReferencedDto.getId(), globalReferencedDto.getName()));
}
for (GlobalReferencedDto globalReferencedDto : globalReferencedDtoList) {
String flinkCepId = globalReferencedDto.getId();
for (String referenceId : globalReferencedDto.getReferencedList()) {
getValue(flinkCepId).addTheIdPointsToList(referenceId.trim());
THREAD_LOCAL.get().get(flinkCepId).addTheIdPointsToList(referenceId.trim());
}
}
// 判断是否有循环引用
GlobalReferenced referenced = getValue(nowId);
referenced.isItReference(nowId, referenced.id, referenced.name);
return referenced.otherToMe.size() > 0;
} finally {
THREAD_LOCAL.remove();
}
}
/**
* 向THREAD_LOCAL中添加一个值
*/
private static void putValue(String key, GlobalReferenced value) {
Map<String, GlobalReferenced> map = THREAD_LOCAL.get();
if (map == null) {
map = new HashMap<>();
}
map.put(key, value);
THREAD_LOCAL.set(map);
}
/**
* 向THREAD_LOCAL中添加一个值
*/
private static GlobalReferenced getValue(String key) {
return THREAD_LOCAL.get().get(key);
}
/**
* 判断某几个id是否被引用, 如果有引用,则返回被引用的列表
*
* @param referencedDto GlobalReferencedDtoList是所有pojo的list, 里面有id属性, List里面需要包括判断的id的pojo
* GlobalReferencedDto除了id属性外,还有一个referenceIdCsv属性, 属性值是它引用的id的csv字符串
* 我使用的是split后遍历添加,这里的逻辑可以根据自己的来自行修改
* @param ids id的列表
*/
private static List<String> isItReference(List<GlobalReferencedDto> referencedDto, String[] ids) throws PaException {
try {
for (GlobalReferencedDto dto : referencedDto) {
putValue(dto.getId(), new GlobalReferenced(dto.getId(), dto.getName()));
}
for (GlobalReferencedDto ref : referencedDto) {
String flinkCepId = ref.getId();
for (String referenceId : ref.getReferencedList()) {
getValue(flinkCepId).addTheIdPointsToList(referenceId.trim());
}
}
for (String nowId : ids) {
if (getValue(nowId) == null) {
throw new PaException(String.format("未找到id为 `%s` 的cep模板", nowId));
}
}
List<String> result = new ArrayList<>();
for (String nowId : ids) {
if (getValue(nowId).otherToMe.size() > 0) {
result.add(nowId);
}
}
// 判断是否有循环引用
return result;
} finally {
THREAD_LOCAL.remove();
}
}
private GlobalReferenced(String id, String name) {
this.id = id;
this.otherToMe = new HashSet<>();
this.meToOther = new HashSet<>();
this.name = name;
}
private final String id;
/**
* 它指向的其它id
*/
private final String name;
private final Set<String> meToOther;
/**
* 指向它的id
*/
private final Set<String> otherToMe;
private void addTheIdPointsToList(String id) throws PaException {
addTheIdPointsToList(id, false);
}
/**
* 新增它指向的其它id
*/
private void addTheIdPointsToList(String id, boolean isCalled) throws PaException {
if (otherToMe.contains(id)) {
throw new PaException(id + "不能相互引用");
}
meToOther.add(id);
if (isCalled) {
return;
}
// 将自己的引用添加到其它id的引用中
GlobalReferenced cepReferenced = getValue(id);
if (cepReferenced == null) {
throw new PaException(id + "对应的记录不存在");
}
cepReferenced.addIdPointingToItList(this.id, true);
}
public void addIdPointingToItList(String id) throws PaException {
addIdPointingToItList(id, false);
}
/**
* 新增指向它的id
*/
public void addIdPointingToItList(String id, boolean isCalled) throws PaException {
if (meToOther.contains(id)) {
throw new PaException(id + "不能相互引用");
}
otherToMe.add(id);
if (isCalled) {
return;
}
// 将自己的引用添加到其它id的引用中
GlobalReferenced cepReferenced = getValue(id);
if (cepReferenced == null) {
throw new PaException(id + "对应的记录不存在");
}
cepReferenced.addTheIdPointsToList(this.id, true);
}
/**
* 判断是否循环引用
*
* @param compareId 判断的id
*/
private void isItReference(String compareId, String chainId, String chainName) throws PaException {
// 遍历自己指向的id
GlobalReferenced referenced = getValue(this.id);
for (String otherId : referenced.meToOther) {
GlobalReferenced cepReferenced = getValue(otherId);
// 判断是否循环引用
String nextChainName = chainName + " -> " + cepReferenced.name;
String nextChainId = chainId + " -> " + cepReferenced.id;
if (cepReferenced.getId().equals(compareId)) {
if (cepReferenced.name != null) {
throw new PaException(String.format("id为 `%s` 的资源被循环引用了, 引用链条名为 `%s`", cepReferenced.id, nextChainName));
}
throw new PaException(String.format("id为 `%s` 的资源被循环引用了, 引用链条Id为 `%s`", cepReferenced.id, nextChainId));
}
cepReferenced.isItReference(compareId, nextChainId, nextChainName);
}
}
public void isItReference() throws PaException {
isItReference(this.id, this.id, this.name);
}
@Override
public String toString() {
return "FlinkCepReferenced{" + "id='" + id + '\'' + ", meToOther=" + meToOther + ", otherToMe=" + otherToMe + '}';
}
public String getId() {
return id;
}
public Set<String> getMeToOther() {
return meToOther;
}
public Set<String> getOtherToMe() {
return otherToMe;
}
public static void main(String[] args) throws PaException {
GlobalReferencedDto cepReferencedA = GlobalReferencedDto.builder().id("A").referencedCsv("B").build();
GlobalReferencedDto cepReferencedB = GlobalReferencedDto.builder().id("B").referencedCsv("C").build();
GlobalReferencedDto cepReferencedC = GlobalReferencedDto.builder().id("C").referencedCsv("A").build();
System.out.println(isItReferenceCsv(Arrays.asList(cepReferencedA, cepReferencedB, cepReferencedC), "A"));
}
}
GlobalReferencedDto.java
/**
* @author LaiYongBin
* @date 创建于 2022/4/19 10:37
* @apiNote 公用的引用数据结构DTO
*/
@Data
@Builder
public class GlobalReferencedDto {
private String id;
private String name;
/**
* 以csv格式存储它引用的id, 为了兼容不同格式的引用
*/
private String referencedCsv;
/**
* 以List格式存储它引用的id, 为了兼容不同格式的引用
*/
private List<String> referencedList;
/**
* 以数组格式存储它引用的id, 为了兼容不同格式的引用
*/
private String[] referencedArray;
}