文章目录
1. Spring的DI
前面提到过,Spring的IOC(控制反转)有两种方式,一种是常见的依赖注入(Dependency Injection)
,另外的一种是依赖查找(Dependency Lookup)
。
在Spring中,依赖关系的管理都交由spring来维护,当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明即可。
这种依赖关系的维护叫做依赖注入。
依赖注入的类型有:
- 基本类型和String
- 其他的bean类型(在配置文件中或者注解中配置过的bean)
- 复杂类型/集合类型
注入的方式有三种:
- 使用构造器注入
- 使用set方法提供
- 使用注解提供
1.1 使用构造器注入
前面在学习6.Spring对bean的管理中,在6.1.1 使用默认构造函数创建bean
中,遇到一个问题。当类的构造器被重写后(入参改变了),如果继续用<bean></bean>
标签穿件,则会报错。
现在可以通过<bean></bean>
标签下的constructor-arg
标签使用重写后的构造器注入。
假如现在AccountServiceImpl的构造器如下:
public AccountServiceImpl(String name, String name1, Integer age, Date brithday) {
this.name = name;
this.name1 = name1;
this.age = age;
this.brithday = brithday;
}
那么它的bean的配置文件为:
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl">
<constructor-arg type="java.lang.String" value="你叫什么名字"></constructor-arg>
<constructor-arg type="java.lang.String" value="你好啊"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="brithday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
详细了解<constructor-arg></constructor-arg>
。
它是位于bean标签的内部的,用来使用构造器初始化一个bean的。相当于调用指定类的构造器。既然要使用构造器实例化某个对象。那么需要传入一些参数(无参构造器直接使用默认的bean配置即可)。为了传入参数,有一下问题:
- 如何定位到某个参数?
- 如何往参数传值?
总的来说,spring怎么知道往哪个
参数传入哪个
值。
如何定位到某个参数?
constructor-arg标签有三个属性:type、index、name
- type:要注入数据的数据类型,同时也是构造器入参的数据类型。如果构造器有多个同类型的属性,则依照配置循序依次注入。
- index:属性的顺序
- name:属性的名称
如何往参数传值?
- value:用于提供基本数据类型 以及基本数据的封装
- ref:前面的例子可以看出,他是传入引用类型的变量
小结
使用构造器注入的特点:
优势:
可以根据类的构造器传入指定值,比默认方式灵活。
缺点:
必须要按照构造器的参数来传值。
1.2 使用set方法注入
使用property标签完成set方式注入
//AccountServiceImpl2片段
public void setName(String name) {
this.name = name;
}
public void setName1(String name1) {
this.name1 = name1;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBrithday(Date brithday) {
this.brithday = brithday;
}
<bean id="accountService2" class="com.ssm.service.impl.AccountServiceImpl2">
<property name="name" value="使用set方式"></property>
<property name="age" value="20"></property>
<property name="brithday" ref="now"></property>
</bean>
属性 | 作用 |
---|---|
name | 定位到set方法,若方法名为setName,则name值为name |
value | 同上,用于提供基本数据类型 以及基本数据的封装 |
ref | 同上,传入引用类型的变量 |
注意:
使用set方式注入,其实还是用无参构造器实例化对象。若没有无参构造器,仍会报前面提到的问题。
1.4 对于集合等复杂类型的注入
private String[] myStr;
private List<String> myList;
private Map<String, String> myMap;
private Properties myProps;
public void setMyStr(String[] myStr) {
this.myStr = myStr;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
bean的内部
<property name="myStr">
<array>
<value>AAA</value>
<value>BBB</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
</list>
</property>
<property name="myMap">
<map>
<entry key="testA" value="value1"></entry>
<entry key="testB">
<value>value2</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="key1">hahahha</prop>
<prop key="key2">lalala</prop>
</props>
</property>
注入方式有
对于String[],List,Set类型的变量
使用<array>
,</list>
,<set>
方式注入。
对于map,props类型等映射关系变量
使用<map>
,<prop>
方式注入。
2. 基于注解的DI
前面我们学习的所有的DI都是基于xml配置文件的。如果一个项目有几十上百的bean。那么它的配置文件将会显得十分冗长庞大。使用注解就能解决这个问题。基于这一点,基于注解的DI应该与基于配置文件的DI的作用应该是一样的,只是实现方式不一样。
回顾前面的知识,我们可以总结出基于xml配置文件实现的功能总共分为四类:
- 用来创建对象,bean标签
- 设置对象的作用范围(单例、多例还是其他),scope属性
- 设置bean的生命周期,init-method属性
- 为bean注入数据,property标签
那么注解应该也能实现上述四种功能
2.1 使用@Component注解创建对象
使用注解创建对象有一下几步:
- 在类上添加注解
- 在bean.xml中配置
为类AccountServiceImpl
添加注解
//使用@Component创建对象(无参构造器),对象名(id)为accountService,若不配置对象,则对象名为accountServiceImpl
@Component(value = "accountService")
public class AccountServiceImpl implements IAccountService {
}
在bean.xml中配置context的名称空间与约束。
前面使用的配置
<?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">
</beans>
其中 xmlns是xml命名空间的意思,而xmlns:xsi是指xml所遵守的标签规范。
- xmlns:关于初始化bean的格式文件地址
- xmlns:xsi:辅助初始化bean
- xsi:context:关于spring上下文,包括加载资源文件
- xsi:schemaLocation:用于声明了目标名称空间的模式文档
因为配置扫描的标签不在beans的约束中,而在一个名为context的名称空间和约束中,所以需要引入。
<?xml version="1.0" encoding="UTF-8"?>
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告诉Spring在创建容器需要扫描的包为com.ssm-->
<context:component-scan base-package="com.ssm"></context:component-scan>
</beans>
用上述方法创建对象时,容器会把包中的所有对象实例化。不管有没有被使用。
2.2 使用@Autowired自动注入对象
- @Controller:一般用于表现层
- @Service:一般用于业务层
- @Repository:一般用于持久层
以上三个注解作用于属性与@Component基本一样,有点类似于父类-子类的关系。
@Autowired先根据数据类型IAccountDao
注入对象,如果容器中没有,报错;如果有唯一一个,成功;
如果有多个,在根据变量名称accountDao
注入,如果没有,报错;如果有唯一一位,成功;
2.2 @Qualifier
按照类中注入的基础上,在按照名称注入。在给类成员注入时不能单独使用,但是在给方法参数注入时可以。
3. 一个IOC案例
创建一个account表
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`money` float(255,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
然后创建一个工程,能够对该表实现CRUD操作。
主要有
- 查询所有账户的信息
- 查询一个账户的信息,根据id
- 保存一个账户
- 修改一个账户
- 删除一个账户
要求有分为业务层和持久层。还有junit测试单元
3.1 基于xml配置文件
- 创建一个maven工程,修改打包方式,引入相关依赖。
- 创建模型类Account,业务层接口IAccountService,持久层接口IAccountDao。
- 实现IAccountService接口,调用持久层相关方法;实现IAccountDao接口,创建
org.apache.commons.dbutils.QueryRunner
对象 - 配置bean.xml文件
- 编写junit测试单元,测试IAccountService的相关方法
3.2 基于注解
基于注解的IOC案例
存在一个问题,还是需要xml配置bean。
<?xml version="1.0" encoding="UTF-8"?>
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告诉Spring在创建容器需要扫描的包为com.ssm-->
<context:component-scan base-package="com.ssm"></context:component-scan>
<!--配置runner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的基本信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
3.3 完全基于注解的配置
前面的配置中,AccountServiceImpl和AccountDaoImpl可以通过注解加到容器中。
对象名 | 类 | 加入方式 |
---|---|---|
accountServiceImpl | com.ssm.service.impl.AccountServiceImpl | 通过@Service加到Spring核心容器中 |
accountDao | com.ssm.dao.impl.AccountDaoImpl | 通过@Repository(value = “accountDao”)加入 |
runner(多例) | org.apache.commons.dbutils.QueryRunner | bean.xml |
dataSource | com.mchange.v2.c3p0.ComboPooledDataSource | bean.xml |
测试类中,核心容器创建过程还是
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
现在配置一种完全舍弃bean.xml的方式。
这时需要两个注解
- @Configuration:指定当前类是一个配置类
- @Companent:通过注解指定spring在创建容器时需要扫描的包
整个类配置如下
package config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.sql.DataSource;
@Configuration
@ComponentScan(basePackages = "com.ssm")
public class SpringConfiguration {
@Bean(name = "runner")
@Scope(value = "prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource(){
ComboPooledDataSource ds = null;
try {
ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8");
ds.setUser("root");
ds.setPassword("123456");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
其中涉及的注解
@Configuration:
@ComponentScan
@Bean
@Scope
4.Spring的IOC总结
回顾前面的学习,我们发现使用Spring的IOC一个最重要的目的是解耦。
它的解耦方式是:IOC容器管理所有的对象,其他对象想使用某个对象,就需要通过对象id来获取。
4.1 对象交由IOC管理
Spring创建Bean的三种方式
使用默认的构造函数(无参即可)创建
<bean id="accountService" class="com.ssm.service.impl.AccountService"></bean>
调用B类的方法来创建A类的对象,主要针对第三方jar包。
<!--先实例化B类(instanceFactory)-->
<bean id="instanceFactory" class="com.ssm.factory.InstanceFactory"></bean>
<!--调用B类的getAccountService方法,创建一个A类的对象-->
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
调用B类的静态方法创建A类对象,无需实例化B类
<!--使用B类的静态方法创建对象A-->
<bean id="accountService" class="com.ssm.factory.StaticFactory" factory-method="getAccountService"></bean>