package com.yourpackage;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.SmartLifecycle;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.*;
public class SpringVisualizer implements ApplicationContextAware, SmartLifecycle {
private ConfigurableApplicationContext context;
private boolean isRunning = false;
private final Set<String> currentBeanManualDeps = new HashSet<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = (ConfigurableApplicationContext) applicationContext;
}
@Override
public void start() {
StringBuilder dot = new StringBuilder("digraph G {\n");
// Simplified engine settings for better stability in Edotor
dot.append(" rankdir=LR; nodesep=1.0; ranksep=2.0; splines=true;\n");
dot.append(" node [shape=none, fontname=\"Arial\", fontsize=11];\n");
dot.append(" edge [fontname=\"Arial\", fontsize=9, color=\"#777777\"];\n\n");
Set<String> processed = 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 (name.startsWith("org.springframework")) continue;
if (processed.add(name)) {
try {
BeanDefinition def = factory.getBeanDefinition(name);
currentBeanManualDeps.clear();
count++;
String color = name.toLowerCase().contains("comet") ? "#FFF9C4" :
(name.toLowerCase().contains("cpls") ? "#C8E6C9" : "#E1F5FE");
dot.append(" \"").append(name).append("\" [label=<<TABLE BORDER=\"1\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"5\" BGCOLOR=\"").append(color).append("\">");
// Header: Using a darker shade and internal border to separate from body
dot.append("<TR><TD BGCOLOR=\"#A0A0A0\" ALIGN=\"CENTER\" BORDER=\"1\"><B> ").append(xmlEscape(name)).append(" </B></TD></TR>");
for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
dot.append("<TR><TD ALIGN=\"LEFT\" BORDER=\"1\">");
dot.append("<B>").append(xmlEscape(pv.getName())).append(":</B> ");
dot.append(extractValue(pv.getValue(), 0));
dot.append("</TD></TR>");
}
dot.append("</TABLE>>];\n");
Set<String> allDeps = new HashSet<>(Arrays.asList(factory.getDependenciesForBean(name)));
allDeps.addAll(currentBeanManualDeps);
for (String dep : allDeps) {
if (!dep.startsWith("org.springframework")) {
dot.append(" \"").append(name).append("\" -> \"").append(dep).append("\";\n");
}
}
} catch (Exception ignored) {}
}
}
}
currentCtx = currentCtx.getParent();
}
dot.append("}\n");
writeToFile(dot.toString());
this.isRunning = true;
}
private String extractValue(Object value, int depth) {
if (value == null) return "null";
if (depth > 1) return "..."; // Drastically reduced depth to avoid the "text wall" crash
if (value instanceof BeanReference) {
String bName = ((BeanReference) value).getBeanName();
currentBeanManualDeps.add(bName);
return "@" + xmlEscape(bName);
}
if (value instanceof TypedStringValue) {
return truncate(xmlEscape(((TypedStringValue) value).getValue()));
}
// Avoid expanding the full BeanDefinition string which caused the overflow
if (value instanceof BeanDefinition || value instanceof BeanDefinitionHolder) {
return "<i>[InnerBean]</i>";
}
if (value instanceof Iterable) {
return "[List]";
}
return truncate(xmlEscape(value.toString()));
}
private String truncate(String s) {
if (s == null) return "";
int limit = 45; // Tighter limit to ensure boxes don't overflow the browser view
if (s.length() <= limit) return s;
return s.substring(0, limit) + "...";
}
private String xmlEscape(String input) {
if (input == null) return "";
// Aggressive cleaning of non-printable characters that often cause Edotor red errors
String clean = input.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", "");
return clean.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
private void writeToFile(String content) {
try {
File buildDir = new File(System.getProperty("user.dir"), "build");
if (!buildDir.exists()) buildDir.mkdirs();
File file = new File(buildDir, "spring-beans.dot");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
writer.write(content);
}
} catch (Exception e) { e.printStackTrace(); }
}
@Override public int getPhase() { return Integer.MAX_VALUE; }
@Override public boolean isAutoStartup() { return true; }
@Override public void stop() { this.isRunning = false; }
@Override public boolean isRunning() { return this.isRunning; }
@Override public void stop(Runnable callback) { stop(); callback.run(); }
}
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)