DEV Community

Query Filter
Query Filter

Posted on

bridge108


Enter fullscreen mode Exit fullscreen mode

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.;
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 {
    System.err.println("!!! REGENERATING EXPANDED GRAPH !!!");
    generateGraph();
}

public void generateGraph() {
    StringBuilder dot = new StringBuilder("digraph G {\n");
    // 'nodesep' and 'ranksep' help prevent overlapping when beans have many properties
    dot.append("  rankdir=LR; nodesep=0.5; ranksep=1.5; splines=true;\n");
    dot.append("  node [shape=none, fontname=\"Verdana\", fontsize=11];\n");
    dot.append("  edge [fontname=\"Verdana\", fontsize=9, 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()) {

                // Filter out internal Spring beans to keep the focus on COMET/CPLS
                if (name.startsWith("org.springframework") || name.contains("AutoProxyCreator")) 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=<");
                        // CELLPADDING prevents text from touching the borders
                        dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\" BGCOLOR=\"").append(color).append("\">");
                        // Header: Using a nested table for the header ensures it spans properly without overflow
                        dot.append("<TR><TD BGCOLOR=\"#999999\"><B>").append(safeXml(name)).append("</B></TD></TR>");

                        for (PropertyValue pv : def.getPropertyValues().getPropertyValues()) {
                            dot.append("<TR><TD ALIGN=\"LEFT\">");
                            dot.append("<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\">");
                            dot.append("<TR><TD ALIGN=\"LEFT\"><B>").append(safeXml(pv.getName())).append(":</B></TD>");
                            dot.append("<TD ALIGN=\"LEFT\">").append(extractValue(pv.getValue(), 0)).append("</TD></TR>");
                            dot.append("</TABLE></TD></TR>");
                        }
                        dot.append("</TABLE>>];\n");

                        // Dependencies
                        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 e) {}
                }
            }
        }
        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>[...]</i>";

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

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

    // FULL RECURSIVE EXPANSION for inBoundEF, etc.
    if (value instanceof BeanDefinition || value instanceof BeanDefinitionHolder) {
        BeanDefinition inner = (value instanceof BeanDefinitionHolder) ? 
                               ((BeanDefinitionHolder) value).getBeanDefinition() : (BeanDefinition) value;

        StringBuilder sb = new StringBuilder("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"2\" BGCOLOR=\"#FFFFFF\">");
        String cls = inner.getBeanClassName();
        String label = (cls != null) ? cls.substring(cls.lastIndexOf('.') + 1) : "InnerBean";
        sb.append("<TR><TD COLSPAN=\"2\" BGCOLOR=\"#EEEEEE\"><I>").append(safeXml(label)).append("</I></TD></TR>");

        for (PropertyValue pv : inner.getPropertyValues().getPropertyValues()) {
            sb.append("<TR><TD>").append(safeXml(pv.getName())).append("</TD>");
            sb.append("<TD>").append(extractValue(pv.getValue(), depth + 1)).append("</TD></TR>");
        }
        sb.append("</TABLE>");
        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();
    }

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

    return safeXml(value.toString());
}

private void save(String data, int count) {
    try {
        File f = new File(findRoot(), "build/spring-beans.dot");
        f.getParentFile().mkdirs();
        try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)))) {
            out.print(data);
        }
        System.err.println("SUCCESS: Expanded graph saved with " + count + " beans.");
    } catch (Exception e) { e.printStackTrace(); }
}

private File findRoot() {
    File f = new File(System.getProperty("user.dir"));
    while (f != null && !new File(f, "build.gradle").exists()) f = f.getParentFile();
    return f != null ? f : new File(System.getProperty("user.dir"));
}

private String safeXml(String s) {
    if (s == null) return "";
    return s.trim().replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
}
Enter fullscreen mode Exit fullscreen mode

}

Top comments (0)