前言
Spring作为Java技术经久不衰的一个功能强大的框架,两大特性依赖注入和面向切面编程对我们现在的代码开发有着很大的帮助,那么这篇文章就来讨论一下Spring依赖注入的几种方式。本人小白一枚,有不对的地方还请大家多多指点。Spring注入方式有很多种,但可以进行一下分类,首先就是通过xml文件声明bean,使用setter、构造器、工厂模式进行注入。另一种就是通过注解声明bean,使用注解进行注入。
1. xml文件声明,setter方式注入,构造器方式注入
Spring的两种依赖注入方式:setter注入与构造方法注入,这两种方法的不同主要就是在xml文件下对应使用property和constructor-arg属性。
setter方式注入需要对应bean中存在set方法,构造器方式注入需要bean中存在构造方法。
代码如下:
有两个实体类Car
public class Car {
private long price;
private String brand;
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
@Override
public String toString() {
return price+brand;
}
}
User类,依赖Car类
public class User {
private int id;
private String name;
private List<String> list;
private Map<String, String> map;
//@Autowired
private Car car;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return id+name+list.get(0)+map.get("map1")+car.toString();
}
}
TestDao实现类TestDaoImpl,依赖User类,其中含有一个两个参数的构造方法
public class TestDaoImpl implements TestDao {
// @Autowired
private User user;
private String pattern;
private User userBean;
public TestDaoImpl(User user1, String pattern1) {
this.user = user1;
this.pattern = pattern1;
}
@Override
public User queryUser(int id) {
System.out.println("into queryUser daoImpl." + pattern);
return user;
}
@Override
public void output() {
System.out.println("pattern is "+pattern+". User is "+user.getId()+user.getName()+user.getCar().getBrand()+user.getCar().getPrice());
}
}
接下来是我们的重点,spring-beans.xml文件,里面包含了setter方式注入和构造器方式注入的两种模式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="testDao" class="com.lxinyu.test.dao.impl.TestDaoImpl">
<constructor-arg ref="user"></constructor-arg>
<constructor-arg value="lxy"></constructor-arg>
</bean>
<bean id="user" class="com.lxinyu.test.entity.User">
<property name="id" value="1"></property>
<property name="name" value="lxy"></property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="map">
<map>
<entry key="map1" value="map1"></entry>
<entry key="map2" value="map2"></entry>
</map>
</property>
<property name="car" ref="car"></property>
</bean>
<bean id="car" class="com.lxinyu.test.entity.Car">
<property name="price" value="2000"></property>
<property name="brand" value="BBA"></property>
</bean>
</beans>
接下来做一个测试,编写一个测试类,内容如下:
public class TestDemo {
public static void main(String[] args) {
// xml方式创建SpringIOC容器ClassPathXmlApplicationContext
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-beans.xml");
// 注解方式创建SpringIOC容器AnnotationConfigApplicationContext
// ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigurationTest.class);
TestDao testDao = (TestDao)ctx.getBean("testDao");
testDao.output();
}
}
结果如下:
以上就是setter方式注入以及构造器方式注入的两种方式。
目录
2. 注解声明,注解注入
Spring 2.5 引入了 @Autowired
注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
Car实体类
import org.springframework.stereotype.Component;
@Component
public class Car {
private long price;
private String brand;
@Override
public String toString() {
return price+brand;
}
}
User实体类依赖Car
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class User {
private int id;
private String name;
private List<String> list;
private Map<String, String> map;
@Autowired
private Car car;
@Override
public String toString() {
return id+name+car.toString();
}
}
Dao类依赖User
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.lxinyu.test.dao.TestDao;
import com.lxinyu.test.entity.User;
// 声明一下该bean名称为testDao,以方便后续注入的时候通过@Autowired和@Qualifier("testDao")来查找
@Repository("testDao")
public class TestDaoImpl implements TestDao {
@Autowired
private User user;
@Override
public User queryUser(int id) {
System.out.println("into queryUser daoImpl.");
return user;
}
}
使用注解声明注解注入时需要用到一个配置类,该类必须要有两个注解@Configuration以及@ComponentScan,如下:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class ConfigurationTest {
// 无需做任何事情,构造方法只是为了打印一条日志,没有也可以
public ConfigurationTest() {
System.out.println("this is a configuration class.");
}
}
@Configuration的作用在于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。
@ComponentScan的作用在于通过注解指定Spring在创建容器时要扫描的包,代替了spring-bean.xml文件中的
<context:component-scan base-package="com.XXXX.XX"></context:component-scan>
值得注意的是 <context:component-scan/> 配置项不但启用了对类包进行扫描以实施注释驱动 Bean 定义的功能,同时还启用了注释驱动自动注入的功能(即还隐式地在内部注册了AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor)即如下内容:
<bean class= "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> Spring 通过一个 BeanPostProcessor
对 @Autowired
进行解析,所以要让@Autowired
起作用必须事先在 Spring 容器中声明AutowiredAnnotationBeanPostProcessor
Bean,该bean是用来使@Autowired注解生效的
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> 该bean是用来使@Resouce、@PostConstruct
以及 @PreDestroy这些注释生效的
测试一下,编写一下测试类,测试类与之前xml文件方式的测试类中构建springIOC容器的方式也不同,差别如下:
//通过xml文件创建springIOC容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-beans.xml");
//通过配置类创建springIOC容器
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigurationTest.class);
完整的测试类代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lxinyu.test.dao.TestDao;
//import com.lxinyu.test.controller.HelloController;
//import com.lxinyu.test.service.TestService;
public class TestDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigurationTest.class);
// HelloController helloController = (HelloController)ctx.getBean("helloController");
// TestService testService = (TestService)ctx.getBean("testServiceImpl");
// helloController.queryUser(1);
TestDao testDao = (TestDao)ctx.getBean("testDao");
testDao.queryUser(1);
}
}
以上,完全通过注解的形式完全摒弃xml文件的声明与注入就全部完成了,几点说明
- @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。
- @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
- @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。
- @Repository 通常作用在数据层,但是目前该功能与@Component 相同。
- 通过@Scope标签来表明作用范围,不填默认是单例模式,可以通过@Scope("prototype")来使其变为多例模式
- 单例模式(singleton): 创建IOC容器时即创建bean对象,以后每次取值都是取的这个bean对象,singleton模式下还可以添加@Lazy注解,即懒加载,表示在创建IOC容器时并不创建bean对象,而是在第一次获取bean对象时才创建,之后再获取bean对象时不再创建,因此仍然是单实例
/**
* 使用懒加载模式生成bean实例
* 懒加载只对单实例模式有效
* 本来,单实例模式,是在启动springIOC容器时创建bean实例
* 使用懒加载后,在启动springIOC容器时并不创建bean实例,而是在首次获取bean时才创建
*/
@Bean(value = "person")
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Lazy
public Person person() {
return new Person("刘能", 53);
}
-
- 原型模式(protortype): 创建IOC容器时不创建bean对象,以后每次取值时再分别创建
import org.springframework.context.annotation.Scope;
...
@Scope("prototype")
@Component
public class User {
…
}
- 当一个接口有多个实现类的时候,由于@Autowired注解是根据类型来获取bean的,所以在声明的时候需要设置一个bean的名称,在注入时使用@Autowired和@Qualifier一起让spring容器知道你具体想要哪一个bean
- @Autowired(required=false)意味着告诉spring容器如果没有找到或者找到多个bean的时候不报错,这个用的比较少,一般在开发前期使用较多
- @Resource注解不是spring的注解而是java6出现的注解,该注解的功能与@Autowired一致,不过@Autowired默认是byType,@Resource默认是byName的
代码实现过程中踩过的坑:
1. java.lang.NoClassDefFoundError: org/springframework/aop/TargetSource,缺少了Spring必要aop jar包,该jar包位置在spring framework包下的spring-aop文件夹中,找到对应的版本放到IDEA的lib文件夹中,add-to-libraries即可。报错如下:
2. Error creating bean with name 'helloController': Unsatisfied dependency expressed through field 'TestServcie'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean 'com.cloud.xp.manager.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
摒弃xml文件,通过注解方式注入bean时,Service或Dao层注入失败,该错误报错原因是Spring容器中没有TestService这个bean,查看代码发现,@Service这个注解加在了TestService这个接口上面,而@Service或@Repository这两个注解应该加在实现类上面,修改过后异常解决。
3. org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.XXXX.XXX.dao.TestDao' available: expected single matching bean but found 2: testDaoImpl, testDaoImpll
摒弃xml文件,通过注解方式注入bean时,Service层注入Dao时报错,该异常信息指出的就非常明显了,在我们的TestDao接口中存在两个实现类,testDaoImpl和testDaoImpll,由于我们注入的时候使用的是@Autowired注解,该注解默认使用byType方式查找对应的bean,而这两个实现类的类型都是TestDao,因此spring容器无法确定到底要用哪一个 Bean因此报错。此时需要在@Repository时指定名称,并通过@Autowired+@Qualifier
注解说明你具体要哪个Bean,代码如下:
TestDaoImpl:
@Repository("testDao")
public class TestDaoImpl implements TestDao {
//忽略其他代码
}
TestDaoImpll
@Repository("testDaol")
public class TestDaoImpll implements TestDao {
// 忽略其他代码
}
TestServiceImpl
@Service
public class TestServiceImpl implements TestService {
// 引用时使用@Autowired+@Qualifier指明你要注入哪个bean
@Autowired
@Qualifier("testDao")
private TestDao testDao;
@Autowired
private User user;
// 忽略其他代码
}
4. Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [spring-beans.xml]; nested exception is java.io.FileNotFoundException: class path resource [spring-beans.xml] cannot be opened because it does not exist
该问题原因是由于ClassPathXmlApplicationContext这个方法找不到我们的配置文件了,将我们的spring-beans.xml文件放到src根路径下即可
5. Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'price' of bean class [com.lxinyu.test.entity.User]: Bean property 'price' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
该问题由于xml文件中设置的price值与entity中设置的price的类型不匹配导致的,修改xml文件中关于<property name="price" value="对应值"> </property>即可。
6. Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDao' defined in class path resource [spring-beans.xml]: Unsatisfied dependency expressed through constructor parameter 0: Ambiguous argument values for parameter of type [com.lxinyu.test.entity.User] - did you specify the correct bean references as arguments?
这个问题很奇怪,原本testDao这个bean的内容如下:
public class TestDaoImpl implements TestDao {
// @Autowired
private User user;
private String pattern;
public TestDaoImpl(User user1, String pattern1) {
this.user = user1;
this.pattern = pattern1;
}
@Override
public User queryUser(int id) {
System.out.println("into queryUser daoImpl." + pattern);
return user;
}
@Override
public void output() {
System.out.println("pattern is "+pattern+". User is "+user.getId()+user.getName()+user.getCar().getBrand()+user.getCar().getPrice());
}
}
对应spring-beans.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="testDao" class="com.lxinyu.test.dao.impl.TestDaoImpl">
<constructor-arg name="user" ref="user"></constructor-arg>
<constructor-arg name="pattern" value="lxy"></constructor-arg>
</bean>
<bean id="user" class="com.lxinyu.test.entity.User">
<property name="id" value="1"></property>
<property name="name" value="lxy"></property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="map">
<map>
<entry key="map1" value="map1"></entry>
<entry key="map2" value="map2"></entry>
</map>
</property>
<property name="car" ref="car"></property>
</bean>
<bean id="car" class="com.lxinyu.test.entity.Car">
<property name="price" value="2000"></property>
<property name="brand" value="BBA"></property>
</bean>
</beans>
启动之后一直报上述错误找了很多方法添加index,调整构造方法参数位置都没有效果,后来我将xml文件中的name属性删除就好了,删除后xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="testDao" class="com.lxinyu.test.dao.impl.TestDaoImpl">
<constructor-arg ref="user"></constructor-arg>
<constructor-arg value="lxy"></constructor-arg>
</bean>
<bean id="user" class="com.lxinyu.test.entity.User">
<property name="id" value="1"></property>
<property name="name" value="lxy"></property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="map">
<map>
<entry key="map1" value="map1"></entry>
<entry key="map2" value="map2"></entry>
</map>
</property>
<property name="car" ref="car"></property>
</bean>
<bean id="car" class="com.lxinyu.test.entity.Car">
<property name="price" value="2000"></property>
<property name="brand" value="BBA"></property>
</bean>
</beans>
这个是什么原因很奇怪,不是说在构造方法有多个参数时需要有个name来区分具体注入的是哪个参数嘛?这咋又不用了?麻烦大家帮我解答一下这个呗。(其他文档中有描述过查看org.springframework.beans.factory.BeanCreationException文档后发现,bean内如果定义了constructor-arg属性的话,是不需要name=“XXX”这个属性的,去掉即可。)