package com.yourpackage;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.*;
public class SpringVisualizer implements ApplicationContextAware, InitializingBean {
private ConfigurableApplicationContext context;
private final Set<String> currentBeanManualDeps = new HashSet<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = (ConfigurableApplicationContext) applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
// Force an immediate print to prove the bean is alive
System.err.println("!!! SPRING VISUALIZER BOOTSTRAP STARTING !!!");
generateGraph();
}
public void generateGraph() {
StringBuilder dot = new StringBuilder("digraph G {\n");
// ortho prevents line-crossing chaos in 300+ bean graphs
dot.append(" rankdir=LR; splines=ortho; node [shape=plain, fontname=\"Arial\", fontsize=10];\n");
dot.append(" edge [fontname=\"Arial\", fontsize=8];\n\n");
Set<String> processedNames = new HashSet<>();
int count = 0;
ApplicationContext currentCtx = this.context;
while (currentCtx != null) {
if (currentCtx instanceof ConfigurableApplicationContext) {
ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) currentCtx).getBeanFactory();
for (String name : factory.getBeanDefinitionNames()) {
if (processedNames.add(name)) {
try {
BeanDefinition def = factory.getBeanDefinition(name);
currentBeanManualDeps.clear();
count++;
String color = name.toLowerCase().contains("comet") ? "#FFF9C4" :
(name.toLowerCase().contains("cpls") ? "#C8E6C9" : "#E1F5FE");
// We use quotes around names to handle special chars like dots/dashes
dot.append(" \"").append(name).append("\" [label=<");
dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"").append(color).append("\">");
dot.append("<TR><TD COLSPAN=\"2\"><B>").append(xmlEscape(name)).append("</B></TD></TR>");
for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
dot.append("<TR><TD ALIGN=\"LEFT\">").append(xmlEscape(pv.getName())).append("</TD>");
dot.append("<TD ALIGN=\"LEFT\">").append(extractValue(pv.getValue(), 0)).append("</TD></TR>");
}
dot.append("</TABLE>>];\n");
// Connections
Set<String> allDeps = new HashSet<>(Arrays.asList(factory.getDependenciesForBean(name)));
allDeps.addAll(currentBeanManualDeps);
for (String dep : allDeps) {
dot.append(" \"").append(name).append("\" -> \"").append(dep).append("\";\n");
}
} catch (Exception e) {
// Skip beans that can't be resolved
}
}
}
}
currentCtx = currentCtx.getParent();
}
dot.append("}\n");
save(dot.toString(), count);
}
private String extractValue(Object value, int depth) {
if (value == null) return "null";
if (depth > 5) return "<i>[Max Depth]</i>";
if (value instanceof BeanReference) {
String bName = ((BeanReference) value).getBeanName().trim();
currentBeanManualDeps.add(bName);
return "@" + xmlEscape(bName);
}
if (value instanceof TypedStringValue) {
String raw = ((TypedStringValue) value).getValue();
return xmlEscape(raw != null ? raw.trim() : "null");
}
// Inner Bean Expansion
if (value instanceof BeanDefinition || value instanceof BeanDefinitionHolder) {
BeanDefinition inner = (value instanceof BeanDefinitionHolder) ?
((BeanDefinitionHolder) value).getBeanDefinition() : (BeanDefinition) value;
StringBuilder tbl = new StringBuilder("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" BGCOLOR=\"#F0F0F0\">");
String cls = inner.getBeanClassName();
String label = cls != null ? cls.substring(cls.lastIndexOf('.') + 1) : "Inner";
tbl.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#D3D3D3\"><B>").append(xmlEscape(label)).append("</B></TD></TR>");
for (PropertyValue pv : inner.getPropertyValues().getPropertyValues()) {
tbl.append("<TR><TD>").append(xmlEscape(pv.getName())).append("</TD>");
tbl.append("<TD>").append(extractValue(pv.getValue(), depth + 1)).append("</TD></TR>");
}
tbl.append("</TABLE>");
return tbl.toString();
}
if (value instanceof Iterable) {
StringBuilder sb = new StringBuilder();
for (Object item : (Iterable<?>) value) {
sb.append(extractValue(item, depth + 1)).append("<BR ALIGN=\"LEFT\"/>");
}
return sb.toString();
}
if (value instanceof Map) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<?, ?> e : ((Map<?, ?>) value).entrySet()) {
sb.append(extractValue(e.getKey(), depth + 1)).append("=").append(extractValue(e.getValue(), depth + 1)).append("<BR ALIGN=\"LEFT\"/>");
}
return sb.toString();
}
return xmlEscape(value.toString());
}
private void save(String data, int count) {
try {
File root = findProjectRoot();
File buildDir = new File(root, "build");
if (!buildDir.exists()) buildDir.mkdirs();
File file = new File(buildDir, "spring-beans.dot");
try (PrintWriter out = new PrintWriter(new FileWriter(file))) {
out.print(data);
out.flush();
}
// Using System.err to bypass any potential stdout buffering
System.err.println("\n############################################################");
System.err.println("SUCCESS: DOT file written to: " + file.getAbsolutePath());
System.err.println("Beans processed: " + count);
System.err.println("File URL: " + file.toURI().toString());
System.err.println("############################################################\n");
} catch (Exception e) {
e.printStackTrace();
}
}
private File findProjectRoot() {
File f = new File(System.getProperty("user.dir"));
while (f != null && !new File(f, "build.gradle").exists() && !new File(f, "settings.gradle").exists()) {
f = f.getParentFile();
}
return f != null ? f : new File(System.getProperty("user.dir"));
}
private String xmlEscape(String s) {
if (s == null) return "";
String escaped = s.trim().replace("&", "&").replace("<", "<")
.replace(">", ">").replace("\"", """).replace("'", "'");
// Keep our intentional line breaks
return escaped.replace("<BR ALIGN="LEFT"/>", "<BR ALIGN=\"LEFT\"/>");
}
}
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)