Author 罗先生
本文是IOC系列的第二篇,往期回顾:
@Required
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
代表某个类必须被注入,否则抛错
在spring5.1 这个注解已经被deprecated
@Autowired
默认是byType匹配,可以放在属性上,还可以放在构造器,set方法上等。
放在构造器上
public class OrderService {
OrderDao orderDao;
@Autowired
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void getOrder() {
orderDao.selectOrder();
}
}
有多个OrderDao类型的,不指定name,会报错
Spring建议,如果有多个构造器时,用@Autowired放在构造器上显示声明使用哪个构造器
放在set方法上
其实不局限于方法名,放在任意方法上都会执行,只要参数类型匹配就会注入
public class OrderService {
OrderDao orderDao;
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void getOrder() {
orderDao.selectOrder();
}
public OrderDao getOrderDao() {
return orderDao;
}
@Autowired
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
}
放在集合上
这种用法比较少见,也比较少人知道。前段时间就碰到**策略模式,**拿所有实现类怎么写更为优雅,如果知道这一点,就十分方便,直接把所有实现注入在map上。
放到数组和set上
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
放到map上
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
或者可以直接@Autowired放在属性上也是行得通的
最后生成的beanName就不用说了吧,如果是注解式的,那最后就是类名首字母小写
需要注意如果放置到集合上,前提是至少容器中有一个相应类型的bean,否则则报错
ps:
(1):如果是注入ApplicationContext等Spring内部的类,直接注入即可,Spring内部初始化的时候这些类已经被自动创建了,如下:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
(2):默认的required为true,即如果容器中没有对应的类型的bean,那么则会报错,所以对于非必须的Bean可以设置required为false
参考:
@Order
控制放入SpringBean容器的顺序,比如就可以以刚才@Autowired注入集合的例子为例,如果加上Order注解,越小的值那么越先注入,最终的结果就是在数组中的下标更靠前,遍历的话更容易先被遍历到,即可以做类似一个优先级队列。
但是值得注意的是如果注入map,那么遍历map的顺序是不受@Order影响的,因为hashmap内部根据Key hash取余
ps:
也可以结合@Bean一起使用
结合@Autowired注入集合控制注入顺序
public class OrderService {
@Autowired
private OrderDao[] orderDaos;
public void selectAllOrderDaoImpl() {
for (OrderDao orderDao : orderDaos) {
System.out.println(orderDao.getClass());
}
}
}
@Repository
@Order(1)
public class OrderDaoImpl implements OrderDao {
@Override
public void selectOrder() {
System.out.println("OrderDaoImpl selectOrder");
}
}
@Repository
@Order(0)
public class OrderDaoImpl1 implements OrderDao {
@Override
public void selectOrder() {
System.out.println("OrderDaoImpl1 selectOrder");
}
}
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Appconfig.class);
OrderService orderService = annotationConfigApplicationContext.getBean(OrderService.class);
orderService.selectAllOrderDaoImpl();
//结果
class com.luban.ioctest.OrderDaoImpl1
class com.luban.ioctest.OrderDaoImpl
map的可自行尝试
@Qualifier
主要就是配合@Autowired配合使用,注入特定名字的Bean,因为@Autowired只能根据类型注入,而如果我们要注入指定名字的Bean怎么办,就可以用这个解决。
@Resource
示例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
和@Autowired差不多,但是**@Resource是byName**,看到byName就要知道和名字肯定有关系,所以分三种情况
- 放到set方法上,那么会注入的beanName=set方法后的名称,以上面的为例就是movieFinder
- 直接放到属性上,beanName等于字段名
- 放到构造器上。和构造器参数名一致的beanName
和@Autowired的区别
1:@Autowired是byType的,而@Resource是byName的,不懂byName、byType的可以看第一篇IOC概念
2:@Resource本身有name属性,所以不用借助其他注解可以指定注入指定name的bean
ps:
1:@Resource在注入ApplicationContext等对象也是byType的,原因很简单,Spring只会有一个ApplicationContext对象,如果你自己定义一个,Spring不会处理,这在源码上是有体现的。
3:需要注意的是不管是@Resource还是@Autowired都是按需装配,和自动装配不一样,带有这个注解的属性就按某种规则进行注入(byName或者byType),自动装配可见IOC概念
参考:
@Autowired和@Resource:
底层实现简述:
这里稍微说一下,@Autowired底层是通过CommonAnnotationBeanPostProcessor(没记错的应该是这),是通过Bean的后置处理器进行实现的,即先用构造器(构造器的选择也是一个学问?)实例化一个Bean后,进行populate属性,拿到属性后,自然可以知道是否被@Autowired注释,剩下的过程,如果没有循环依赖(怎么判断有循环依赖?)的Bean则直接从容器中拿注入就行了,有循环依赖,还要有一个递归填充的过程。
整个过程后面有时间整理原理的时候在讲清楚,现在只要知道有CommonBeanPostProcessor这个东西,Spring自己提供了这个Bean进行依赖注入的就行了。
一些例子:
下面讲一些个人之前会存疑的,现在解决了的例子,后续可能会有补充
例子一:
public interface OrderDao {
void selectOrder();
}
@Repository
public class OrderDaoImpl implements OrderDao {
public void selectOrder() {
System.out.println("OrderDao1");
}
}
@Service
public class OrderService {
@Resource//或者@Autowired
private OrderDao orderDao;
public void selectOrder() {
orderDao.selectOrder();
}
}
嗯。大家都是这么用的。。Spring也不会报错
@Autowired好理解,但是在用@Resource的时候会不会有一个问题,既然**@Resource是byName的,现在容器里没有一个叫orderDao的bean,为什么不会报错呢?原因在于@Resource找不到对应名称的bean时,会自动转换成byType**
例子二:
多了一个实现
@Repository("orderDaoImpl1")
public class OrderDaoImpl1 implements OrderDao {
public void selectOrder() {
System.out.println("OrderDaoImpl1");
}
}
@Repository("orderDaoImpl")
public class OrderDaoImpl implements OrderDao {
public void selectOrder() {
System.out.println("OrderDaoImpl");
}
}
@Service
public class OrderService {
@Autowired
private OrderDao orderDaoimpl;
public void selectOrder() {
orderDaoimpl.selectOrder();
}
}
这么注入是不会报错的,而且注入的是name为orderDaoimpl的bean
总结:
1:@Autowired和@Resource都可以用属性名来表示需要注入对应beanName的bean
2:当@Resource找不到对应名称的bean时,会自动转换成byType
@Configuration
@Configuration标注的类代表是个配置类,Spring解析这个类标注的类是很复杂的,具体后面有时间搞源码的会说。
@Configuration
public class AppConfig {
@Bean
public Foo foo() {
return new Foo(bar());
}
@Bean
public Bar bar() {
return new Bar();
}
}
与@Bean的结合
@Bean的含义就是往Spring中注入一个bean,用java代码的形式,按Spring的话说injecting inter-bean dependencies,beanName为方法名,beanClass为返回值的类型。
然后来看一个例子:
@Configuration
@ComponentScan("cn.lqw.study")
public class Appconfig {
@Bean
public OrderDao orderDaoImpl() {
return new OrderDaoImpl();
}
@Bean
public OrderDao orderDaoImpl1() {
orderDaoImpl();
return new OrderDaoImpl1();
}
}
让我们提一个问题,容器中此时有几个orderDaoImpl。可以测试一下,答案是一个。原因可以大概说一下,Spring会对@Configuration的类进行cglib代理,即执行的orderDaoImpl1()是代理方法,那么调用orderDaoImpl1()调****orderDaoImpl()时会判断,在执行的方法是不是orderDaoImpl1(),如果是的那么他会不往Spring中注入。
与@Import的结合
可以import三种类,普通类,配置类,实现ImportBeanDefinitionRegistrar的类,Spring源码内部对着三种处理是不同的
普通类:
很简单,就是可以import一个没有被@Compoent标注的类,和****annotationConfigApplicationContext.register()效果是一样的
配置类
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
实现****ImportBeanDefinitionRegistrar的类
这个先不说,但是很重要,讲AOP的时候再说这个类, 很多框架与Spring的集成都靠实现ImportBeanDefinitionRegistrar来提供往容器中register Bean的功能
参考:
@DependOn
这个注解字面意思上可以理解为依赖某个Bean,但是这不是一个直接依赖,什么意思呢?举个例:A依赖B,有两种方式可以表明,第一种是直接声明在属性上,这是个直接依赖,这种一般我们都需要实例化B,第二种是用@DependOn在A类声明B类在Spring中的beanName,那么此时,A是间接依赖B的,这种是不需要实例化B的,A可能只需要B执行他的静态代码块来初始化一些东西,所以@DependOn的作用就在于此了,来个例子:
@Service
@DependsOn("orderDaoImpl")
public class OrderService {
public void selectOrder() {
// orderDaoImpl.selectOrder();
}
}
@Repository("orderDaoImpl")
public class OrderDaoImpl implements OrderDao {
static {
System.out.println("static");
}
public void selectOrder() {
System.out.println("OrderDaoImpl");
}
}
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(Appconfig.class);
// OrderDao orderDao = (OrderDao) annotationConfigApplicationContext.getBean("orderDaoImpl");
// orderDao.selectOrder();
}
}
//最后输出static
参考:
@Lookup
解决单例bean依赖原型bean每次都是获取到同样的bean的问题,原因是Spring容器只初始化一次,也就是说一个依赖注入的机会只有一次,所以就会出现这样的问题,解决问题方式有两种,第一种就是注入ApplicationContext对象,每次都重新从容器中拿,那么每次都会重新new。
第二种就是@Lookup。举个例子:
@Service
public class OrderService {
// @Autowired
// OrderDao orderDao;
public void selectOrder() {
for (int i = 0; i < 3; i++) {
System.out.println(getOrderDao().hashCode());
}
// orderDaoImpl.selectOrder();
}
@Lookup
protected OrderDao getOrderDao(){
return null;
}
}
@Repository("orderDaoImpl1")
@Scope("prototype")
public class OrderDaoImpl1 implements OrderDao {
public void selectOrder() {
System.out.println("OrderDaoImpl1");
}
}
//结果
1241529534
1082309267
402405659
结果可以看到是三个不同hashcode。
然后可以看到getOrderDao()的返回值是个null,不用在意这个,因为被@Lookup标注的方法会被重写,会根据其返回值的类型,容器会调用BeanFactory的getBean()方法来返回一个bean。
使用的@Lookup的方法需要符合如下的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
[abstract]是可选的,Spring官方的例子是抽象方法,但是我们业务开发很少声明抽象类,可以用正常方法,return null就好了。