static属性能用@Autowired注入吗?

本文内容:

1、起因

2、static

3、@Autowired

4、解决方案

先说结论:不能。

||  起因

从某系统交接过来一部分功能的代码,其中包含了一个工具类,该工具类中用到了一个Component的方法。 原系统的实现是:

public class CityUtil {
    public static List<City> getCitiesByProvinceId(Integer provinceId){
        return SpringBeanFactory.getBean(LocalComponent.class).getCities(201);
    }
}
@Component
public class LocalComponent {
    public List<City> getCities(Integer provinceId){
        // 实现逻辑略
        return null;
    }
}

新工程里没有类似SpringBeanFactory.getBean(Class class)的工具方法,也不能在Spring初始化的时候setApplicationContext(新工程使用的是自研的web框架,和Spring的整合是在框架内部实现的), 所以也就没细想,直接使用 @Autowired 注入了LocalComponent

public class CityUtil {
    @Autowired
    private static LocalComponent localComponent;
    public static List<City> getCitiesByProvinceId(Integer provinceId){
      return localComponent.getCities(201);
    }
}

细心的朋友可能已经看出问题了,上面第5行在运行时会报空指针,因为 @Autowired 注入根本没生效。localComponent为空无非有2种原因,要么注入的就是空,要么没注入。所以要先了解一下 static  和 @Autowired 各自的特性,看看到底哪里用错了。

||  static

| static 是什么?

static 是java的一个关键字,很多书里也叫修饰符,可以用来修饰属性、方法、代码块、内部类。Java关键字可以在这里查看:

https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html

然而我在官网并没有找到专门讲static的章节,但是发现在类设计中的4个小节里,都涉及到了static关键字的使用,日常的使用场景也都涵盖了。

https://docs.oracle.com/javase/tutorial/extra/certification/javase-8-programmer2.html

1.6章节。

文档中提到,它修饰的内容都是属于class的(要注意class、object、instance的区别)。这些在class加载的时候就会分配内存空间,就是JVM规范里的方法区。所以一个项目中不应该有大量带有static的类,或者用static修饰占用很大空间的变量,即便Java 8 对应的HotSpot 采用元空间转移到堆空间的形式实现方法区。

看过文档之后才发现,原来所学的Java入门教程,都源自这里啊。从Hello World! 到数据类型,从运算符到语法结构,从IO到Socket这里都能够找到。

| Java不是号称一切皆对象吗,为什么又有static?

Java本身是一门面向对象的语言,日常使用最多的也是new一个object,然后通过object来使用它的属性和方法。那为什么又有static修饰的属性和方法呢。

我们都知道JVM有自己的常量池,供Java自己的一些class使用,这些在JVM启动的时候就创建好了。那我们用户自己也有类似的需求怎么办?

官网Bicycle的例子中设定了一个场景,希望有一个变量numberOfBicycles能够记录创建了多少个bicycle对象,所以这个变量属于class,不属于任何object,所有的object共享这个变量,class的每一个instance会共享同一内存空间的这个类变量。

对此,我总结了3点:

职责划分:这个计数不属于某一个object,所有的object是独立的,相互是不通信的,所以这个只能归属于类,就好比网站的计数器,它不属于某个session,所有session共享它。

节省空间:资源复用,相同的内容没必要重复的开辟内存空间。

节省时间:以时间换时间,前面提到static修饰的内容在class加载时就分配好了内存空间,所以它是以牺牲JVM启动耗时来降低使用时初始化带来的耗时。毕竟JVM启动的次数相对于对象使用的次数是可以忽略不计的。很多软件设计中用写耗时来换取读省时也体现了这一理念。

没有static可以吗?

如果不考虑上面的问题,没有static好像程序也能运行,无非就是浪费点空间,耗费点时间。

不过回想我们写个第一个Java程序Hello World! ,这是我们第一次接触static,它修饰的是main()方法。如果没有static,启动类要new出来?new出来之后如何调用呢?如果启动类里依赖了其他的类呢?一直new下去?

底层的实现已经不得而知,有兴趣的朋友可以继续深究。

||  @Autowired

@Autowired是什么?

官方文档:

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-autowired-annotation

这一节的开篇就注明了可以使用JSR330标准里的@Inject来替换Spring的@Autowired。也就是说,@Autowired和@Inject一样,都是对JSR330标准注解(依赖注入)的一种实现。

@Autowired的用法在文档里已经讲的非常清楚了,但是整个章节里也没提到能不能用在static修饰的属性或方法上。所以只能在源码里找找答案了,本章的最后说到,这类注解都是被 Spring的BeanPostProcessor处理的。

底层对static修饰属性、方法是如何处理的?

顺着上面的思路,在源码中找到对应的BeanPostProcessor -- AutowiredAnnotationBeanPostProcessor。

