前言

我们平时会写一些工具类, 为了方便调用, 一般将工具类中的方法定义为静态方法, 同时又可能需要读取配置文件中某些配置项的值, 由于方法是静态的, 这些变量也必须是静态的, 但是@Value注解又不支持对静态变量的注入, 至少我用的Spring Boot 2.4.2版本不行, 当然, 有很多方法可以规避这个限制, 比如把@Value打在setter方法上, 或者利用@PostConstruct在类实例化之后通过中间变量赋值给静态变量, 然而这样代码可能略显繁琐:

/**
     * 通过setter注入
     */
    private static String STATIC_VALUE;
    
    @Value("${static.prop}")
    public void setStaticValue(String staticValue) {
        TestStaticValueInject.STATIC_VALUE = staticValue;
    }

    /**
     * 通过@PostConstruct注入
     */
    private static String STATIC_VALUE;

    @Value("${static.prop}")
    private String tempStaticValue;

    @PostConstruct
    public void init() {
        TestStaticValueInject.STATIC_VALUE = this.tempStaticValue;
    }

因此我希望实现静态属性直接用@Value注入, 如:

@Value("${static.prop}")
    private static String STATIC_VALUE;

实现

我们可以模仿@MapperScan实现一个注解打在启动类上让它去自动扫描然后注入.

1. 创建注册器类

首先创建一个类, 需要实现三个接口ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware

ImportBeanDefinitionRegistrar实现此接口的类有注册bean的能力, 由于静态属性不属于bean, 而是属于这个bean的类, 因此实际上不用注册扫描到的bean; 需要@Import引用才能生效.
EnvironmentAware, ResourceLoaderAware实现这两个接口可以获取应用的运行环境和资源路径加载器, 这里主要用来获取配置微间的属性, 扫描和加载类, 实现时就是实现一下setter方法.

2. 实现方法

实现registerBeanDefinitions方法, 可以在这里面扫描bean, 获取它的类; 获取到类就好操作了, 拿到类的Field[]后直接set值, environment.resolvePlaceholders()方法可以处理el表达式, 完整代码如下:

@Component
public class StaticValueInjector implements ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware {
    private Environment environment;
    private ResourceLoader resourceLoader;
    private ClassLoader classLoader;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
        this.classLoader = Objects.requireNonNull(resourceLoader.getClassLoader());
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 暂时使用默认过滤器, 扫描启动类下所有包
        new ClassPathBeanDefinitionScanner(registry, true, environment, resourceLoader)
                .findCandidateComponents(ClassUtils.getPackageName(importingClassMetadata.getClassName()))
                .forEach(this::inject);
    }

    /**
     * 向该bean中的静态变量注入值
     *
     * @param beanDefinition bean
     */
    public void inject(BeanDefinition beanDefinition) {
        try {
            // 获取bean中的属性, 过滤非静态变量以及不需要注入的属性
            Arrays.stream(classLoader.loadClass(beanDefinition.getBeanClassName()).getDeclaredFields())
                    .filter(f -> Modifier.isStatic(f.getModifiers()) && f.isAnnotationPresent(Value.class))
                    .forEach(v -> {
                        try {
                            v.setAccessible(true);
                            // 处理el表达式
                            v.set(null, environment.resolvePlaceholders(v.getAnnotation(Value.class).value()));
                        } catch (IllegalAccessException e) {
                            throw new BusinessException(e.getMessage());
                        }
                    });
        } catch (ClassNotFoundException e) {
            throw new BusinessException(e.getMessage());
        }
    }
}
3. 定义注解引用注册器类

要让注册器类生效需要定义一个注解, 用@Import引入, 如:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(StaticValueInjector.class)
public @interface EnableStaticValueInjection {
}

使用和测试

首先要在启动类上打上新定义的注解:

@EnableStaticValueInjection
@SpringBootApplication
public class NotesApplication {
    public static void main(String[] args) {
        SpringApplication.run(NotesApplication.class, args);
    }
}

然后可以直接使用@Value打在静态属性上了, 顺便测试一下:

@Slf4j
@Component
public class TestStaticValueInject {

    @Value("${static.prop}")
    private static String STATIC_VALUE;

    @PostConstruct
    public void test() {
        log.warn("测试静态注入: {}", STATIC_VALUE);
    }
}

配置文件配置项:

static:
  prop: 测试

测试结果及总结:

spring AOP 静态类切入点 不生效 spring注入静态类_java


是可以注入成功的, 但是会有一条日志: 静态属性不支持自动注入, Spring确实不推荐也不支持静态变量的自动注入, 然而我们是利用反射注入的, 如果觉得这条日志烦人, 可以自定义一个注解专门用于标记, 只要在获取属性时获取该注解下面的属性就好了, 使用上和Spring的@Value基本一样;

不知道大伙还有没有好方法, 这应该不是一个比较好的实现