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和自动装配的处理时机不同,执行逻辑不同。