帮你猜猜你为何有如此需求?
从上面示例类的命名中,我或许能猜出你的用意。UserHelper它被命名为一个工具类,而一般我们对工具类的理解是:
- 方法均为static工具方法
- 使用越便捷越好
- 很明显,static方法使用是最便捷的嘛
现状是:使用UserHelper去处理用户信息还得先@Autowired注入它的实例,实属不便。因此你想方设法的想把getAndFilterTest()这个方法变为静态方法,这样通过类名便可直接调用而并不再依赖于注入UserHelper实例了,so你想当然的这么“优化”:
@Component public class UserHelper { @Autowired static UCClient ucClient; public static List<User> getAndFilterTest(List<Long> userIds) { ... // 处理逻辑完全同上 } }
属性和方法都添加上static修饰,这样使用方通过类名便可直接访问(无需注入):
@Service public class RoomService { public void create(List<Long> studentIds, Long teacherId) { ... // 通过类名直接调用其静态方法 List<User> users = UserHelper.getAndFilterTest(userIds); ... } }
运行程序,结果输出:
07:22:49.359 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient 07:22:49.359 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient ... Exception in thread "main" java.lang.NullPointerException at cn.yourbatman.temp.component.UserHelper.getAndFilterTest(UserHelper.java:23) at cn.yourbatman.temp.component.RoomService.create(RoomService.java:26) at cn.yourbatman.temp.DemoTest.main(DemoTest.java:19)
以为天衣无缝,可结果并不完美,抛异常了。我特意多粘贴了两句info日志,它们告诉了你为何抛出NPE异常的原因:@Autowired不支持标注在static字段/属性上。
为什么@Autowired不能注入static成员属性
静态变量是属于类本身的信息,当类加载器加载静态变量时,Spring的上下文环境还没有被加载,所以不可能为静态变量绑定值(这只是最表象原因,并不准确)。同时,Spring也不鼓励为静态变量注入值(言外之意:并不是不能注入),因为它认为这会增加了耦合度,对测试不友好。
这些都是表象,那么实际上Spring是如何“操作”的呢?我们沿着AutowiredAnnotationBeanPostProcessor输出的这句info日志,倒着找原因,这句日志的输出在这:
AutowiredAnnotationBeanPostProcessor: // 构建@Autowired注入元数据方法 // 简单的说就是找到该Class类下有哪些是需要做依赖注入的 private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) { ... // 循环递归,因为父类的也要管上 do { // 遍历所有的字段(包括静态字段) ReflectionUtils.doWithLocalFields(targetClass, field -> { if (Modifier.isStatic(field.getModifiers())) { logger.info("Autowired annotation is not supported on static fields: " + field); } return; ... }); // 遍历所有的方法(包括静态方法) ReflectionUtils.doWithLocalMethods(targetClass, method -> { if (Modifier.isStatic(method.getModifiers())) { logger.info("Autowired annotation is not supported on static methods: " + method); } return; ... }); ... targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); ... }
这几句代码道出了Spring为何不给static静态字段/静态方法执行@Autowired注入的最真实原因:扫描Class类需要注入的元数据的时候,直接选择忽略掉了static成员(包括属性和方法)。
那么这个处理的入口在哪儿呢?是否在这个阶段时Spring真的无法给static成员完成赋值而选择忽略掉它呢,我们继续最终此方法的调用处。此方法唯一调用处是findAutowiringMetadata()方法,而它被调用的地方有三个:
调用处一:执行时机较早,在MergedBeanDefinitionPostProcessor处理bd合并期间就会解析出需要注入的元数据,然后做check。它会作用于每个bd身上,所以上例中的2句info日志第一句就是从这输出的
AutowiredAnnotationBeanPostProcessor: @Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); metadata.checkConfigMembers(beanDefinition); }
调用处二:在InstantiationAwareBeanPostProcessor
也就是实例创建好后,给属性赋值阶段(也就是populateBean()
阶段)执行。所以它也是会作用于每个bd的,上例中2句info日志的第二句就是从这输出的
AutowiredAnnotationBeanPostProcessor: @Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); } ... return pvs; }
调用处三:这个方法比较特殊,它表示对于带有任意目标实例(已经不仅是Class,而是实例本身)直接调用的“本地”处理方法实行注入。这是Spring提供给“外部”使用/注入的一个public公共方法,比如给容器外的实例注入属性,还是比较实用的,本文下面会介绍它的使用办法
说明:此方法Spring自己并不会主动调用,所以不会自动输出日志(这也是为何调用处有3处,但日志只有2条的原因)
AutowiredAnnotationBeanPostProcessor: public void processInjection(Object bean) throws BeanCreationException { Class<?> clazz = bean.getClass(); InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null); try { metadata.inject(bean, null, null); } ... }
通过这部分源码,从底层诠释了Spring为何不让你@Autowired
注入static成员的原因。既然这样,难道就没有办法满足我的“诉求”了吗?答案是有的,接着往下看。