正确使用@Autowired

一、前言

  • 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的都没问题。

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 (类型兼容)
  • 正例(可以向上转型):
@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
*/
  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值