@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();
    }
}
想法
-  在实际开发中,很少会采用构造器注入,因为写起来太麻烦了…       
那我们需要搞清楚,为什么不推荐使用注入?
-  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 
