We use Spring annotation based configuration, and @Autowire the dependencies. Using Autowire injection is a more convenient way than using constructor based injection, with less code to write. However, if your code grows, you may end up with circular dependencies in your code. While spring does a great job of still instantiating beans that have a circular reference, after too many circular references, you will end up getting an exception like this:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'agencyBean': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:252) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190) at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:844) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:786) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:703) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:474) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:84) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:282) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1074) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456) at org.springframework.beans.factory.support.AbstractBeanFactory$2.getObject(AbstractBeanFactory.java:329) at com.myapp.utils.ViewScope.get(ViewScope.java:20)
public class CircularDependencyUtil { private static final Logger logger = LoggerFactory.getLogger(CircularDependencyUtil.class);
When you get this exception, you might have reached a point where you have 50 services that have been autowired in such a way that you have ended up with multiple spring bean circular references. At this moment it is best to find out all the beans that have a circular reference.
Here is what you need to do:
First, add maven dependencies to jgrapht to your pom.xml
org.jgrapht jgrapht-core 0.9.1 org.jgrapht jgrapht-ext 0.9.0
Second, add the following code to a class of your choice.
public static DirectedGraphgenerateDependencyGraphForServices(String packageName) { DirectedGraph dgraph = new DefaultDirectedGraph (DefaultEdge.class); ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true); scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class)); for (BeanDefinition bd : scanner.findCandidateComponents(packageName)) { try { Class beanClass = Class.forName(((ScannedGenericBeanDefinition) bd).getMetadata().getClassName()); if (beanClass.getInterfaces().length == 0) continue; String beanName = beanClass.getInterfaces()[0].getSimpleName(); if (!beanClass.getInterfaces()[0].getName().startsWith(packageName)) { // only common services continue; } if (!dgraph.containsVertex(beanName)) { dgraph.addVertex(beanName); } Field[] fields = beanClass.getDeclaredFields(); if (fields.length == 0) continue; for (Field field : fields) { if (!field.isAnnotationPresent(Autowired.class)) { continue; } if (!field.getType().getName().startsWith(packageName)) { // only common services continue; } String fieldName = field.getType().getSimpleName(); if (!dgraph.containsVertex(fieldName)) { dgraph.addVertex(fieldName); } dgraph.addEdge(beanName, fieldName); logger.info("Field={}", fieldName); } } catch (ClassNotFoundException e) { continue; } } return dgraph; } public static Set getCycleInDependencyGraph(DirectedGraph dgraph){ CycleDetector cycleDetector = new CycleDetector<>(dgraph); Set cycles = cycleDetector.findCycles(); return cycles; } public static OutputStream exportGraphInDOTFormat(DirectedGraph dgraph){ VertexNameProvider vertexNameProvider = new VertexNameProvider () { @Override public String getVertexName(String vertex) { return vertex; } }; DOTExporter exporter = new DOTExporter(vertexNameProvider, null, null, null, null); OutputStream outputStream = new ByteArrayOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); try { exporter.export(outputStreamWriter, dgraph); //exporter.export(new FileWriter(targetDirectory + "initial-graph.dot"), dgraph); } catch (Exception e) { e.printStackTrace(); } return outputStream; } }
generateDependencyGraphForServices(String packageName) will create a graph of all the Beans in the package that have the Service annotation and their Autowired dependencies. After the graph is created, pass it to the getCycleInDependencyGraph(DirectedGraph The function dgraph) function, which will find all the beans that have a circular dependency.
Circular dependencies are never good, so even if Spring does work around them most of the times, it is best to not have them in your code.
Thanks, that was useful! Was looking for a tool like this, but none of them does so clear and simple like this to showing the dependencies, and figure out the circular dependencies between beans.
ReplyDeleteOne thing which is missing from this code, to handle the constructor based @AutoWire