想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做(上)

简介: 想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做(上)

前言


通过本专栏前两篇的学习,相信你对static关键字在Spring/Spring Boot里的应用有了全新的认识,能够解释工作中遇到的大多数问题/疑问了。本文继续来聊聊static关键字更为常见的一种case:使用@Autowired依赖注入静态成员(属性)。


在Java中,针对static静态成员,我们有一些最基本的常识:静态变量(成员)它是属于类的,而非属于实例对象的属性;同样的静态方法也是属于类的,普通方法(实例方法)才属于对象。而Spring容器管理的都是实例对象,包括它的@Autowired依赖注入的均是容器内的对象实例,所以对于static成员是不能直接使用@Autowired注入的。


这很容易理解:类成员的初始化较早,并不需要依赖实例的创建,所以这个时候Spring容器可能都还没“出生”,谈何依赖注入呢?


这个示例,你或许似曾相识:


@Component
public class SonHolder {
    @Autowired
    private static Son son;
    public static Son getSon() {
        return son;
    }
}


然后“正常使用”这个组件:


@Autowired
private SonHolder sonHolder;
@Transaction
public void method1(){
  ...
  sonHolder.getSon().toString();
}


运行程序,结果抛错:

Exception in thread "main" java.lang.NullPointerException
  ...


很明显,getSon()得到的是一个null,所以给你扔了个NPE。


版本约定

本文内容若没做特殊说明,均基于以下版本:

  • JDK:1.8
  • Spring Framework:5.2.2.RELEASE


正文


说起@Autowired注解的作用,没有人不熟悉,自动装配嘛。根据此注解的定义,它似乎能使用在很多地方:


@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, 
  ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
  boolean required() default true;
}


本文我们重点关注它使用在FIELD成员属性上的case,标注在static静态属性上是本文讨论的中心。


说明:虽然Spring官方现在并不推荐字段/属性注入的方式,但它的便捷性仍无可取代,因此在做业务开发时它仍旧是主流的使用方式


场景描述


假如有这样一个场景需求:创建一个教室(Room),需要传入一批学生和一个老师,此时我需要对这些用户按照规则(如名字中含有test字样的示为测试帐号)进行数据合法性校验和过滤,然后才能正常走创建逻辑。此case还有以下特点:


  • 用户名字/详细信息,需要远程调用(如FeignClient方式)从UC中心获取
  • 因此很需要做桥接,提供防腐层
  • 该过滤规则功能性很强,工程内很多地方都有用到
  • 有点工具的意思有木有


阅读完“题目”感觉还是蛮简单的,很normal的一个业务需求case嘛,下面我来模拟一下它的实现。


从UC用户中心获取用户数据(使用本地数据模拟远程访问):

/**
 * 模拟去远端用户中心,根据ids批量获取用户数据
 *
 * @author yourbatman
 * @date 2020/6/5 7:16
 */
@Component
public class UCClient {
    /**
     * 模拟远程调用的结果返回(有正常的,也有测试数据)
     */
    public List<User> getByIds(List<Long> userIds) {
        return userIds.stream().map(uId -> {
            User user = new User();
            user.setId(uId);
            user.setName("YourBatman");
            if (uId % 2 == 0) {
                user.setName(user.getName() + "_test");
            }
            return user;
        }).collect(Collectors.toList());
    }
}


说明:实际情况这里可能只是一个@FeignClient接口而已,本例就使用它mock喽

因为过滤测试用户的功能过于通用,并且规则也需要收口,须对它进行封装,因此有了我们的内部帮助类UserHelper


/**
 * 工具方法:根据用户ids,按照一定的规则过滤掉测试用户后返回结果
 *
 * @author yourbatman
 * @date 2020/6/5 7:43
 */
@Component
public class UserHelper {
    @Autowired
    UCClient ucClient;
    public List<User> getAndFilterTest(List<Long> userIds) {
        List<User> users = ucClient.getByIds(userIds);
        return users.stream().filter(u -> {
            Long id = u.getId();
            String name = u.getName();
            if (name.contains("test")) {
                System.out.printf("id=%s name=%s是测试用户,已过滤\n", id, name);
                return false;
            }
            return true;
        }).collect(Collectors.toList());
    }
}


很明显,它内部需依赖于UCClient这个远程调用的结果。封装好后,我们的业务Service层任何组件就可以尽情的“享用”该工具啦,形如这样:


/**
 * 业务服务:教室服务
 *
 * @author yourbatman
 * @date 2020/6/5 7:29
 */
@Service
public class RoomService {
    @Autowired
    UserHelper userHelper;
    public void create(List<Long> studentIds, Long teacherId) {
        // 因为学生和老师统称为user 所以可以放在一起校验
        List<Long> userIds = new ArrayList<>(studentIds);
        userIds.add(teacherId);
        List<User> users = userHelper.getAndFilterTest(userIds);
        // ...  排除掉测试数据后,执行创建逻辑
        System.out.println("教室创建成功");
    }
}


书写个测试程序来模拟Service业务调用:


@ComponentScan
public class DemoTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(DemoTest.class);
        // 模拟接口调用/单元测试
        RoomService roomService = context.getBean(RoomService.class);
        roomService.create(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L), 101L);
    }
}


运行程序,结果输出:


id=2 name=YourBatman_test是测试用户,已过滤
id=4 name=YourBatman_test是测试用户,已过滤
id=6 name=YourBatman_test是测试用户,已过滤
教室创建成功

一切都这么美好,相安无事的,那为何还会有本文指出的问题存在呢?正所谓“不作死不会死”,总有那么一些“追求极致”的选手就喜欢玩花,下面姑且让我猜猜你为何想要依赖注入static成员属性呢?




相关文章
|
设计模式 Java
Java反射(Class、反射实例化、反射与单例、获取类结构)附带相关面试题
1.了解反射,2.Class类的三种实例化方法,3.反射机制与对象实例化,4.反射与单例设计模式,5.通过反射获取类结构的信息
305 0
|
5月前
|
Java
java反射-获取类的Class对象方式
java反射-获取类的Class对象方式
|
6月前
|
存储 Java 编译器
实例化&&构造方法&&static统统都学会
实例化&&构造方法&&static统统都学会
43 0
|
Java 容器
Java实现Autowired自动注入
Test2正常 Test3空指针 因为不在容器里
126 0
|
druid Java 编译器
Java的第七篇文章——面向对象接口(包含了接口、static修饰符、final修饰符、main方法、内部类等知识点)
Java的第七篇文章——面向对象接口(包含了接口、static修饰符、final修饰符、main方法、内部类等知识点)
java一个文件只能有一个公有类的解决方法。 用公有静态内部类。 public static。 类似于C++的命令空间。
java一个文件只能有一个公有类的解决方法。 用公有静态内部类。 public static。 类似于C++的命令空间。
|
Java Spring 容器
想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做(中)
想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做(中)
|
Java
SpringBoot static静态变量使用@Value注入方式
SpringBoot static静态变量使用@Value注入方式
321 0
|
Java Spring 容器
通过反射获得并调用类的方法导致@Autowired注入失效的解决方案
通过反射获得并调用类的方法导致@Autowired注入失效的解决方案
530 0
|
Java Spring 容器
SpringBoot (走读源码)静态方法中调用spring注入的对象,注入对象为null?
SpringBoot (走读源码)静态方法中调用spring注入的对象,注入对象为null?
438 0
SpringBoot (走读源码)静态方法中调用spring注入的对象,注入对象为null?