spring成神之路第二十一篇:注解实现依赖注入(@Autowired、@Resource、@Primary、@Qulifier)
先来看几个问题
-
通过注解的方式注入依赖对象,介绍一下你知道的几种方式
-
@Autowired和@Resource有何区别
-
说一下@Autowired查找候选者的过程
-
说一下@Resource查找候选者的过程
-
@Qulifier有哪些用法?
-
@Qulifier加在类上面是干什么用的?
-
@Primary是做什么的?
-
泛型注入用过么?
这些问题如果你都ok,那么恭喜你,很厉害。
本文内容
-
介绍spring中通过注解实现依赖注入的所有方式
-
@Autowired注解
-
@Qualifier注解
-
@Resource注解
-
@Primary注解
-
@Bean中注入的几种方式
-
将指定类型的所有bean,注入到集合中
-
将指定类型的所有bean,注入到map中
-
注入泛型
-
依赖注入源码方面的一些介绍
本文内容比较多,所有知识点均有详细案例,大家一定要敲一遍,加深理解。
@Autowired:注入依赖对象
作用
实现依赖注入,spring容器会对bean中所有字段、方法进行遍历,标注有@Autowired注解的,都会进行注入。
看一下其定义:
@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;
}
可以用在构造器、方法、方法参数、字段、注解上。
参数:
required:标注的对象是否必须注入,可能这个对象在容器中不存在,如果为true的时候,找不到匹配的候选者就会报错,为false的时候,找不到也没关系 。
@Autowire查找候选者的过程
查找过程有点复杂,看不懂的可以先跳过,先看后面案例,本文看完之后,可以回头再来看这个过程。
@Autowired标注在字段上面:假定字段类型为一个自定义的普通的类型,候选者查找过程如下
@Autowired标注在方法上或者方法参数上面:假定参数类型为为一个自定义的普通的类型,候选者查找过程如下:
上图中深色的表示方法注入和字段注入查找过程的不同点。
上图中展示的是方法中只有一个参数的情况,如果有多个参数,就重复上面的过程,直到找到所有需要注入的参数。
将指定类型的所有bean注入到Collection中
如果被注入的对象是Collection类型的,可以指定泛型的类型,然后会按照上面的方式查找所有满足泛型类型所有的bean
将指定类型的所有bean注入到Map中
如果被注入的对象是Map类型的,可以指定泛型的类型,key通常为String类型,value为需要查找的bean的类型,然后会按照上面方式查找所有注入value类型的bean,将bean的name作为key,bean对象作为value,放在HashMap中,然后注入。
@Autowired查找候选者可以简化为下面这样
按类型找->通过限定符@Qualifier过滤->@Primary->@Priority->根据名称找(字段名称或者方法名称)
概括为:先按类型找,然后按名称找
案例1:@Autowired标注在构造器上,通过构造器注入依赖对象
Service1
package com.javacode2018.lesson001.demo26.test0;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test0;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
public Service2() { //@1
System.out.println(this.getClass() + "无参构造器");
}
public Service2(Service1 service1) { //@2
System.out.println(this.getClass() + "有参构造器");
this.service1 = service1;
}
@Override
public String toString() { //@2
return "Service2{" +
"service1=" + service1 +
'}';
}
}
Service2中依赖于Service1,有2个构造方法
@1:无参构造器
@2:有参构造器,可以通过这个传入依赖的Service1
@3:重写了toString方法,一会打印测试的时候方便查看
来个总的配置文件
package com.javacode2018.lesson001.demo26.test0;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan //@1
public class MainConfig0 {
}
@1:会自动扫描当前类所在的包,会将Service1和Service2注册到容器。
来个测试用例
package com.javacode2018.lesson001.demo26;
import com.javacode2018.lesson001.demo26.test0.MainConfig0;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class InjectTest {
@Test
public void test0() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig0.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
}
main方法中启动容器,加载MainConfig0配置类,然后输出容器中所有的bean
运行部分输出
class com.javacode2018.lesson001.demo26.test0.Service2无参构造器
service1->com.javacode2018.lesson001.demo26.test0.Service1@4a94ee4
service2->Service2{service1=null}
输出中可以看出调用了Service2的无参构造器,service2中的service1为null
通过@Autowired指定注入的构造器
在Service2有参有参构造器上面加上@Autowired注解,如下:
@Autowired
public Service2(Service1 service1) {
System.out.println(this.getClass() + "有参构造器");
this.service1 = service1;
}
再次运行test0()
class com.javacode2018.lesson001.demo26.test0.Service2有参构造器
service1->com.javacode2018.lesson001.demo26.test0.Service1@4ec4f3a0
service2->Service2{service1=com.javacode2018.lesson001.demo26.test0.Service1@4ec4f3a0}
Service2有参构造器被调用了,service2中的service1有值了。
案例2:@Autowired标注在方法上,通过方法注入依赖的对象
Service1
package com.javacode2018.lesson001.demo26.test1;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
@Autowired
public void injectService1(Service1 service1) { //@1
System.out.println(this.getClass().getName() + ".injectService1()");
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:方法上标注了@Autowired,spring容器会调用这个方法,从容器中查找Service1类型的bean,然后注入。
来个总的配置文件
package com.javacode2018.lesson001.demo26.test1;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig1 {
}
来个测试用例
InjectTest中加个方法
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
com.javacode2018.lesson001.demo26.test1.Service2.injectService1()
service1->com.javacode2018.lesson001.demo26.test1.Service1@9597028
service2->Service2{service1=com.javacode2018.lesson001.demo26.test1.Service1@9597028}
通过injectService1方法成功注入service1
案例3:@Autowired标注在setter方法上,通过setter方法注入
上面2种通过构造器,和通过普通的一个方法注入,不是很常见,可以将@Autowired标注在set方法上面,来注入指定的对象
Service1
package com.javacode2018.lesson001.demo26.test2;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
@Autowired
public void setService1(Service1 service1) { //@1
System.out.println(this.getClass().getName() + ".setService1方法");
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:标准的set方法,方法上使用了 @Autowired,会通过这个方法注入Service1类型的bean对象。
来个总的配置文件
package com.javacode2018.lesson001.demo26.test2;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig2 {
}
来个测试用例
@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
com.javacode2018.lesson001.demo26.test2.Service2.setService1方法
service1->com.javacode2018.lesson001.demo26.test2.Service1@6069db50
service2->Service2{service1=com.javacode2018.lesson001.demo26.test2.Service1@6069db50}
案例4:@Autowired标注在方法参数上
Service1
package com.javacode2018.lesson001.demo26.test3;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
private Service1 service1;
@Autowired
public void injectService1(Service1 service1, String name) { //@1
System.out.println(String.format("%s.injectService1(),{service1=%s,name=%s}", this.getClass().getName(), service1, name));
this.service1 = service1;
}
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:方法上标注了@Autowired,表示会将这个方法作为注入方法,这个方法有2个参数,spring查找这2个参数对应的bean,然后注入。
第一个参数对应的bean是存在的,第二个是一个String类型的,我们并没有定义String类型bean,一会看看效果
来个总的配置文件
package com.javacode2018.lesson001.demo26.test3;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig3 {
}
来个测试用例
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'service2': Unsatisfied dependency expressed through method 'injectService1' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
报错了,从错误信息中可以看出,通过injectService1方法注入的时候,第二个参数为String类型,spring从容器中没有找到String类型的候选bean,所以报错了。
我们可以这么做
多个参数的时候,方法上面的@Autowire默认对方法中所有参数起效,如果我们想对某个参数进行特定的配置,可以在参数上加上@Autowired,这个配置会覆盖方法上面的@Autowired配置。
在第二个参数上面加上@Autowired,设置required为false:表示这个bean不是强制注入的,能找到就注入,找不到就注入一个null对象,调整一下代码,如下:
@Autowired
public void injectService1(Service1 service1, @Autowired(required = false) String name) { //@1
System.out.println(String.format("%s.injectService1(),{service1=%s,name=%s}", this.getClass().getName(), service1, name));
this.service1 = service1;
}
此时方法的第一个参数被方法上面的@Autowired约束
第二个参数受@Autowired(required = false)约束
再次运行输出
com.javacode2018.lesson001.demo26.test3.Service2.injectService1(),{service1=com.javacode2018.lesson001.demo26.test3.Service1@59309333,name=null}
service1->com.javacode2018.lesson001.demo26.test3.Service1@59309333
service2->Service2{service1=com.javacode2018.lesson001.demo26.test3.Service1@59309333}
注入成功了,service1有值,name为null
案例5:@Autowired用在字段上
Service1
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.stereotype.Component;
@Component
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
}
Service3
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service3 {
@Autowired
private Service1 service1;//@1
@Autowired
private Service2 service2;//@2
@Override
public String toString() {
return "Service3{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}
}
@1和@2:定义了2个字段,上面都标注了@Autowired,spring会去容器中按照类型查找这2种类型的bean,然后设置给这2个属性。
来个总的配置文件
package com.javacode2018.lesson001.demo26.test4;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig4 {
}
来个测试用例
@Test
public void test4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service1->com.javacode2018.lesson001.demo26.test4.Service1@7e07db1f
service2->com.javacode2018.lesson001.demo26.test4.Service2@1189dd52
service3->Service3{service1=com.javacode2018.lesson001.demo26.test4.Service1@7e07db1f, service2=com.javacode2018.lesson001.demo26.test4.Service2@1189dd52}
service3中标注@Autowired的2个属性都有值了,都被注入成功了。
案例6:@Autowire标注字段,多个候选者的时候,按字段名称注入
IService接口
package com.javacode2018.lesson001.demo26.test5;
public interface IService {
}
接口来2个实现
2个实现类上都标注了@Component注解,都会被注册到容器中。
Service0
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.stereotype.Component;
@Component
public class Service0 implements IService {
}
Service1
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
来个Service2
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Service2 {
@Autowired
private IService service1; //@1
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:标注了@Autowired注解,需要注入类型为IService类型的bean,满足这种类型的有2个:service0和service1
按照上面介绍的候选者查找过程,最后会注入和字段名称一样的bean,即:service1
来个总的配置类,负责扫描当前包中的组件
package com.javacode2018.lesson001.demo26.test5;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig5 {
}
来个测试用例
@Test
public void test5() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service0->com.javacode2018.lesson001.demo26.test5.Service0@36902638
service1->com.javacode2018.lesson001.demo26.test5.Service1@223d2c72
service2->Service2{service1=com.javacode2018.lesson001.demo26.test5.Service1@223d2c72}
注意最后一行,service2中的service1被注入了bean:service1
案例7:将指定类型的所有bean,注入到Collection、Map中
注入到Collection中
被注入的类型为Collection类型或者Collection子接口类型,注意必须是接口类型,如:
Collection<IService>
List<IService>
Set<IService>
会在容器中找到所有IService类型的bean,放到这个集合中。
注入到Map中
被注入的类型为Map类型或者Map子接口类型,注意必须是接口类型,如:
Map<String,IService>
会在容器中找到所有IService类型的bean,放到这个Map中,key为bean的名称,value为bean对象。
来看案例代码。
来个接口
package com.javacode2018.lesson001.demo26.test6;
public interface IService {
}
来2个实现类,标注@Component注解
Service0
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.stereotype.Component;
@Component
public class Service0 implements IService {
}
Service1
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
再来个类Service2
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class Service2 {
@Autowired
private List<IService> services;
@Autowired
private Map<String, IService> serviceMap;
@Override
public String toString() {
return "Service2{\n" +
"services=" + services +
", \n serviceMap=" + serviceMap +
'}';
}
}
@1:注入IService类型的所有bean
@2:注入一个map
来个总的配置类
package com.javacode2018.lesson001.demo26.test6;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig6 {
}
来个测试用例
@Test
public void test6() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service0->com.javacode2018.lesson001.demo26.test6.Service0@1189dd52
service1->com.javacode2018.lesson001.demo26.test6.Service1@36bc55de
service2->Service2{
services=[com.javacode2018.lesson001.demo26.test6.Service0@1189dd52, com.javacode2018.lesson001.demo26.test6.Service1@36bc55de],
serviceMap={service0=com.javacode2018.lesson001.demo26.test6.Service0@1189dd52, service1=com.javacode2018.lesson001.demo26.test6.Service1@36bc55de}}
注意看一下上面services和serviceMap的值。
@Autowired源码
spring使用下面这个类处理@Autowired注解
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
@Resource:注意依赖对象
作用
和@Autowired注解类似,也是用来注入依赖的对象的,spring容器会对bean中所有字段、方法进行遍历,标注有@Resource注解的,都会进行注入。
看一下这个注解定义:
javax.annotation.Resource
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
..其他不常用的参数省略
}
这个注解是javax中定义的,并不是spring中定义的注解。
从定义上可以见,这个注解可以用在任何类型上面、字段、方法上面。
注意点:
用在方法上的时候,方法参数只能有一个。
@Resource查找候选者的过程
查找过程有点复杂,看不懂的可以先跳过,先看后面案例,本文看完之后,可以回头再来看这个过程。
@Resource标注在字段上面:假定字段类型为一个自定义的普通的类型,候选者查找过程如下
@Autowired标注在方法上或者方法参数上面:假定参数类型为为一个自定义的普通的类型,候选者查找过程如下:
将指定类型的所有bean注入到Collection中
如果被注入的对象是Collection类型的,可以指定泛型的类型,然后会按照上面的方式查找所有满足泛型类型所有的bean
将指定类型的所有bean注入到Map中
如果被注入的对象是Map类型的,可以指定泛型的类型,key通常为String类型,value为需要查找的bean的类型,然后会按照上面方式查找所有注入value类型的bean,将bean的name作为key,bean对象作为value,放在HashMap中,然后注入。
@Resource查找候选者可以简化为
先按Resource的name值作为bean名称找->按名称(字段名称、方法名称、set属性名称)找->按类型找->通过限定符@Qualifier过滤->@Primary->@Priority->根据名称找(字段名称或者方法参数名称)
概括为:先按名称找,然后按类型找
案例1:将@Resource标注在字段上
IService接口
package com.javacode2018.lesson001.demo26.test7;
public interface IService {
}
2个实现类
Service0
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.stereotype.Component;
@Component
public class Service0 implements IService {
}
@Component标注的bean名称默认为service0
Service1
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
@Component标注的bean名称默认为service1
再来一个类
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Component
public class Service2 {
@Resource
private IService service1;//@1
@Override
public String toString() {
return "Service2{" +
"service1=" + service1 +
'}';
}
}
@1:字段名称为service1,按照字段名称查找bean,会找到Service1
来个配置类
package com.javacode2018.lesson001.demo26.test7;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig7 {
}
测试用例
@Test
public void test7() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service0->com.javacode2018.lesson001.demo26.test7.Service0@222545dc
service1->com.javacode2018.lesson001.demo26.test7.Service1@5c5eefef
service2->Service2{service1=com.javacode2018.lesson001.demo26.test7.Service1@5c5eefef}
最后一行可以看出注入了service1
如果将Service2中的代码调整一下
@Resource
private IService service0;
此时会注入service0这个bean
同样@Resource可以用在方法上,也可以将所有类型的bean注入到Collection、Map中,这里就不演示了,重点了解一下候选者查找的过程,使用上就比较简单了,@Resource的其他案例,大家可以自己写写练练。
下面来说另外几个注解,也是比较重要的。
@Resource源码
spring使用下面这个类处理@Resource注解
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
@Qualifier:限定符
作用
这个单词的意思是:限定符。
可以在依赖注入查找候选者的过程中对候选者进行过滤。
看一下其定义:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {
String value() default "";
}
可以用在字段、方法、参数、任意类型、注解上面
有一个参数value
还是来看案例,通过案例理解更容易。
案例1:用在类上
用在类上,你可以理解为给通过@Qulifier给这个bean打了一个标签。
先来一个接口
package com.javacode2018.lesson001.demo26.test8;
public interface IService {
}
来3个实现类
前2个@Qulifier的value为tag1,第3个实现类为tag2
Service1
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("tag1") //@1
public class Service1 implements IService {
}
@1:tag1
Service2
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("tag1")
public class Service2 implements IService {
}
@1:tag1
Service3
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("tag2")//@1
public class Service3 implements IService {
}
@1:tag2
来一个类,来注入上面几个bean
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class InjectService {
@Autowired
@Qualifier("tag1") //@1
private Map<String, IService> serviceMap1;
@Autowired
@Qualifier("tag2") //@2
private Map<String, IService> serviceMap2;
@Override
public String toString() {
return "InjectService{" +
"serviceMap1=" + serviceMap1 +
", serviceMap2=" + serviceMap2 +
'}';
}
}
@1:限定符的值为tag1,此时会将类上限定符为tag1的所有bean注入进来
@2:限定符的值为tag2,此时会将类上限定符为tag2的所有bean注入进来
来个配置类
package com.javacode2018.lesson001.demo26.test8;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig8 {
}
测试用例
@Test
public void test8() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
injectService->InjectService{serviceMap1={service1=com.javacode2018.lesson001.demo26.test8.Service1@9597028, service2=com.javacode2018.lesson001.demo26.test8.Service2@6069db50}, serviceMap2={service3=com.javacode2018.lesson001.demo26.test8.Service3@4efbca5a}}
service1->com.javacode2018.lesson001.demo26.test8.Service1@9597028
service2->com.javacode2018.lesson001.demo26.test8.Service2@6069db50
service3->com.javacode2018.lesson001.demo26.test8.Service3@4efbca5a
注意第一行的输出,看一下serviceMap1和serviceMap2的值。
serviceMap1注入了@Qulifier的value为tag1的所有IService类型的bean
serviceMap1注入了@Qulifier的value为tag2的所有IService类型的bean
实现了bean分组的效果。
案例2:@Autowired结合@Qulifier指定注入的bean
被注入的类型有多个的时候,可以使用@Qulifier来指定需要注入那个bean,将@Qulifier的value设置为需要注入bean的名称
看案例代码
来个接口
package com.javacode2018.lesson001.demo26.test9;
public interface IService {
}
有2个实现类
2个实现类上面没有使用@Qulifier注解了
Service1
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.stereotype.Component;
@Component
public class Service2 implements IService {
}
我们可以知道上面2个bean的名称分别为:service1、service2
来个类,注入IService类型的bean
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
@Autowired
@Qualifier("service2") //@1
private IService service;
@Override
public String toString() {
return "InjectService{" +
"service=" + service +
'}';
}
}
@1:这里限定符的值为service2,容器中IService类型的bean有2个[service1和service2],当类上没有标注@Qualifier注解的时候,可以理解为:bean的名称就是限定符的值,所以@1这里会匹配到service2
来个配置类
package com.javacode2018.lesson001.demo26.test9;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
@ComponentScan
public class MainConfig9 {
}
来个测试用例
@Test
public void test9() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig9.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
injectService->InjectService{service=com.javacode2018.lesson001.demo26.test9.Service2@223d2c72}
service1->com.javacode2018.lesson001.demo26.test9.Service1@8f4ea7c
service2->com.javacode2018.lesson001.demo26.test9.Service2@223d2c72
从第一行可以看出注入了service1
案例3:用在方法参数上
代码
package com.javacode2018.lesson001.demo26.test10;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
private IService s1;
private IService s2;
@Autowired
public void injectBean(@Qualifier("service2") IService s1, @Qualifier("service1") IService s2) { //@1
this.s1 = s1;
this.s2 = s2;
}
@Override
public String toString() {
return "InjectService{" +
"s1=" + s1 +
", s2=" + s2 +
'}';
}
}
@1:方法上标注了@Autowired注解,说明会被注入依赖,2个参数上分别使用了限定符来指定具体需要注入哪个bean
测试用例
@Test
public void test10() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig10.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
injectService->InjectService{s1=com.javacode2018.lesson001.demo26.test10.Service2@55183b20, s2=com.javacode2018.lesson001.demo26.test10.Service1@4f83df68}
service1->com.javacode2018.lesson001.demo26.test10.Service1@4f83df68
service2->com.javacode2018.lesson001.demo26.test10.Service2@55183b20
第一行中的
s1:service2
s2:service1
案例4:用在setter方法上
不管是用在setter方法还是普通方法上面,都是一样的效果
代码
package com.javacode2018.lesson001.demo26.test11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
private IService s1;
private IService s2;
@Autowired
@Qualifier("service2")
public void setS1(IService s1) {
this.s1 = s1;
}
@Autowired
@Qualifier("service2")
public void setS2(IService s2) {
this.s2 = s2;
}
@Override
public String toString() {
return "InjectService{" +
"s1=" + s1 +
", s2=" + s2 +
'}';
}
}
上面2个setter方法上都有@Autowired注解,并且结合了@Qulifier注解来限定需要注入哪个bean
测试用例
@Test
public void test11() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig11.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
injectService->InjectService{s1=com.javacode2018.lesson001.demo26.test11.Service2@35e2d654, s2=com.javacode2018.lesson001.demo26.test11.Service2@35e2d654}
service1->com.javacode2018.lesson001.demo26.test11.Service1@1bd4fdd
service2->com.javacode2018.lesson001.demo26.test11.Service2@35e2d654
输出中可以看出:s1为service2,s2为service1
@Primary:设置为主要候选者
注入依赖的过程中,当有多个候选者的时候,可以指定哪个候选者为主要的候选者。
看一下其定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Primary {
}
可以用在类上或者方法上面。
通常定义bean常见的有2种方式:
方式1:在类上标注@Component注解,此时可以配合@Primary,标注这个bean为主要候选者
方式2:在配置文件中使用@Bean注解标注方法,来注册bean,可以在@Bean标注的方法上加上@Primary,标注这个bean为主要候选bean。
看案例。
案例1:用在类上
来个接口
package com.javacode2018.lesson001.demo26.test12;
public interface IService {
}
2个实现类
Service1
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.stereotype.Component;
@Component
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class Service2 implements IService {
}
Service2上面使用了@Primary,表示这是个主要的候选者
再来个类,注入IService类型的bean
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class InjectService {
@Autowired
private IService service1; //@1
@Override
public String toString() {
return "InjectService{" +
"service1=" + service1 +
'}';
}
}
@1:容器中IService类型的bean有2个,但是service2为主要的候选者,所以此处会注入service2
总的配置类
package com.javacode2018.lesson001.demo26.test12;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig12 {
}
测试用例
@Test
public void test12() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig12.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
injectService->InjectService{service1=com.javacode2018.lesson001.demo26.test12.Service2@49ec71f8}
service1->com.javacode2018.lesson001.demo26.test12.Service1@1d2adfbe
service2->com.javacode2018.lesson001.demo26.test12.Service2@49ec71f8
案例2:用在方法上,结合@Bean使用
来个接口
package com.javacode2018.lesson001.demo26.test13;
public interface IService {
}
2个实现类
Service1
package com.javacode2018.lesson001.demo26.test13;
public class Service1 implements IService {
}
Service2
package com.javacode2018.lesson001.demo26.test13;
public class Service2 implements IService {
}
InjectService
package com.javacode2018.lesson001.demo26.test13;
import org.springframework.beans.factory.annotation.Autowired;
public class InjectService {
@Autowired
private IService service1;//@1
@Override
public String toString() {
return "InjectService{" +
"service1=" + service1 +
'}';
}
}
使用了@Autowired,需要注入
来个配置类,通过@Bean定义上面3个类型的bean
package com.javacode2018.lesson001.demo26.test13;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class MainConfig13 {
@Bean
public IService service1() {
return new Service1();
}
@Bean
@Primary //@1
public IService service2() {
return new Service2();
}
@Bean
public InjectService injectService() {
return new InjectService();
}
}
上面是一个配置类,定义了3个bean
@1:这个bean被标注为主要的候选者
来个测试用例
@Test
public void test13() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig13.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service1->com.javacode2018.lesson001.demo26.test13.Service1@6913c1fb
service2->com.javacode2018.lesson001.demo26.test13.Service2@66d18979
injectService->InjectService{service1=com.javacode2018.lesson001.demo26.test13.Service2@66d18979}
注意最后一行,service1注入的是service2这个bean
@Bean定义bean时注入依赖的几种方式
常见3种方式
-
硬编码方式
-
@Autowired、@Resource的方式
-
@Bean标注的方法参数的方式
方式1:硬编码方式
来3个类
Service1
package com.javacode2018.lesson001.demo26.test14;
public class Service1 {
}
Service2
package com.javacode2018.lesson001.demo26.test14;
public class Service2 {
}
Service3
package com.javacode2018.lesson001.demo26.test14;
public class Service3 {
private Service1 service1;
private Service2 service2;
public Service1 getService1() {
return service1;
}
public void setService1(Service1 service1) {
this.service1 = service1;
}
public Service2 getService2() {
return service2;
}
public void setService2(Service2 service2) {
this.service2 = service2;
}
@Override
public String toString() {
return "Service3{" +
"service1=" + service1 +
", service2=" + service2 +
'}';
}
}
上面类中会用到service1和service2,提供了对应的setter方法,一会我们通过setter方法注入依赖对象
来个配置类,通过@Bean的方式创建上面对象
package com.javacode2018.lesson001.demo26.test14;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig14 {
@Bean
public Service1 service1() {
return new Service1();
}
@Bean
public Service2 service2() {
return new Service2();
}
@Bean
public Service3 service3() {
Service3 service3 = new Service3(); //@0
service3.setService1(this.service1()); //@1
service3.setService2(this.service2()); //@2
return service3;
}
}
上面代码中通过@Bean定义了3个bean
Service3中需要用到Service1和Service2,注意@1和@2直接调用当前方法获取另外2个bean,注入到service3中
测试用例
@Test
public void test14() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig14.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service1->com.javacode2018.lesson001.demo26.test14.Service1@41a2befb
service2->com.javacode2018.lesson001.demo26.test14.Service2@6c40365c
service3->Service3{service1=com.javacode2018.lesson001.demo26.test14.Service1@41a2befb, service2=com.javacode2018.lesson001.demo26.test14.Service2@6c40365c}
方式2:@Autowired、@Resource的方式
这种方式就不讲了直接在需要注入的对象上面加上这2个注解的任意一个就行了,可以参考文章前面的部分。
方式3:@Bean标注的方法使用参数来进行注入
package com.javacode2018.lesson001.demo26.test15;
import com.javacode2018.lesson001.demo26.test14.Service1;
import com.javacode2018.lesson001.demo26.test14.Service2;
import com.javacode2018.lesson001.demo26.test14.Service3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig15 {
@Bean
public Service1 service1() {
return new Service1();
}
@Bean
public Service2 service2() {
return new Service2();
}
@Bean
public Service3 service3(Service1 s1, Service2 s2) { //@0
Service3 service3 = new Service3();
service3.setService1(s1); //@1
service3.setService2(s2); //@2
return service3;
}
}
@0:这个地方是关键,方法上标注了@Bean,并且方法中是有参数的,spring调用这个方法创建bean的时候,会将参数中的两个参数注入进来。
注入对象的查找逻辑可以参考上面@Autowired标注方法时查找候选者的逻辑。
来个测试用例
@Test
public void test15() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig15.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service1->com.javacode2018.lesson001.demo26.test14.Service1@4009e306
service2->com.javacode2018.lesson001.demo26.test14.Service2@43c1b556
service3->Service3{service1=com.javacode2018.lesson001.demo26.test14.Service1@4009e306, service2=com.javacode2018.lesson001.demo26.test14.Service2@43c1b556}
同样注入成功了。
其他
@Bean标注的方法参数上使用@Autowired注解
@Bean
public Service3 service3_0(Service1 s1, @Autowired(required = false) Service2 s2) { //@0
Service3 service3 = new Service3();
service3.setService1(s1); //@1
service3.setService2(s2); //@2
return service3;
}
@0:方法由2个参数,第二个参数上标注了@Autowired(required = false),说明第二个参数候选者不是必须的,找不到会注入一个null对象;第一个参数候选者是必须的,找不到会抛出异常
@Bean结合@Qualifier
package com.javacode2018.lesson001.demo26.test17;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
@Configuration
public class MainConfig17 {
@Bean
@Qualifier("tag1") //@1
public Service1 service1() {
return new Service1();
}
@Bean
@Qualifier("tag1") //@2
public Service2 service2() {
return new Service2();
}
@Bean
@Qualifier("tag2") //@3
public Service3 service3() {
return new Service3();
}
@Bean
public InjectService injectService(@Qualifier("tag1") Map<String, IService> map1) { //@4
InjectService injectService = new InjectService();
injectService.setServiceMap1(map1);
return injectService;
}
}
Service1,Service2,Service3都实现了IService接口
@1,@2,@3这3个方法上面使用了@Bean注解,用来定义3个bean,这3个方法上还是用了@Qualifier注解,用来给这些bean定义标签,service1()方法类似于下面的写法:
@Compontent
@Qualifier("tag1")
public class Service1 implements IService{
}
再回到MainConfig17中的@4:参数中需要注入Map<String, IService>,会查找IService类型的bean,容器中有3个,但是这个参数前面加上了@Qualifier限定符,值为tag1,所以会通过这个过滤,最后满足的候选者为:[service1,service2]
对应测试用例
@Test
public void test17() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig17.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
}
}
运行输出
service1->com.javacode2018.lesson001.demo26.test17.Service1@1190200a
service2->com.javacode2018.lesson001.demo26.test17.Service2@6a2f6f80
service3->com.javacode2018.lesson001.demo26.test17.Service3@45b4c3a9
injectService->InjectService{serviceMap1={service1=com.javacode2018.lesson001.demo26.test17.Service1@1190200a, service2=com.javacode2018.lesson001.demo26.test17.Service2@6a2f6f80}, serviceMap2=null}
注意最后一行serviceMap1,注入了service1和service2
泛型注入
先来2个普通的类
UserModel
package com.javacode2018.lesson001.demo26.test18;
public class UserModel {
}
OrderModel
package com.javacode2018.lesson001.demo26.test18;
public class OrderModel {
}
记住上面2个普通的类UserModel和OrderModel,一会下面会用到。
来个泛型接口
package com.javacode2018.lesson001.demo26.test18;
public interface IDao<T> {
}
上面是个泛型类,类名后面后尖括号
来2个实现类
两个实现类都会标注@Compontent,交给spring容器管理
UserDao
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class UserDao implements IDao<UserModel> { //@1
}
@1:指定了IDao后面泛型的类型为UserModel
OrderDao
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class OrderDao implements IDao<OrderModel> {//@1
}
@1:指定了IDao后面泛型的类型为OrderModel
在来个泛型类型
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseService<T> {
@Autowired
private IDao<T> dao; //@1
public IDao<T> getDao() {
return dao;
}
public void setDao(IDao<T> dao) {
this.dao = dao;
}
}
BaseService同样是个泛型类
@1:这个地方要注意了,上面使用了@Autowired,来注入IDao对象
BaseService来2个子类
两个子类都会标注@Compontent,交给spring容器管理
UserService
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class UserService extends BaseService<UserModel> {//@1
}
@1:指定了BaseService后面泛型的类型为UserModel
OrderService
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.stereotype.Component;
@Component
public class OrderService extends BaseService<OrderModel> {//@1
}
@1:指定了BaseService后面泛型的类型为OrderModel
UserService和OrderService继承了BaseService,所以一会BaseService中的dao属性会被注入,一会我们关注一下dao这个属性的值,会是什么样的
来个总的配置类
package com.javacode2018.lesson001.demo26.test18;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig18 {
}
上面有@CompontentScan注解,会自动扫描当前包中的所有类,并进行自动注入
来个测试用例
@Test
public void test18() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig18.class);
System.out.println(context.getBean(UserService.class).getDao());
System.out.println(context.getBean(OrderService.class).getDao());
}
上面代码中会将两个service中的dao输出,我们来看一下效果
运行输出
com.javacode2018.lesson001.demo26.test18.UserDao@6adbc9d
com.javacode2018.lesson001.demo26.test18.OrderDao@4550bb58
结果就是重点了,dao属性并没有指定具体需要注入那个bean,此时是根据尖括号中的泛型类型来匹配的,这个功能也是相当厉害的。
总结
这篇文中内容比较多,每个案例大家都要去敲一遍,不清楚的,可以留言,或者直接微信中@我
-
需要掌握@Autowired注解和@Resource注解中候选者查找的过程
-
@Autowired:先通过类型找,然后通过名称找
-
@Resource:先通过名称找,然后通过类型找
-
@Autowired和@Resource,建议开发中使用@Autowired来实现依赖注入,spring的注解用起来更名正言顺一些
-
@Qulifier:限定符,可以用在类上;也可以用在依赖注入的地方,可以对候选者的查找进行过滤
-
@Primary:多个候选者的时候,可以标注某个候选者为主要的候选者
-
@Bean中注入依赖的3种方式需要掌握
-
掌握泛型注入的使用
-
主要还是掌握候选者的查找过程,过程熟悉了,其他的都是小意思,回头再去看看上面的几个查找的流程图。
案例源码
https://gitee.com/javacode2018/spring-series
路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。
来源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648934232&idx=1&sn=fd2f34d8d1342fe819c5a71059e440a7&chksm=88621f66bf159670a8268f8db74db075634a24a58b75589e4e7db2f06e6166c971074feae764&token=979575345&lang=zh_CN&scene=21#wechat_redirect