Spring—@Value在static中引用方式

 更新时间:2023年09月21日 10:07:01   作者:花无名v5  
这篇文章主要介绍了Spring—@Value在static中引用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

@Value注解

spring在读取yml、properties等文件中的配置时,可直接使用@Value注解。

而且@Value除了支持String,int等类型的数据,还支持数组、Map、bean多种类型数据注入,应用起来非常方便。

不过在使用这个注解的过程中也有需要注意的点。

其中一点就是静态属性的注入时机,如果使用方法不当,静态属性无法完成属性注入;第二点是需要用什么方式,才可以在在静态方法、静态代码块中获取配置的属性信息。

本文主要是对@Value在这两种情况下的使用进行说明,同时简单讲解一下@Value注入的原理。

代码加载顺序

在进入@Value使用介绍之前,先说下对于包含静态方法、静态代码块、@PostConstruct方法、默认构造方法的代码执行顺序是什么样的。

static修饰的静态方法、代码块、静态属性,都是在类加载的时候就进行加载,并且静态代码块是会主动执行,静态方法可以直接通过类名引用。

类实例是在类加载之后才进行的。

下边的例子针对静态代码块和默认构造参数、PostConstruct方法进行对比,根据输出结果可知静态代码块是先加载,然后是默认构造函数,最后才是PostConstruct修饰的方法。所以如果想要在静态代码块中使用spring注入的属性需要做些特殊处理(后边会讲到)。

@Component
public class FuncLoadOrderService {
    //记录每个步骤执行的顺序
    private static AtomicLong step = new AtomicLong(0);
    //默认构造参数
    public FuncLoadOrderService() {
        System.out.println("construct run step " + step.getAndIncrement());
    }
    //静态代码块
    static {
        System.out.println("static block run step " + step.getAndIncrement());
    }
    //
    @PostConstruct
    public void constructFunc() {
        System.out.println("PostConstruct run step " + step.getAndIncrement());
    }
}

结果输出

static block run step 0
construct run step 1
PostConstruct run step 2

@Value属性注入

普通属性注入

此处说的普通属性为非static变量,类似如下声明

//这就是一个普通属性
private String testConfigId;
  • 结论先行。
  • 对于这种注入方式,由于实例是在默认构造参数执行之后才会创建,且方法加载顺序为 静态代码块 --> 静态方法 --> 默认构造参数 --> PostConstruct修饰的方法。
  • 所以普通属性使用@Value注入的变量,只有在PostConstruct修饰的方法可以取到值,即只有对象bean完成了初始化才可以获取到配置值。
  • 想在PostConstruct前的几步中取到值需要直接读取配置文件,加载内容。

属性的普通注入方式如下,直接使用@Value注解就可以注入配置文件中的配置。

@Value("${test.configId}")
private String testConfigId;

这种方式是工作中比较常用的注入方式了,但是因为value是在类实例创建之后才注入的,

所以这里有两个注意点

  • 1.默认构造参数中无法获取注入的value
  • 2.static修饰的方法无法获取注入的value

如果一定想要在默认构造参数里获取@Value注入的值怎么办呢?想一想为什么默认构造参数无法使用@Value注入的值~~

是不是因为此时bean还没有创建,类对象还没有实例化,所以所有依赖Bean创建方式来注入值的方式都不可以使用,因此可以考虑直接读取配置文件来获取值。

这里提供两种方式。第一种是使用 YamlMapFactoryBean 将配置文件的内容读到map中

YamlMapFactoryBean yaml = new YamlMapFactoryBean();
yaml.setResources(new ClassPathResource("application.yml"));
Map<String, Object> configMap = yaml.getObject();

这个方式可以达到目的,但是数据是嵌套的map,没有按照key展开,使用起来不是很方便。

比如配置文件中内容如下,如果用YamlMapFactoryBean方式读配置,只能先 map.get(“test”) 获取返回值,进行类型转换,再做其他处理

test:
  configId: 1oiieuu
  configMap: "{\"id\":123,\"key\":12333}"

第二种方式和第一个类似,只不过读出来的数据是按照key做了展开。

这种方式使用的是 YamlPropertiesFactoryBean

YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("application.yml"));
Properties properties = yamlProperties.getObject();

对于上边提到的同样的配置,如果想读configId,可以直接使用 properties.get(“test.configId”) / properties.getProperty(“test.configId”) 等方法。

使用 YamlPropertiesFactoryBean 的方式最接近直接用@Value,强烈推荐!!

static块、默认构造方法获取@Value值

对于static代码块和默认构造方法,想要获取配置文件中的值,只能通过将配置文件读到内存,转成map、properties等方法(也可以转json、yaml等),来获取值。

静态方法中获取@Value值

上边讲到普通的变量使用@Value修饰,在静态方法中获取不到值,是因为静态方法中想要获取普通变量,需要用new来创建对象,new出来的对象没有注入@Value,所以想要在静态方法中使用@Value修饰的对象,需要把对象定义为static类型,

