You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Java静态工具类自动生成Spring Bean?

自动生成静态工具类的Spring Bean代理方案

一、Lombok能否实现?

Lombok无法直接完成这个需求。它的核心能力是编译时生成Getter/Setter、构造器等常规Java代码,没有提供批量包装静态方法为实例方法并生成Spring Bean的注解或扩展机制,无法覆盖这类定制化的静态工具类代理场景。

二、可行的自动实现方案

1. 运行时动态代理(Spring FactoryBean + CGLIB)

通过自定义FactoryBean结合CGLIB动态代理,在Spring容器启动时自动生成代理类,包装静态方法调用,无需手动编写代理代码。

步骤1:实现通用代理FactoryBean

import org.springframework.beans.factory.FactoryBean;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class StaticUtilProxyFactoryBean<T> implements FactoryBean<T> {
    private final Class<T> targetUtilClass;

    public StaticUtilProxyFactoryBean(Class<T> targetUtilClass) {
        this.targetUtilClass = targetUtilClass;
    }

    @Override
    public T getObject() throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Object.class);
        enhancer.setInterfaces(new Class[]{targetUtilClass});
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> 
            targetUtilClass.getMethod(method.getName(), method.getParameterTypes())
                          .invoke(null, args));
        return (T) enhancer.create();
    }

    @Override
    public Class<?> getObjectType() {
        return targetUtilClass;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

步骤2:批量注册代理Bean

通过Spring配置类自动扫描指定包下的静态工具类,批量注册代理Bean:

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;

@Configuration
public class StaticUtilAutoConfig implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        String utilPackage = "com.yourpackage.utils"; // 替换为你的静态工具类所在包
        var metadataReaderFactory = new CachingMetadataReaderFactory(new PathMatchingResourcePatternResolver());

        try {
            // 扫描包下符合条件的静态工具类
            Set<String> utilClassNames = metadataReaderFactory.getMetadataReaderFactory()
                    .getAllMetadataReaders(utilPackage)
                    .stream()
                    .map(MetadataReader::getClassMetadata)
                    .map(classMetadata -> classMetadata.getClassName())
                    .filter(this::isStaticUtilClass)
                    .collect(Collectors.toSet());

            // 为每个工具类注册代理Bean
            for (String className : utilClassNames) {
                Class<?> utilClass = Class.forName(className);
                GenericBeanDefinition beanDef = new GenericBeanDefinition();
                beanDef.setBeanClass(StaticUtilProxyFactoryBean.class);
                beanDef.getConstructorArgumentValues().addIndexedArgumentValue(0, utilClass);
                
                String beanName = className.substring(className.lastIndexOf(".") + 1);
                beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1) + "Bean";
                registry.registerBeanDefinition(beanName, beanDef);
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 判断是否为纯静态工具类(无公开构造器、所有方法为静态)
    private boolean isStaticUtilClass(String className) throws ClassNotFoundException {
        Class<?> clazz = Class.forName(className);
        return clazz.getConstructors().length == 0 
                && clazz.getDeclaredMethods().length > 0 
                && java.util.Arrays.stream(clazz.getDeclaredMethods()).allMatch(Method::isStatic);
    }
}

2. 编译时代码生成(自定义注解处理器)

通过自定义注解和注解处理器,在编译阶段自动生成与静态工具类对应的Spring Bean代理类,生成的代码和手动编写的完全一致,无运行时开销。

步骤1:定义标记注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface StaticUtilProxy {
    String suffix() default "Bean"; // 生成Bean类名的后缀,默认是Bean
}

步骤2:给静态工具类添加注解

@StaticUtilProxy
public class Util {
   public static void op1(String param_string) {
      System.out.println("op1 " + param_string);
   }

   public static void op2(int param_int) {
      System.out.println("op2 " + param_int);
   }
}

步骤3:实现注解处理器

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;

@SupportedAnnotationTypes("com.yourpackage.annotation.StaticUtilProxy")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class StaticUtilProxyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(StaticUtilProxy.class)) {
            if (!(element instanceof TypeElement)) continue;

            TypeElement utilClass = (TypeElement) element;
            StaticUtilProxy annotation = element.getAnnotation(StaticUtilProxy.class);
            String suffix = annotation.suffix();
            String utilClassName = utilClass.getSimpleName().toString();
            String utilFullName = utilClass.getQualifiedName().toString();
            String packageName = utilFullName.substring(0, utilFullName.lastIndexOf("."));
            String beanClassName = utilClassName + suffix;
            String beanFullName = packageName + "." + beanClassName;

            try {
                JavaFileObject file = processingEnv.getFiler().createSourceFile(beanFullName);
                try (Writer writer = file.openWriter()) {
                    // 生成代理类代码
                    writer.write("package " + packageName + ";\n");
                    writer.write("\n");
                    writer.write("import org.springframework.stereotype.Component;\n");
                    writer.write("\n");
                    writer.write("@Component\n");
                    writer.write("public class " + beanClassName + " {\n");
                    writer.write("\n");

                    // 遍历所有静态方法,生成代理方法
                    for (Element enclosedElement : utilClass.getEnclosedElements()) {
                        if (!enclosedElement.getKind().isMethod() || !enclosedElement.getModifiers().contains(javax.lang.model.element.Modifier.STATIC)) {
                            continue;
                        }
                        ExecutableElement method = (ExecutableElement) enclosedElement;
                        String methodName = method.getSimpleName().toString();
                        String returnType = method.getReturnType().toString();

                        // 拼接参数列表
                        StringBuilder params = new StringBuilder();
                        StringBuilder paramNames = new StringBuilder();
                        for (int i = 0; i < method.getParameters().size(); i++) {
                            var param = method.getParameters().get(i);
                            params.append(param.asType()).append(" ").append(param.getSimpleName());
                            paramNames.append(param.getSimpleName());
                            if (i < method.getParameters().size() - 1) {
                                params.append(", ");
                                paramNames.append(", ");
                            }
                        }

                        writer.write("    public " + returnType + " " + methodName + "(" + params + ") {\n");
                        writer.write("        " + utilFullName + "." + methodName + "(" + paramNames + ");\n");
                        if (!returnType.equals("void")) {
                            writer.write("        return " + utilFullName + "." + methodName + "(" + paramNames + ");\n");
                        }
                        writer.write("    }\n");
                        writer.write("\n");
                    }

                    writer.write("}\n");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}

总结

  • Lombok不支持批量生成静态工具类的Spring Bean代理,无法满足需求。
  • 推荐两种方案:
    • 运行时动态代理:无需额外编译配置,实现灵活,适合快速迭代场景;
    • 编译时代码生成:生成的代码直观可控,无运行时性能开销,适合追求稳定性的场景。

内容的提问来源于stack exchange,提问作者Nicholas DiPiazza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 07:34:58