目录
- 一、前言
- 二、跟着官方文档,学习正确使用@Autowired
- 0、实验环境
- 1、通过构造方法进行注入
- 1.1 问题1:那万一没有这个CustomerPreferenceDao对象,会报错吗?
- 2、通过setter方法注入
- 3、通过方法注入(这个方法可以是任意名称,有任意参数)
- 4、通过字段注入 【非常常见的做法,简洁】
- 5、字段注入和构造方法注入可以混用。
- 6、解释
- 7、注入一组依赖(通过:字段 或 方法)
- 7.1 通过字段
- 7.2 通过方法
- 8、按顺序注入一组依赖【以字段注入为例】
- 9、还能注入Map<String, xxx>
一、前言
- Spring框架两大核心特性:IoC容器(对开发者来说,就是希望Spring帮助注入依赖) + AOP
- 因此,熟练使用Spring框架之一便是熟练使用依赖注入。
- 之前介绍了“正确使用@Resource”,本文重点介绍“正确使用@Autowired”
二、跟着官方文档,学习正确使用@Autowired
0、实验环境
@ComponentScan
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
Arrays.stream(applicationContext.getBeanDefinitionNames()).forEach(System.out::println);
System.out.println("---------------------------------------------------------------");
MovieRecommender movieRecommender = applicationContext.getBean(MovieRecommender.class);
movieRecommender.sayHello();
}
}
public class CustomerPreferenceDao {
public void sayHello() {
System.out.println("Hello, I am " + this.getClass().getSimpleName());
}
}
@Configuration
public class LearnAutowiredConfig {
@Bean
public CustomerPreferenceDao customerPreferenceDao() {
return new CustomerPreferenceDao();
}
}
1、通过构造方法进行注入
@Component
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
public void sayHello() {
customerPreferenceDao.sayHello();
System.out.println("Hello, I am " + this.getClass().getSimpleName());
}
}
- 从Spring 4.3开始,如果组件只有一个构造方法,那么没必要给这个构造方法带上@Autowired。
- 道理也很简单,Spring为MovieRecommender类创建对象时,少不了调用构造方法。它发现需要一个CustomerPreferenceDao对象,然后在IoC容器里面确实有这个对象,那就自动帮咱注入了。
1.1 问题1:那万一没有这个CustomerPreferenceDao对象,会报错吗?
- 会报错!
个人觉得,
注解是一个非常好的标识,即使能省略,也别省略。
- 为啥会报错呢?
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
/**
* Declares whether the annotated dependency is required.
* <p>Defaults to {@code true}.
*/
boolean required() default true;
}
- 表面上省略了@Autowired,但实际上仍然和带上@Autowired的效果一致。默认要求必须注入依赖,如果待注入的依赖不存在,则报错。
- 那我改成
@Autowired(required = false)
可以吗?–> 依然报错
Inconsistent constructor declaration on bean with name 'movieRecommender': single autowire-marked constructor flagged as optional - this constructor is effectively required since there is no default constructor to fall back to: public com.forrest.learnspring.autowired.example1.bean.MovieRecommender(com.forrest.learnspring.autowired.example1.dao.CustomerPreferenceDao)
- 大意:Spring认为提供的构造方法不符合需求(因为找不到可用的CustomerPreferenceDao的bean),然后发现这个构造方法是可选的,那我就选其他的吧。结果没其他可选了,那只能报错了。
- 补一个空参构造方法就行:
public MovieRecommender() {
this.customerPreferenceDao = null;
}
@Autowired(required = false)
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
如果bean具有多个构造方法,那么不能省略@Autowired
,否则Spring在创建bean的时候,不知道用哪个构造方法。
2、通过setter方法注入
@Component
public class MovieRecommender {
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void setCustomerPreferenceDao(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
...
}
3、通过方法注入(这个方法可以是任意名称,有任意参数)
@Component
public class MovieRecommender {
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
public void sayHello() {
customerPreferenceDao.sayHello();
System.out.println("Hello, I am " + this.getClass().getSimpleName());
}
}
- 这个prepare方法会被Spring调用,咱不用管。咱需要注意的是:
- 入参必须是能够在IoC容器中找得到的bean。
(String s, CustomerPreferenceDao customerPreferenceDao)
这是不可以的。因此,IoC容器中,没有s
这个bean。 - 对访问修饰符没有要求。
- 我猜测是,Spring创建好MovieRecommender的bean后,由这个bean去调用自己的方法。因此,哪怕方法是private的都没问题。
- 入参必须是能够在IoC容器中找得到的bean。
4、通过字段注入 【非常常见的做法,简洁】
@Component
public class MovieRecommender {
@Autowired
private CustomerPreferenceDao customerPreferenceDao;
...
}
- 但IDEA不建议字段注入。【但写的代码少了,一般都采用这种,哈哈】
5、字段注入和构造方法注入可以混用。
@Component
public class MovieRecommender {
@Autowired
private CustomerPreferenceDao customerPreferenceDao;
private final UserProcessor userProcessor;
@Autowired
public MovieRecommender(UserProcessor userProcessor) {
this.userProcessor = userProcessor;
}
...
}
6、解释
官方文档:
Make sure that your target components (for example, MovieCatalog or CustomerPreferenceDao) are consistently declared by the type that you use for your @Autowired-annotated injection points. Otherwise, injection may fail due to a “no type match found” error at runtime.
For XML-defined beans or component classes found via classpath scanning, the container usually knows the concrete type up front. However, for @Bean factory methods, you need to make sure that the declared return type is sufficiently expressive. For components that implement several interfaces or for components potentially referred to by their implementation type, consider declaring the most specific return type on your factory method (at least as specific as required by the injection points referring to your bean).
咋一看,这都是啥啊… 但还是要理解下啊
- 解释:
- 从上面的例子可知,不管@Autowired用在构造方法、setter方法、还是字段上,我们都能知道需要的依赖是什么类型的。
- Spring就会根据这个类型,去IoC容器中找:
- 因此,我们要保证类型别写错了,否则Spring会找不到。
Make sure that your target components are consistently declared by the type
(类型兼容)
- 因此,我们要保证类型别写错了,否则Spring会找不到。
- 正例(可以向上转型):
@Controller
public class UserController {
@Autowired
private UserService userService;
...
}
@Configuration
public class LearnAutowiredConfig {
// 很显然,这么写相当于:UserService userServie = new UserServiceImpl();
// UserController的userService = userServie = new UserServiceImpl(); 也是成立的。
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
// 同理这么写也行
@Configuration
public class LearnAutowiredConfig {
@Bean
public UserServiceImpl userService() {
return new UserServiceImpl();
}
}
- 反例(不能向下转型):
@Controller
public class UserController {
@Autowired
private UserServiceImpl userService;
...
}
@Configuration
public class LearnAutowiredConfig {
// UserService userService = new UserServiceImpl();
// UserController的userService = userService; 这是不行的。属于向下转型。
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
- 报错:No qualifying bean of type ‘com.forrest.learnspring.autowired.example2.service.impl.UserServiceImpl’ available
- 向下转型需要强转,否则会报错,等同于:
- 【
推荐
】注入依赖,依赖的类型尽可能抽象(有接口用接口!)。
7、注入一组依赖(通过:字段 或 方法)
7.1 通过字段
@Controller
public class UserController {
@Autowired
private List<UserService> userServices;
...
}
@Configuration
public class LearnAutowiredConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
@Bean
public UserService userProxyService() {
return new UserProxyServiceImpl();
}
}
7.2 通过方法
@Controller
public class UserController {
private List<UserService> userServices;
@Autowired
public void prepare(List<UserService> userServices) {
this.userServices = userServices;
}
...
}
8、按顺序注入一组依赖【以字段注入为例】
- 办法:
- (1) implement the org.springframework.core.Ordered interface
- (2) @Order
- (3) @Priority
- 以@Order为例子
@Configuration
public class LearnAutowiredConfig {
@Bean
@Order(2)
public UserService userService() {
return new UserServiceImpl();
}
@Bean
@Order(1)
public UserService userProxyService() {
return new UserProxyServiceImpl();
}
}
/*
1 : UserProxyServiceImpl
2 : UserServiceImpl
*/
@Order(1)
@Service
public class UserProxyServiceImpl implements UserService {
...
}
@Order(2)
@Service
public class UserServiceImpl implements UserService {
...
}
9、还能注入Map<String, xxx>
- 以字段注入为例:
@Controller
public class UserController {
@Autowired
private Map<String, UserService> userServiceMap;
...
}
/*
userProxyServiceImpl: UserProxyServiceImpl
userServiceImpl: UserServiceImpl
*/