代码如下:

private static String configId;
@Value("${test.configId}")
public void setConfigId(String configId) {
      FuncLoadOrderService.configId = configId;
}
public static void staticFuncTest() {
      System.out.println("static func " + FuncLoadOrderService.configId);
}

原理

上边我们先给出了在不同情况下使用@Value会出现的情况,并给出了解决方案,下边我们来简单看下@Value为什么有些情况不能完成value注入。

  • 结论先行。
  • 对于@Value修饰的属性、方法,在底层处理的时候和@Autowired处理逻辑是一样的。
  • 处理逻辑是由AutowiredAnnotationBeanPostProcessor类的内部类,AutowiredFieldElement 和 AutowiredMethodElement进行具体逻辑处理。
  • 其中AutowiredFieldElement处理使用了相关注解的属性
  • AutowiredMethodElement处理使用了相关注解的方法
  • 以属性处理链路为例,给出处理逻辑的调用链
  • AutowiredFieldElement#inject#resolveDependency#doResolveDependency#convertIfNecessary
//AutowiredAnnotationBeanPostProcessor默认构造方法,把此类可以处理的类型加入到列表中,从代码中可以看到这个类可以处理 @Value、@Autowired、@Inject三种注解
public AutowiredAnnotationBeanPostProcessor() {
		this.autowiredAnnotationTypes.add(Autowired.class);
		this.autowiredAnnotationTypes.add(Value.class);
		try {
			this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
					ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
			logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

内部类AutowiredFieldElement的关键源码如下。

从源码中可看到处理逻辑都是针对bean来进行处理,而static修饰的方法、属性,是在bean中获取不到,所以static属性使用@Value无法注入对应的值。

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
      Field field = (Field) this.member;
      Object value;
      if (this.cached) {
            //在缓存中,直接进行属性处理
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
      }
      else {
            //设置属性信息
            DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
            desc.setContainingClass(bean.getClass());
            Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            try {
                  value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
            }
            catch (BeansException ex) {
                  throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
            }
            synchronized (this) {
                  if (!this.cached) {
                        //加入缓存处理
                  }
            }
      }
      if (value != null) {
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
      }
}

AutowiredMethodElement和AutowiredFieldElement一样,都是继承自 InjectionMetadata.InjectedElement,所以对于使用@Value的方法能够完成注入。

下边这段代码中configId能够注入,就是因为@Value使用在方法上,在bean加载时会将configId装配到bean中。

private static String configId;
@Value("${test.configId}")
public void setConfigId(String configId) {
      FuncLoadOrderService.configId = configId;
}
public static void staticFuncTest() {
      System.out.println("static func " + FuncLoadOrderService.configId);
}

结论

@Value是平时用的比较多的注解,使用时也会遇到某些情况注入失败,所以进行了一番了解并将了解的结论分享给大家。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java中transient关键字的详细总结

    Java中transient关键字的详细总结

    本文要介绍的是Java中的transient关键字,transient是短暂的意思。对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略,感兴趣的朋友可以参考阅读
    2023-04-04
  • Java实现考试系统

    Java实现考试系统

    这篇文章主要为大家详细介绍了Java实现考试系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • SpringCloud使用feign调用错误的问题

    SpringCloud使用feign调用错误的问题

    这篇文章主要介绍了SpringCloud使用feign调用错误的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • JAVA中的OutputStreamWriter流解析

    JAVA中的OutputStreamWriter流解析

    这篇文章主要介绍了JAVA中的OutputStreamWriter流解析,OutputStreamWriter提供了一种方便的方式将字符数据写入到输出流中,并进行字符编码转换,它是Java中处理字符流和字节流之间转换的重要工具之一,需要的朋友可以参考下
    2023-10-10
  • spring security自定义决策管理器

    spring security自定义决策管理器

    这篇文章主要介绍了spring security自定义决策管理器的实现代码,需要的朋友参考下吧
    2017-09-09
  • java接收文件流+response.body()调用两次问题(分别接收文件和对象)

    java接收文件流+response.body()调用两次问题(分别接收文件和对象)

    这篇文章主要介绍了java接收文件流+response.body()调用两次问题(分别接收文件和对象),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • 完美解决Get和Post请求中文乱码的问题

    完美解决Get和Post请求中文乱码的问题

    下面小编就为大家带来一篇完美解决Get和Post请求中文乱码的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-05-05
  • java并发编程_线程池的使用方法(详解)

    java并发编程_线程池的使用方法(详解)

    下面小编就为大家带来一篇java并发编程_线程池的使用方法(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • spring boot如何加入mail邮件支持

    spring boot如何加入mail邮件支持

    这篇文章主要介绍了spring boot如何加入mail邮件支持,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java基础之反射原理与用法详解

    Java基础之反射原理与用法详解

    这篇文章主要介绍了Java基础之反射原理与用法,结合实例形式详细分析了java反射的相关概念、原理、使用方法与操作注意事项,需要的朋友可以参考下
    2020-02-02

最新评论