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

参考:

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-autowired-annotation

@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概念

参考:

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-resource-annotation

@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的功能

参考:

https://docs.spring.io/spring/docs/5.0.16.RELEASE/spring-framework-reference/core.html#beans-java-configuration-annotation

@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

参考:

https://docs.spring.io/spring/docs/5.0.16.RELEASE/spring-framework-reference/core.html#beans-factory-dependson

@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就好了。

参考:

https://docs.spring.io/spring/docs/5.0.16.RELEASE/spring-framework-reference/core.html#beans-factory-lookup-method-injection