背景
- 在看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();
}
}
想法
-
在实际开发中,很少会采用构造器注入,因为写起来太麻烦了…
那我们需要搞清楚,为什么不推荐使用注入?
-
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();
}
}
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 框架在构建对象后填充依赖项。
@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再调用依赖的方法:
总结
-
通过@Autowired字段注入,并未有不可接受的副作用,而且代码简洁。
-
IDEA的警告挺烦的,怎么关了?
-
或者采用@Resouce