文章目录
依赖注入(Dependency Injection)
Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes or the Service Locator pattern.
——引自Spring官方文档
依赖注入(DI)是一个过程,通过该过程,对象仅通过构造函数参数、工厂方法的参数,或在构造、或在创建对象实力后,在对象实力上设置的属性来定义依赖关系。从工厂方法返回, 容器在创建Bean时注入依赖项。此过程从根本上讲是通过类的直接构造或服务定位模式来控制bean自身依赖或位置的bean本身逆过程(因此称之为Control Inversion)
使用DI原理,代码更加简洁,对象之间依赖耦合度更低。
基于xml的Ioc容器配置
xml基础配置如下
<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">
</beans>
基于Constructor和Setter注入
Constructor(构造)注入
例如以下代码,构造函数中有两个参数
public class IScoreDao {
private String name;
private int score;
public IScoreDao(String name, int score) {
this.name = name;
this.score = score;
}
}
此时在xml中配置有三类如下:
- 构造函数参数类型匹配,通过 type 属性显示指定构造参数类型
<beans>
<bean id="scoreDao" class="dao.IScoreDao">
<constructor-arg type="java.lang.String" value="English"/>
<constructor-arg type="int" value="90"/>
</bean>
</beans>
此种方式有着明显的缺陷,如果构造参数类型都一样,都是String或int类型,那么这种方式就不行
- 构造函数参数索引,使用 index 指定构造参数的索引(索引从0开始)
<beans>
<bean id="scoreDao" class="dao.IScoreDao">
<constructor-arg index="0" value="English"/>
<constructor-arg index="1" value="90"/>
</bean>
</beans>
此方式解决了构造参数类型相同的奇异问题
- 构造函数参数名称
<beans>
<bean id="scoreDao" class="dao.IScoreDao">
<constructor-arg name="name" value="English"/>
<constructor-arg name="score" value="90"/>
</bean>
</beans>
此方式可消除构造参数中的歧义
注意:使用构造函数注入依赖,value 属性只能用于基本类型(Integer,bollean…)和String类型的构造参数,其它类型则需使用 ref 属性
基于setter注入
通过调用无参构造函数或static的工厂方法创建bean实例后,在通过setter方法完成。
例如以下代码:
public class UserserviceImpl implements IUserService {
private IUserDao userDao;
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
}
xml中的配置如下:
<!--注入service-->
<bean id="service.IUserService" class="serviceImpl.UserserviceImpl">
<!--成员变量: 注入dao-->
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 配置dao对象-->
<bean id="userDao" class="dao.UseDaoImpl"></bean>
- spring可同时支持constuctor 和 setter,就是活,两者可联合使用。将构造函数用于强制依赖项,setter用于可选依赖项是一个很好的方式。
解析依赖项过程
-
使用ApplicationContext描述所有bean的实例化和初始化(存入Map容器中)。可使用xml、java代码或注解来指定配置元数据。
-
对于每个bean,其依赖项属性都是以属性、构造参数、static-factory方法的参数的形式表示。创建bean时,依赖项会提供给bean。
-
每个属性或构造参数都要设置实际意义,或者是对另一个容器中的bean的引用。
-
每个属性或构造参数的类型都会从指定格式转为实际类型。spring默认将string类型的所有值转为内置基础类型(long,boolean…)和string类型。
在创建容器时,spring会检查每个bean的配置,默认以单例模式创建bean的实例对象,但可通过 scope 属性指定作用范围。不适用单例模式创建bean实例时,会在请求(需要调用bean)时,才会创建bean。
以下代码是创建bean并使用的示例:
ApplicationContext context = null;
IUserService userService = null;
@Before
public void getApplicationContext() {
context = new ClassPathXmlApplicationContext("bean.xml");
userService = context.getBean(IUserService.class.getName(),IUserService.class);
}
@Test
public void finAllTest() throws SQLException {
List<UserEntity> entityList = userService.findAll();
for (UserEntity userEntity : entityList) {
System.out.println(userEntity);
}
}
ApplicationContext初始化容器和创建bean,有了bean对象之后,就可以调用它的方法了。
基于注解的IoC容器配置
有了前面基于xml的IoC容器配置,使用注解的方式来配置Ioc容器,会发现这是个简单容易的过程。简化了很多工作量,减少了很多的注入配置。
首先修改xml中配置,移除IUserService和IUserDao的配置,修改xml的架构,使用Context Schema Configuration架构
<?xml version="1.0" encoding="UTF-8"?>
<!--这里使用context schema configuration-->
<beans 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 https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 通知spring要扫描的包 -->
<context:component-scan base-package="com"></context:component-scan>
<!--配置QueryRunner-->
<bean id="query" class="org.apache.commons.dbutils.QueryRunner" scope="prototype" > <!--线程安全:多个dao同时操作数据库,一个runner对象会存在线程安全问题,这里采用多利模式-->
<!--配置数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://192.168.1.8:3306/studenms?serverTimezone=UTC"></property>
<property name="user" value="root"></property>
<property name="password" value="sa"></property>
</bean>
</beans>
使用注解@Service,@Repository,@Autowired
@Service(value = "IUserService")
public class UserserviceImpl implements IUserService {
@Autowired
private IUserDao userDao;
// your code...
}
@Repository(value = "UserDao")
public class UseDaoImpl implements IUserDao {
@Autowired
private QueryRunner query;
// your code...
}
测试代码:
public class AnnotationIoCTest {
ApplicationContext context = null;
IUserService userService = null;
@Before
public void getApplicationContext() {
context = new ClassPathXmlApplicationContext("bean.xml");
userService = context.getBean("IUserService", IUserService.class);
}
@Test
public void finAllTest() throws SQLException {
List<UserEntity> entityList = userService.findAll();
for (UserEntity userEntity : entityList) {
System.out.println(userEntity);
}
}
}
测试结果如下:
注意事项
- 使用注解时,一定要在xml中使用context:component-scan,它的作用是指定spring IoC容器创建时要扫描注解的包
<context:component-scan base-package="com"></context:component-scan>
否则会抛出无法找到名叫"IUservice"的Bean:
- 一定要在xml配置org.apache.commons.dbutils.QueryRunner
这里可能会产生一个疑问,使用了注解@Autowried来注入QueryRunner,为什么还要在xml中配置呢?因为在QueryRunner中注入了c3p0的DataSource(数据库连接信息),如果不配置,就会无法使用c3p0,也不能连接数据库。如果仅仅在xml中配置了QueryRunner的注入,而不是用@Autowired注解,会怎么样呢?
把QueryRunner的注解去掉
//@Autowired
private QueryRunner query;
执行测试,结果如下:
空指针异常,没有获取到query对象,说明没有注入成功。所以这里一定要注意。
到了这里会产生僧一个疑问,不是使用注解吗,怎么还是有xml配置?接下来开始移除xml中的配置
移除xml
- 新建一个配置类
/**
* 配置类
*/
@Configuration
@ComponentScan(value = {"com"})
public class SpringConfiguration {
}
@Configuration:标记配置类
@ComponentScan(value = {“com”}):扫描注解包,相当于xml中的
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
- 创建QueryRunner对象并存入IoC容器
@Bean(name = "queryRunner")
@Scope(value = "prototype")
public QueryRunner instancceQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean:作用是将当前方法的返回值作为一个Bean对象,存入IoC容器中。在IoC容器中Bean对象都是以Key-Value形式存储的,那么当@Bean不赋予属性时,它是的name(vaue)属性默认是当前方法名
@Scope(value = “prototype”):作用是创建多例,保证线程安全,如果这里不写@Scope注解或者不写属性或者Scope(value = “singleton”),那么创建的Bean对象默认是单例,存在着线程安全问题。
- 配置数据源
@Bean(name = "dataSource")
public DataSource instanceDataSource() throws PropertyVetoException {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://192.168.1.8:3306/studenms?serverTimezone=UTC");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("sa");
return comboPooledDataSource;
}
这里存在一个问题
创建QueryRunner对象的方法的参数报错,这是为什么?写一个方法为什么会参数报错。这是因为使用了@Bean注解,spring会在IoC容器中找dataSource的Bean对象,他没有找到DataSource的Bean对象,所以才会报错。它的寻找原理和@AutoWried的机制一样,后面介绍。
将instanceDataSource的注解放开,发现错误消失
神奇的框架!
- 删除bean.xml文件,修改测试代码
这里和使用xml注入的测试只有一处最重要的区别,其它都一样,就是这一行:
context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
区别在于AnnotationConfigApplicationContext与ClassPathXmlApplicationContext,从名称可以看出,这个类是用来读取注解配置的。
测试结果如下: