08
Сен
2018

Проверка классов на циклические зависимости (избежать StackOverflowError)

При написании своего DI контейнера, столкнулся с проблемой - циклические зависимости в классах

Пример:

@Component
public class DefaultService {
    private static final Logger log = LoggerFactory.getLogger(DefaultService.class);

    @Dependency
    private DefaultComponent defaultComponent;

    public void printInfo() {
        log.info(String.valueOf(defaultComponent));
    }
}

@Component
@LoadOpt(PROTOTYPE)
public class DefaultComponent {
    private static final Logger log = LoggerFactory.getLogger(DefaultComponent.class);

    @Dependency
    private DefaultService defaultService;

    public void printInfo() {
        log.info(String.valueOf(defaultService));
    }
}

Как видно из примера - при рекурсивном обходе зависимостей классов получаем StackOwerFlowError, ибо в синглтон мапе либо же при создании экземпляра и внедрению его в значение поля моя функция заходит в класс-зависимость предыдущего. Да, конечно, этого можно избежать инициализируя класс до прыжков по дереву зависимостей, но пока не понимаю как это можно было бы правильно сделать.

Вопросы:

  1. как правильно анализировать классы до их инициализации, чтобы не возникало зацикливания?

Пример моего анализатора, но он отказывается работать (не спорю, код не первого сорта, увы пишу как считаю правильным. Возможно мог что-то упустить либо просто мне не хватает навыков написать все на листе у себя в голове)

public class CyclicDependenciesAnalyzer implements Analyzer<CyclicDependencyResult, List<Class<?>>> {
private final Map<String, ClassDefinition> classDefinitions = new HashMap<>();

    @Override
    public CyclicDependencyResult analyze(List<Class<?>> tested) throws Exception {
        for (Class<?> type : tested) {
            final ClassDefinition main = new ClassDefinition();
            addToMap(type.getSimpleName(), main);

            final Constructor<?> constructor = type.getConstructors()[0];
            final Class<?>[] parameterTypes = constructor.getParameterTypes();
            final ClassDefinition parent = checkIncluded(type);
            if (parameterTypes.length > 0) {
                for (Class<?> parameter : parameterTypes) {
                    if (parameter.isAnnotationPresent(Component.class)) {
                        if (!checkClass(parameter) && !checkTypes(parameter)) {
                            continue;
                    }

                        ClassDefinition definition = checkIncluded(parameter);
                        if (definition == null) {
                            definition = new ClassDefinition();
                            addToMap(parameter.getSimpleName(), definition);
                            analyze(Collections.singletonList(parameter));
                            continue;
                        }

                        if (parent != null) {
                            definition.setParent(parent);
                        }

                        addDependencies(parameter, definition);
                    }
                }
            }

            final Field[] fields = type.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Dependency.class)) {
                    final Class<?> fieldType = field.getType();
                    if (!checkClass(fieldType) && !checkTypes(fieldType)) {
                        continue;
                    }

                    ClassDefinition definition = checkIncluded(fieldType);
                    if (definition == null) {
                        definition = new ClassDefinition();
                        addToMap(fieldType.getSimpleName(), definition);
                        analyze(Collections.singletonList(fieldType));
                        continue;
                    }

                    if (parent != null) {
                        definition.setParent(parent);
                    }

                    addDependencies(fieldType, definition);
                }
            }

            final Method[] methods = type.getDeclaredMethods();
            if (methods.length > 1) {
                for (Method method : methods) {
                    if (method.isAnnotationPresent(Dependency.class)) {
                        if (method.getParameterCount() == 0) {
                            throw new IntroInstantiateException("Impossibility of injection into a function with fewer parameters than one");
                        }

                        if (method.getParameterCount() > 1) {
                           throw new IntroInstantiateException("Inability to inject a function with more than one parameter");
                        }

                        final Class<?> methodParameterType = method.getParameterTypes()[0];

                        if (!checkClass(methodParameterType) && !checkTypes(methodParameterType)) {
                            continue;
                        }

                        ClassDefinition definition = checkIncluded(methodParameterType);
                        if (definition == null) {
                            definition = new ClassDefinition();
                            addToMap(methodParameterType.getSimpleName(), definition);
                            analyze(Collections.singletonList(methodParameterType));
                            continue;
                         }

                        if (parent != null) {
                             definition.setParent(parent);
                        }

                        addDependencies(methodParameterType, definition);
                    }
                 }
             }
         }

        return checkCyclicDependencies();
    }

    @Override
    public boolean supportFor(List<Class<?>> tested) {
        return true;
    }

    private CyclicDependencyResult checkCyclicDependencies() {
        for (Map.Entry<String, ClassDefinition> entry : classDefinitions.entrySet()) {
            final ClassDefinition def = entry.getValue();
            final ClassDefinition parent = def.getParent();
            if (parent != null) {
                for (Class<?> type : parent.getDependencies()) {
                    if (type.getSimpleName().equals(entry.getKey())) {
                        for (Class<?> defType : entry.getValue().getDependencies()) {
                             if (defType.getSimpleName().equals(type.getSimpleName())) {
                                return new CyclicDependencyResult("Component: " + type.getSimpleName() + ". Requested component is currently in creation: Is there an unresolvable circular reference?");
                            }
                        }
                    }
                }
            }
        }

        return new CyclicDependencyResult(TRUE);
     }

    private ClassDefinition checkIncluded(Class<?> parameter) {
        final Optional<ClassDefinition> optional = classDefinitions.entrySet()
            .stream()
            .filter(e -> e.getKey().equals(parameter.getSimpleName()))
            .map(Map.Entry::getValue)
            .findFirst();
        return optional.orElse(null);
    }

    private void addDependencies(Class<?> type, ClassDefinition definition) {
        if (definition.getDependencies() == null) {
            definition.setDependencies(new ArrayList<>());
        }

        definition.getDependencies().add(type);
    }

    private void addToMap(String name, ClassDefinition definition) {
        if (!classDefinitions.containsKey(name)) {
            classDefinitions.put(name, definition);
        }
    }

    private static class ClassDefinition {
        private List<Class<?>> dependencies;
        private ClassDefinition parent;

        public List<Class<?>> getDependencies() {
            return dependencies;
        }

        public ClassDefinition getParent() {
            return parent;
        }

        public void setParent(ClassDefinition parent) {
            this.parent = parent;
        }

        public void setDependencies(List<Class<?>> dependencies) {
             this.dependencies = dependencies;
        }
    }

}
  1. как правильно можно было бы сделать циклические зависимости между компонентами? Допустим ленивая инициализация или что посоветуете.

Источник: https://ru.stackoverflow.com/questions/879000/%D0%9F%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%B2-%D0%BD%D0%B0-%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5-%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8-%D0%B8%D0%B7%D0%B1%D0%B5%D0%B6%D0%B0%D1%82%D1%8C-stackoverflowerror

Тебе может это понравится...

Добавить комментарий