AutowiredAnnotationBeanPostProcessor实现了MergedBeanDefinitionPostProcessor接口,而这个接口有好几个实现类,有的从名字上就能看出是哪个注解的Processor。这里顺便说一下CommonAnnotationBeanPostProcessor,因为@Resource就是被它处理的,日后再谈论@Autowired和@Resource区别的时候就可以加上这一点,它们是被不同的BeanPostProcessor处理的。

 AutowiredAnnotationBeanPostProcessor里面重点看一下buildAutowiringMetadata方法的这几行代码就可以了

if (Modifier.isStatic(field.getModifiers())) {
      if (logger.isInfoEnabled()) {
        logger.info("Autowired annotation is not supported on static fields: " + field);
      }
      return;
    }
    ...
    if (Modifier.isStatic(method.getModifiers())) {
      if (logger.isInfoEnabled()) {
        logger.info("Autowired annotation is not supported on static methods: " + method);
      }
      return;
    }

代码在构建带有@Autowired修饰的Element的时候会过滤掉static修饰的属性和方法,所以到后面赋值的阶段就和这些static修饰的属性、方法毫无关系了。

而且这种情况还打印了日志,启动的时候怎么没发现呢?再次启动项目,还真找到了这一行日志...

Autowired annotation is not supported on static fields: private static com.test.LocalComponent com.test.CityUtil.localComponent


事后再仔细想一下,这样处理也是合理的:

1、类加载肯定是在Spring初始化bean之前,前面刚赋好的值,后面被Spring偷偷改了,也不像话

2、类属性的值被改了,就不是影响一个对象的事情了,所有的都会受影响

||  解决方案

| 能不能在XML中注入?

继续在官方文档中寻找答案,文档中给出了<bean>标签的使用说明,但仍然没有提到能不能注入static修饰的属性,所以只能参考例子试一把,看看底层的处理逻辑就知道了。

修改applicationContext.xml

<bean class="com.test.CityUtil">
    <property name="localComponent" ref="localComponent"></property>
  </bean>

文档1.3中提到,容器内的bean被表示为BeanDefinition对象,由于是从xml中解析初始化的,所以顺着这个思路debug到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader,继续找到解析<bean>的代码

7fe2480d69913a9b65a2d62ca3b57f69.png

顺着这个方法继续debug,走到下面这个方法:

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyElement(Element ele, BeanDefinition bd)

这里面会把要赋值的 propertyName和value封装成PropertyValue对象放到BeanDefinition中的propertyValueList中。

到此没有发现对static做特殊处理的逻辑,中间的逻辑快速跳过,直接去看实例化的环节。org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)

到这一步已经可以拿到CityUtil的实例对象,也没有发现对static做特殊处理的逻辑,继续看赋值环节:

org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)

org.springframework.beans.BeanWrapperImpl.BeanPropertyHandler#setValue(final Object object, Object valueToApply)

方法的最后,拿到要赋值的方法和值,invoke之后就会赋值成功,也就是调用了setter方法。到这里还是没看到对static做特殊处理的逻辑,invoke之后其实已经能看到localComponent有值了,但心里还是不踏实,赋的值是否有效?

执行了单测,通过,说明这种方式是行得通的。

不能自动注入,但可以通过XML注入,说白了就是你可以显示的set,自己随便搞,但是我不会帮你自动set。static属性值被修改时一定是你人为操作,有源可溯。

不注入了,用的时候从容器中获取

这种就是最常用的方式了。首先,需要先能拿到容器,看一下BeanFactory和ApplicationContext的类图关系:

1899abb061f15ca034f9cb3f24ad0e11.png

所以得先拿到BeanFactory 或者 ApplicationContext,然后就能通过getBean( )方法拿到我们想要的bean了。

方案一:拿到BeanFactory

@Component
public class SpringBeanFactory implements BeanFactoryAware {
    private static BeanFactory beanFactory;
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        SpringBeanFactory.beanFactory = beanFactory;
    }
    public static <T>T getBean(Class<T> clazz){
        return beanFactory.getBean(clazz);
    }
}

方案二:拿到ApplicationContext

@Component
public class ApplicationContextFactory implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextFactory.applicationContext = applicationContext;
    }
    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

两种方案都可以达到目的,但这种写法不符合编码规范,sonar会有警告。这就再次说明static修饰的属性是不希望被再次赋值的。

b4445017f54115d2fd3bec462ad001f0.png

XML注入已经很少使用了,需要注入多个属性的话,配置会变得臃肿,如果不看配置文件,可能就不知道从哪里赋的值,所以就有了@Autowired吧;

从容器中获取,比较灵活,而且更直观。

具体采用哪种形式,看个人喜好吧。

||  总结

1、有问题可以先试着官方文档中寻找答案

2、看源码一定要debug起来,否则多态会搞死你

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值