如何从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
相关产品推荐
相关产品推荐

