Spring源码解析之基础应用(四)

基于注解的容器配置

spring允许我们用注解来代替XML配置,至于是注解更好还是XML更好视情况而定,一般开发人员喜欢使用注解来进行配置,因为这样更靠近代码;而运维更喜欢XML配置,来决定服务运行的环境,比如:数据库配置。spring不但允许两种不同配置风格的存在,甚至还能混合使用。

谈到注解,@Autowired一定是spring应用最广泛的注解之一,一般我们是将@Autowired标注在字段上,但这个注解同样可以标注在具有多个参数的方法上,前提是这些参数能在容器内找到类型匹配的bean。

MovieRecommender一共有3处使用了@Autowired,首先是在MovieFinder字段上标注,这是大家最熟悉的做法;其次在MovieRecommender(MovieCatalog movieCatalog)构造方法上标注,如果这个方法不标注@Autowired,spring默认会调用无参构造方法;最后在setCustomerPreferenceDao(CustomerPreferenceDao customerPreferenceDao)方法上标注,spring会传入这个方法所需要的参数bean。

package org.example.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MovieRecommender {
    @Autowired
    private MovieFinder movieFinder;
    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;

    public MovieRecommender() {
        System.out.println("Construct MovieRecommender()");
    }

    @Autowired
    public MovieRecommender(MovieCatalog movieCatalog) {
        this.movieCatalog = movieCatalog;
        System.out.println("Construct MovieRecommender(MovieCatalog movieCatalog) ");
    }

    public MovieFinder getMovieFinder() {
        return movieFinder;
    }

    public MovieCatalog getMovieCatalog() {
        return movieCatalog;
    }


    public CustomerPreferenceDao getCustomerPreferenceDao() {
        return customerPreferenceDao;
    }

    @Autowired
    public void setCustomerPreferenceDao(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }
}

package org.example.beans;

import org.springframework.stereotype.Component;

@Component
public class MovieFinder {
}

package org.example.beans;

import org.springframework.stereotype.Component;

@Component
public class MovieCatalog {
}

package org.example.beans;

import org.springframework.stereotype.Component;

@Component
public class CustomerPreferenceDao {
}

  

测试用例:

    @Test
    public void test14() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
        MovieRecommender recommender = ac.getBean(MovieRecommender.class);
        System.out.println(recommender.getMovieFinder());
        System.out.println(recommender.getMovieCatalog());
        System.out.println(recommender.getCustomerPreferenceDao());
    }

  

运行结果:

Construct MovieRecommender(MovieCatalog movieCatalog) 
org.example.beans.MovieFinder@35083305
org.example.beans.MovieCatalog@8e0379d
org.example.beans.CustomerPreferenceDao@341b80b2

  

@Autowired还允许我们设置参数required,默认为true,当注入的时候找不到bean则会报错,我们可以设置required为false,当spring没有找到所需的bean,则会跳过标记了@Autowired的方法或字段。

@Autowired也可以用于标记一个集合,当这个集合所描述的类型包含多个实现。比如下面的例子,Fruit存在Apple和Banana这两个实现,我们在FruitPlate类中分别用数组、List、Set、Map来存放Fruit元素,如果用@Autowired来标注这四个字段,spring容器会把所有实现Fruit的bean数组到这个集合,Map的key为beanName:

package org.example.beans;

public class Fruit {
}

package org.example.beans;

import org.springframework.stereotype.Component;

@Component
public class Apple extends Fruit {
}

package org.example.beans;

import org.springframework.stereotype.Component;

@Component
public class Banana extends Fruit {
}

package org.example.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
public class FruitPlate {
    @Autowired
    private Fruit[] fruits;
    @Autowired
    private List<Fruit> fruitList;
    @Autowired
    private Set<Fruit> fruitSet;
    @Autowired
    private Map<String, Fruit> fruitMap;

    public Fruit[] getFruits() {
        return fruits;
    }

    public List<Fruit> getFruitList() {
        return fruitList;
    }

    public Set<Fruit> getFruitSet() {
        return fruitSet;
    }

    public Map<String, Fruit> getFruitMap() {
        return fruitMap;
    }
}

  

测试用例:

    @Test
    public void test15() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig5.class);
        FruitPlate fruitPlate = ac.getBean(FruitPlate.class);
        System.out.println(Arrays.toString(fruitPlate.getFruits()));
        System.out.println(fruitPlate.getFruitList());
        System.out.println(fruitPlate.getFruitSet());
        System.out.println(fruitPlate.getFruitMap());
    }

  

运行结果:

[org.example.beans.Apple@429bd883, org.example.beans.Banana@4d49af10]
[org.example.beans.Apple@429bd883, org.example.beans.Banana@4d49af10]
[org.example.beans.Apple@429bd883, org.example.beans.Banana@4d49af10]
{apple=org.example.beans.Apple@429bd883, banana=org.example.beans.Banana@4d49af10}

  

由于按照类型注入时可能存在多个候选bean,我们可以在类或方法上标注@Primary注解,当通过类型注入bean时存在多个实现,优先使用标记注@Primary的bean:

@Primary
@Component
public class Apple extends Fruit {
}

@Component
public class FruitPlate {
	……
	@Autowired
    private Fruit primary;
	public Fruit getPrimary() {
		return primary;
	}
	……
}

  

当注入primary时,会优先选用Apple所对应的bean,大家可以在测试用例里面试一下,这里就不再另外试了。

除了@Autowired,还有另外两个属性能帮助我们完成注入,分别是:JSR-250定义的注解@Resource 和JSR-330定义的注解@Inject。@Autowired和@Inject使用同一套逻辑进行注入,先根据类型进行bean的查找,如果存在多个bean,再根据字段名查找对应的bean。@Resource允许填写name和type,如果同时指定name和type,则注入的时候会找到beanName和类型都能匹配的上的bean进行注入;如果只指定了name,则根据beanName进行查找并注入,找不到则抛出异常;如果只指定了type,则根据类型进行查找,如果找不到或者找到多个,则抛出异常;如果name和type都不指定,则根据字段名查找,找不到再回退到根据类型查找。

@Autowired虽然是一种装配技术,但不能与之前讲到的自动装配归为一类,即便这二者从表现上来看都会为我们注入所需要的bean,但它们所使用的技术不同,执行注入的时机不同,就像鸡蛋跟鸭蛋煮熟后味道差不多,但我们不能说鸡蛋就是鸭蛋。首先从使用方式上来看@Autowired和自动装配的不同,对于@Autowired我们只需要在字段或方法上标记,spring才会为我们注入,这是基于注解的注入,而自动装配我们只要提供setter方法,在<bean/>标记装配类型,spring会根据装配类型来调用setter方法,这是基于XML的注入。当然,等到后续讲到spring源码时,笔者会证明@Autowired和自动装配的处理时机不同,执行逻辑不同。

 

posted @ 2020-10-19 20:36  北洛  阅读(187)  评论(0编辑  收藏  举报