测试环境:Intellij + gradle
编写build.gradle
group 'com.xiya'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'idea'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile group: 'org.springframework', name: 'spring-context', version: '4.3.7.RELEASE'
}
Spring存在三种依赖注入的注解:
@Autowired:org.springframework.beans.factory.annotation.Autowired
@Resource:javax.annotation.Resource
@Inject:javax.inject.Inject
三种方式的区别在哪里呢?
测试:
定义Person接口
package com.xiya.entity; /** * @file Person.java * @CopyRight (C) http://blog.csdn.net/x_iya * @Description * @author N3verL4nd * @email [email protected] * @date 2017/6/17 */ public interface Person { void sayHello(); }
定义两个实现类:
package com.xiya.entity; import org.springframework.stereotype.Component; /** * @file Chinese.java * @CopyRight (C) http://blog.csdn.net/x_iya * @Description * @author N3verL4nd * @email [email protected] * @date 2017/6/17 */ @Component public class Chinese implements Person { @Override public void sayHello() { System.out.println("你好,我来自中国!"); } }
package com.xiya.entity; import org.springframework.stereotype.Component; /** * @file American.java * @CopyRight (C) http://blog.csdn.net/x_iya * @Description * @author N3verL4nd * @email [email protected] * @date 2017/6/17 */ @Component public class American implements Person { @Override public void sayHello() { System.out.println("Hello, I come from America!"); } }
定义Service层
package com.xiya.service; import com.xiya.entity.Person; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @file PersonManager.java * @CopyRight (C) http://blog.csdn.net/x_iya * @Description * @author N3verL4nd * @email [email protected] * @date 2017/6/17 */ @Service public class PersonManager { private Person person; /* 构造器注入 * 使用: * @Autowired * @Qualifier("chinese") * 或者使用: * @Resource(name = "chinese") * */ @Resource(name = "chinese") public void setPeople(Person person) { this.person = person; } public void sayHello() { person.sayHello(); } }
xml version="1.0" encoding="UTF-8"?>xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.xiya.entity, com.xiya.service"> context:component-scan>
测试类:
import com.xiya.service.PersonManager; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class T { private ApplicationContext context; @Before public void setBefore() { context = new ClassPathXmlApplicationContext("applicationContext.xml"); } @Test public void test() { PersonManager peopleManager = context.getBean(PersonManager.class); peopleManager.sayHello(); } }
目录结构:
先按类型注入,然后按照名称注入,都无法找到唯一的一个实现类则报错。
我们将American类中@Component注释掉,这样在Spring环境中Person只有Chinese一个实现类
输出:你好
因为只有Chinese实现了Person,所以会正确运行(通过类型查找)。
我们取消American类中@Component的注释,运行程序会出现异常:
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'people': Unsatisfied dependency expressed through field 'person'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.xiya.spring.entity.Person' available:expected single matching bean but found 2: american,chinese
因为Person有American和Chinese两个实现类,Spring不知道该用哪一个进行注入。接着按照名称注入,spring容器中没有名字为person的bean。
People修改如下:(Chinese与American类都用@Component注解)
package com.xiya.service; import com.xiya.entity.Person; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @file PersonManager.java * @CopyRight (C) http://blog.csdn.net/x_iya * @Description * @author N3verL4nd * @email [email protected] * @date 2017/6/17 */ @Service public class PersonManager { private Person person; /* 构造器注入 * 使用: * @Autowired * @Qualifier("chinese") * 或者使用: * @Resource(name = "chinese") * */ // @Resource(name = "chinese") @Autowired // @Qualifier("chinese") public void setPeople(Person chinese) { this.person = chinese; } public void sayHello() { person.sayHello(); } }
相当于:
@Qualifier("chinese")
如果只有一个实体类Chinese(删除American类),而且Chinese不实现Person接口,此时怎么注入Person chinese都会出错(请求的是Person对象,注入的却不是)。
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'people': Unsatisfied dependency expressed through field 'chinese'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xiya.spring.entity.Person' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
查找流程:首先在Spring容器内查找类型为Person的bean,没有找到;接着按照chinese名称进行查找,虽然查找到,但是是类型为Chinese,抛异常。
@Autowired(required=false)中如果设置required为false(默认为true),则注入失败时不会抛出异常,但person.sayHello();调用时会出现空指针异常NullPointerException。
需要引入:
compile group: 'javax.inject', name: 'javax.inject', version: '1'
在Spring的环境下,@Inject和@Autowired是相同的,都是使用AutowiredAnnotationBeanPostProcessor来处理依赖注入,@Inject是jsr-330定义的规范,还是比较推荐使用这种方式进行依赖注入,如果使用这种方式,切换到Guice也是可以的。
如果硬要说两个的区别,首先@Inject是Java EE包里的,在SE环境需要单独引入。另一个区别在于@Autowired可以设置required=false而@Inject并没有这个设置选项。
先按名字注入,再按类型注入,都无法找到唯一的一个出现异常。
这是jsr-250定义的规范,相对于jsr-330算是比较老的了。这里不推荐使用这种注入方式,下面讨论一下其注入的问题。
首先我们注释American里的@Component,这样在Spring托管的Bean里只有Chinese实现了Person接口,测试用例如下:
@Component public class People { @Resource private Person person; public void sayHello() { person.sayHello(); } }输出结果:“你好,我来自中国!”。 此时@Resource先按名字person,并未找到名称为person的bean,然后按照类型来找,只有Chinese,注入成功。
取消American中的@Component注释,出现如下异常:
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'people': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.xiya.spring.entity.Person' available: expected single matching bean but found 2: american,chinese
此时先通过名字无法确定,然后通过类型还是无法确定,抛出异常。
修改测试代码:
@Component public class People { @Resource private Person chinese; public void sayHello() { chinese.sayHello(); } }输出结果你好,此时按名字找到了chinese。
--------------
@Autowired是可以写在字段或者setter方法上或者构造器上。
然而,在字段上使用@Autowired注解,IDEA会提示“Field injection is not recommended.Spring Team recommends:’Always use constructor based dependency injection in your beans.Always use assertions for mandatory dependencies.’” 不推荐使用字段注入,会发生空指针错误,推荐使用构造器注入。
@Component
public class People {
@Autowired
@Qualifier("chinese")
private Person person;
public void sayHello() {
person.sayHello();// NullPointerException发生
}
}
使用构造器注入则可以避免该问题的发生:
@Component public class People { /*@Autowired @Qualifier("chinese")*/ private Person person; @Autowired public People(@Qualifier("chinese") Person person) { Assert.notNull(person, "person must not be null"); this.person = person; } public void sayHello() { person.sayHello(); } }