当前位置: 首页 > news >正文

@Autowired 注解在构造器上的使用规则(字段注入也挺好的)

背景

  • 在看Spring Framework官方文档时,看到这样一段描述:

As of Spring Framework 4.3, an @Autowired​ annotation on such a constructor is no longer necessary if the target bean defines only one constructor to begin with. However, if several constructors are available and there is no primary/default constructor, at least one of the constructors must be annotated with @Autowired​ in order to instruct the container which one to use. See the discussion on constructor resolution for details.

  • 为了更好地理解这段话,做了一些实践,写成文章分享给大家。

实践

第一句

“As of Spring Framework 4.3, an @Autowired annotation on such a constructor is no longer necessary if the target bean defines only one constructor to begin with.”

  • 含义:从 Spring Framework 4.3 版本开始,如果一个目标 Bean 只定义了一个构造器,那么在该构造器上标注 @Autowired​ 注解就不再是必须的。
@RestController
public class HelloController {

    private HelloService helloService;

    // 只有一个构造器,可以不写@Autowired
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello();
    }
}

第二句

“However, if several constructors are available and there is no primary/default constructor, at least one of the constructors must be annotated with @Autowired in order to instruct the container which one to use.”

  • 含义:然而,如果一个类有多个构造器,并且没有一个被标记为“主要构造器”或默认构造器,那么至少需要在一个构造器上标注 @Autowired​,以便告诉 Spring 容器应该使用哪一个构造器。
@RestController
public class HelloController {

    private HelloService helloService;

//    @Autowired 不写@Autowired,helloSService会为null
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    public HelloController() {

    }

    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello();
    }
}


Cannot invoke "com.forrest.learn.spring.autowired.example1.HelloService.sayHello()" because "this.helloService" is null
@RestController
public class HelloController {

    private HelloService helloService;

    @Autowired
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    public HelloController() {

    }

    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello();
    }
}

想法

  • 在实际开发中,很少会采用构造器注入,因为写起来太麻烦了…

    image

    image

那我们需要搞清楚,为什么不推荐使用注入?

  • Why field injection is not recommended?

    • (1)不方便写单测(不赞同,挺方便的)
    • (2)不能被final修饰(确实有风险,但完全属于人为造成的风险…)
    • (3)字段注入隐藏了类的依赖项(不赞同,根据字段注入,也可以知道当前类依赖了哪些类)
    • (4)字段注入依赖于 Spring 框架在构建对象后填充依赖项。(下文解释)
    • (5)字段注入使类依赖于用于注入依赖项的框架。(不赞同,@Autowired是Spring的注解,确实依赖了框架,但我们可以用Java的@Resource进行依赖注入)

不能被final修饰(确实有风险,但helloService被恶意修改了,在运行时可以被测出来,因为运行结果不符合预期了)

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello();
    }
}
  • 由于helloService不是final的,因此有被修改的风险:
@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/hello")
    public String hello() {
        HelloController helloController = applicationContext.getBean(HelloController.class);
        Field field = null;
        try {
            field = HelloController.class.getDeclaredField("helloService");
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        field.setAccessible(true);

        try {
            field.set(helloController, null);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        return helloService.sayHello();
    }
}

image

helloService注入时不为null,但被修改成了null,导致了npe…

不方便写单测(挺方便的)

public class HelloControllerTest {

    private MockMvc mockMvc;

    @Mock
    private HelloService helloService;

    @InjectMocks
    private HelloController helloController;

    @BeforeEach
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(helloController).build();
    }

    @Test
    public void hello_WhenCalled_ReturnsHelloMessage() throws Exception {
        String expectedMessage = "Hello, World!";
        when(helloService.sayHello()).thenReturn(expectedMessage);

        mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string(expectedMessage));
    }
}

字段注入依赖于 Spring 框架在构建对象后填充依赖项。

image

@Service
public class HelloServiceImpl implements HelloService {

    private final HelloRepository helloRepository;
//
//    public HelloServiceImpl() {
//
//    }

    @Autowired
    public HelloServiceImpl(HelloRepository helloRepository) {
        this.helloRepository = helloRepository;
        helloRepository.initialize();
    }

    @Override
    public String sayHello() {
        return "Hello World";
    }
}
  • 但完全可以先字段注入,然后通过@PostContruct再调用依赖的方法:

    image

总结

  • 通过@Autowired字段注入,并未有不可接受的副作用,而且代码简洁。

  • IDEA的警告挺烦的,怎么关了?

    image

  • 或者采用@Resouce

相关文章:

  • DeepSeek在医学领域的应用
  • Go语言对于MySQL的基本操作
  • .NET 9 中 OpenAPI 替代 Swagger 文档生成
  • Python精进系列:解包(Unpacking)用法之 *args 和 **kwargs
  • 使用py-ffmpeg批量合成视频的脚本
  • HarmonyOS NEXT开发进阶(十二):build-profile.json5 文件解析
  • 根据公式和a求出假设的b,再将b代入公式中反证是否能求出a
  • Vue 中的 MVVM、MVC 和 MVP 模式深度解析
  • 【java】网络编程——UDP协议通信
  • 【go语言圣经1.6】
  • Linux操作系统6- 线程2(线程的创建,终止,等待与退出)
  • docker 增加镜像(忘记什么bug了)
  • Java 反射机制学习
  • 对C++面向对象的理解
  • 学习用WinDbg查看程序当前运行的堆栈
  • 代码随想录day17 二叉树part05
  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring MVC 的崛起:从 Struts 到 Spring 的演进
  • 软考网络安全专业
  • selenium等待
  • Python----数据分析(Pandas一:pandas库介绍,pandas操作文件读取和保存)
  • 中拉论坛部长级会议为何悬挂海地和圣卢西亚的国旗?外交部回应
  • 专访|导演刘江:给谍战题材注入现实主义的魂
  • 习近平在中拉论坛第四届部长级会议开幕式的主旨讲话(全文)
  • 云南威信麟凤镇通报“有人穿‘警察’字样雨衣参与丧事”:已立案查处
  • 工人日报:“鼠标手”被纳入职业病,劳动保障网越织越密
  • IPO周报|本周A股暂无新股网上申购,年内最低价股周二上市