DEV Community

Query Filter
Query Filter

Posted on

bridge113

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");
        // Using 'newrank' and 'pad' to give the graph more room to breathe
        dot.append("  rankdir=LR; nodesep=1.0; ranksep=2.0; splines=true; pad=0.5; newrank=true;\n");
        dot.append("  node [shape=none, fontname=\"Verdana\", fontsize=10];\n");
        dot.append("  edge [fontname=\"Verdana\", fontsize=8, color=\"#666666\"];\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=<");
                            // CELLSPACING=0 is critical to prevent the 'double border' overstrike look
                            dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"6\" BGCOLOR=\"").append(color).append("\">");

                            // HEADER: Centered and Bold. We don't set a width here; the rows below will push it out.
                            dot.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#999999\" ALIGN=\"CENTER\"><B>").append(xmlEscape(name)).append("</B></TD></TR>");

                            for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
                                dot.append("<TR>");
                                dot.append("<TD ALIGN=\"LEFT\" VALIGN=\"TOP\"><B>").append(xmlEscape(pv.getName())).append("</B></TD>");
                                // We wrap the value in another table if it's complex, otherwise keep it simple
                                dot.append("<TD ALIGN=\"LEFT\" VALIGN=\"TOP\">").append(extractValue(pv.getValue(), 0)).append("</TD>");
                                dot.append("</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 > 2) return "..."; // Prevent the infinite expansion seen in your image

        if (value instanceof BeanReference) {
            String bName = ((BeanReference) value).getBeanName();
            currentBeanManualDeps.add(bName);
            return "@" + xmlEscape(bName);
        }

        if (value instanceof TypedStringValue) {
            return xmlEscape(((TypedStringValue) value).getValue());
        }

        // Handle inner beans without the massive toString() dump
        if (value instanceof BeanDefinition || value instanceof BeanDefinitionHolder) {
            BeanDefinition inner = (value instanceof BeanDefinitionHolder) ? 
                                   ((BeanDefinitionHolder) value).getBeanDefinition() : (BeanDefinition) value;
            String cls = inner.getBeanClassName();
            return "<i>[Bean: " + (cls != null ? xmlEscape(cls.substring(cls.lastIndexOf('.') + 1)) : "Inner") + "]</i>";
        }

        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();
        }

        // Limit string length to prevent the massive horizontal overflow
        String str = value.toString();
        if (str.length() > 60) {
            str = str.substring(0, 57) + "...";
        }
        return xmlEscape(str);
    }

    private String xmlEscape(String input) {
        if (input == null) return "";
        return input.replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace("\"", "&quot;")
                    .replace("'", "&apos;")
                    .replace("&lt;BR ALIGN=&quot;LEFT&quot;/&gt;", "<BR ALIGN=\"LEFT\"/>");
    }

    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);
            }
            System.err.println("SUCCESS: DOT file written to: " + file.getAbsolutePath());
        } 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(); }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)