依赖注入
Spring Ioc简单来讲就是让spring框架(或者说容器, 工厂)帮我们创建bean, 而DI(依赖注入)则是spring帮我们注入属性
注入是什么意思? 简单来讲就是给对象设置值
注入的方式:
- 方式1: 调用对象的setter方法
- 方式2: 创建对象时(即调用对象构造器时)设置对象的属性值
xml自动装配(不推荐)
自动装配对应bean元素的autowire
属性, 让spring按照一定的规则自己去找对应的对象, 并完成DI操作.
可选值有:
- default: 不要自动注入, 相当于
no
- no: 不要自动注入
- byName: 按照属性的id在spring中寻找bean, 类似
factory.getBean(String beanName);
- byType: 按照依赖对象的类型注入, 类似
factory.getBean(Class requiredType)
- constructor: 按照对象的构造器上面的参数类型注入
bean:
package _02_DI._01_xml_autowire;
public class SomeBean {
private OtherBean otherBean;
public void setOtherBean(OtherBean otherBean) {
this.otherBean = otherBean;
}
@Override
public String toString() {
return "SomeBean [otherBean=" + otherBean + "]";
}
}
class OtherBean {
}
配置文件:
<bean id="otherBean" class="_02_DI._01_xml_autowire.OtherBean" />
<bean id="someBean" class="_02_DI._01_xml_autowire.SomeBean" autowire="byType" />
测试代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:_02_DI/_01_xml_autowire/AppTest.xml")
public class App {
@Autowired
private SomeBean someBean;
@Test
public void testXmlAutoWire() throws Exception {
System.out.println(someBean);
}
}
通过setter方法注入
使用bean元素中的property
子元素, 通过对象的setter方法完成设置操作
注入值类型:
- 常量值(简单类型), value属性
- 对象, ref属性
- 集合, 对应集合类型元素
value属性给简单类型设置值
bean:
package _02_DI._02_setter_wire;
public class Obj {
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Obj [name=" + name + ", age=" + age + "]";
}
}
xml:
<bean id="obj" class="_02_DI._02_setter_wire.Obj">
<property name="name">
<value>Fighter</value>
</property>
<property name="age" value="21" />
</bean>
注意:
name
属性的值是bean中被注入的属性的名称;value
属性(value子元素)的值是被注入的值- 两种设置值的方式等价
- 传入时都传入的是字符串, spring会自动转换类型
测试代码:
@Autowired
private Obj obj;
@Test
public void test01() throws Exception {
System.out.println(obj);
}
ref属性给对象类型设置值
bean:
package _02_DI._02_setter_wire._02_obj_setter;
public class Obj {
private Wired wired;
public void setWired(Wired wired) {
this.wired = wired;
}
@Override
public String toString() {
return "Obj [wired=" + wired + "]";
}
}
class Wired {
}
xml:
<bean id="wired" class="_02_DI._02_setter_wire._02_obj_setter.Wired" />
<bean id="obj" class="_02_DI._02_setter_wire._02_obj_setter.Obj">
<property name="wired" ref="wired" />
</bean>
ref
属性与被注入的对象的id
属性相同
给集合类型设置值
bean:
package _02_DI._02_setter_wire._03_collection_wire;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class CollectionBean {
private Set<String> s;
private List<String> l;
private String[] array;
private Map<String, String> map;
private Properties p;
public void setS(Set<String> s) {
this.s = s;
}
public void setL(List<String> l) {
this.l = l;
}
public void setArray(String[] array) {
this.array = array;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setP(Properties p) {
this.p = p;
}
@Override
public String toString() {
return "CollectionBean [\ns=" + s + ", \nl=" + l + ", \narray="
+ Arrays.toString(array) + ", \nmap=" + map + ", \np=" + p + "\n]";
}
}
配置文件:
<bean id="cb" class="_02_DI._02_setter_wire._03_collection_wire.CollectionBean">
<!-- 给属性注入值一律用property子元素 -->
<property name="s">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="l">
<list>
<value>list1</value>
<value>list2</value>
</list>
</property>
<property name="array">
<array>
<value>array1</value>
<value>array2</value>
</array>
</property>
<property name="map">
<map>
<entry key="key1" value="value1" />
<entry key="key2" value="value2" />
</map>
</property>
<property name="p">
<value>
p1=v1
p2=v2
</value>
</property>
</bean>
说明:
- 给属性注入值一律用property子元素, 不管是简单类型还是对象类型还是集合类型
- map类型由键值对组成, 所以需要使用entry子元素, 在entry中设置key和value
- properties类型其实本质上属于map类型, 所以使用和map一样的注入方式也是可以的. 但更推荐使用演示的这种写法
- 经过测试, 发现set, list和array这三个其实是可以乱用的, 原因查明了再回来补, 留个坑
构造器注入
以上演示的是setter方法注入, 底层调用对象的setter方法, 下面的案例演示通过构造器注入(bean不用提供setter方法)
setter注入使用的是<property>
元素, 表示bean的属性; 而构造器注入使用的是<constructor-arg>
元素
构造器注入有多钟方式, 如按照默认顺序, 使用index, 使用type等, 但不推荐, 推荐使用name, 与<property>
元素里的name一样代表属性的名字, 下面案例很容易看懂其用法
bean:
package _02_DI._02_setter_wire._04_constructor_setter;
import java.util.List;
import java.util.Properties;
public class Bean {
private String name;
private Integer age;
private OtherBean otherBean;
private List<String> list;
private Properties p;
public Bean(String name, Integer age, OtherBean otherBean,
List<String> list, Properties p) {
this.name = name;
this.age = age;
this.otherBean = otherBean;
this.list = list;
this.p = p;
}
@Override
public String toString() {
return "Bean [name=" + name + ", age=" + age + ", otherBean="
+ otherBean + ", list=" + list + ", p=" + p + "]";
}
}
class OtherBean{
}
配置文件:
<bean id="ob" class="_02_DI._02_setter_wire._04_constructor_setter.OtherBean" />
<bean id="bean" class="_02_DI._02_setter_wire._04_constructor_setter.Bean">
<constructor-arg name="name" value="Fighter" />
<constructor-arg name="age" value="21" />
<constructor-arg name="otherBean" ref="ob" />
<constructor-arg name="list">
<list>
<value>list1</value>
<value>list2</value>
</list>
</constructor-arg>
<constructor-arg name="p">
<value>
k1=v1
k2=v2
</value>
</constructor-arg>
</bean>
bean元素的继承
先看一下这个案例(普通的bean注入的演示):
bean:
package _02_DI._03_inheritance;
public class Bean1 {
private String name;
private Integer age;
private Integer id;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "Bean1 [name=" + name + ", age=" + age + ", id=" + id + "]";
}
}
//---------------------------------------------
package _02_DI._03_inheritance;
public class Bean2 {
private String name;
private Integer age;
private String color;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Bean2 [name=" + name + ", age=" + age + ", color=" + color
+ "]";
}
}
这里有两个bean, 两者都有name
属性和age
属性
配置文件:
<bean id="bean1" class="_02_DI._03_inheritance.Bean1">
<property name="name" value="Fighter" />
<property name="age" value="21" />
<property name="id" value="201603775" />
</bean>
<bean id="bean2" class="_02_DI._03_inheritance.Bean2">
<property name="name" value="Fighter" />
<property name="age" value="21" />
<property name="color" value="deepskyblue" />
</bean>
可以发现, 两个bean的两个属性是一样的, 而且值也一样, 这样就可以把相同的地方抽取出来
现在配置文件应该这样写:
<bean id="base_bean" abstract="true">
<property name="name" value="Fighter" />
<property name="age" value="21" />
</bean>
<bean id="bean1" class="_02_DI._03_inheritance.Bean1" parent="base_bean">
<property name="id" value="201603775" />
</bean>
<bean id="bean2" class="_02_DI._03_inheritance.Bean2" parent="base_bean">
<property name="color" value="deepskyblue" />
</bean>
说明:
- abstract="true"表示spring不需要为我们创建对象
- bean的继承和java继承不一样, bean继承是把相同的代码抽取出来, 相当于一段xml代码的拷贝
- bean元素的parent属性表示继承于哪一个bean
配置数据库连接池
之前给数据库连接池设置连接四要素时是这样设置的(以druid为例):
public class App {
private DruidDataSource ds;
@Test
public void test01() throws Exception {
ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/jdbcdemo");
ds.setUsername("root");
ds.setPassword("1092568516");
Connection conn = ds.getConnection();
...
}
}
观察一下可以发现, 创建连接池对象实际上就是new出来一个连接池, 然后给它设置属性
现在我们交给spring帮我们管理连接池:
配置文件:
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/jdbcdemo" />
<property name="username" value="root" />
<property name="password" value="1092568516" />
</bean>
测试代码:
@Autowired
private DruidDataSource ds;
@Test
public void test01() throws Exception {
Connection conn = ds.getConnection();
...
}
但是上述代码耦合度太高了, 应该把连接数据库的四要素写在properties文件里面, 方便维护(比如哪天不用MySql而用用其他数据库, 不用修改源代码), 然后让xml配置文件加载properties文件
properties文件:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcdemo
username=root
password=1092568516
现在就该让xml配置文件读取properties文件了:
<?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">
<!-- 加载db.properties配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
</beans>
一运行会发现报错, 原因是因为java中当前系统账户的属性也叫username
, 为避免冲突, 在properties文件中所有key值前加上jdbc.
新的properties:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbcdemo
jdbc.username=root
jdbc.password=1092568516
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"
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">
<!-- 加载db.properties配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
</beans>
现在就没问题了
注册测试案例
到这里IoC和DI差不多就这些了, 下面讲一个注册的案例, 将之前的知识点融合一下
domain类:
package _02_DI._05_register.domain;
@Getter@Setter@toString
public class User {
private Long id;
private String username;
private int age;
新建dao包, 包下新建IUserDao接口和其实现类:
package _02_DI._05_register.dao;
import _02_DI._05_register.domain.User;
public interface IUserDao {
void save(User u);
}
package _02_DI._05_register.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import _02_DI._05_register.dao.IUserDao;
import _02_DI._05_register.domain.User;
public class UserDaoImpl implements IUserDao {
private DataSource ds;
public void setDs(DataSource ds) {
this.ds = ds;
}
public void save(User u) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = ds.getConnection();
String sql = "INSERT INTO `t_student` (sname, age) VALUE (?, ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, u.getUsername());
ps.setInt(2, u.getAge());
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally{
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
}
}
然后在xml配置文件中配置数据库连接池和dao的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">
<!-- 加载db.properties配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="userDao" class="_02_DI._05_register.dao.impl.UserDaoImpl">
<property name="ds" ref="ds" />
</bean>
</beans>
新建service包, 包下新建IUserService接口和其实现类, 实现类中调用dao中的save方法
package _02_DI._05_register.service;
import _02_DI._05_register.domain.User;
public interface IUserService {
void Register(User u);
}
package _02_DI._05_register.service.impl;
import _02_DI._05_register.dao.IUserDao;
import _02_DI._05_register.domain.User;
import _02_DI._05_register.service.IUserService;
public class UserServiceImpl implements IUserService {
IUserDao userDao;
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
public void Register(User u) {
userDao.save(u);
}
}
配置文件中添加service的配置:
<bean id="userService" class="_02_DI._05_register.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao" />
</bean>
新建action包, 包下新建UserAction类, 调用service的register方法:
package _02_DI._05_register.action;
import _02_DI._05_register.domain.User;
import _02_DI._05_register.service.IUserService;
public class UserAction {
private IUserService userService;
public void setUserService(IUserService userService) {
this.userService = userService;
}
public String execute(){
userService.Register(new User());
return "Success";
}
}
配置文件添加action的bean配置:
<bean id="userAction" class="_02_DI._05_register.action.UserAction">
<property name="userService" ref="userService" />
</bean>
测试代码, 注入UserAction类, 调用其execute方法, execute方法会调用service的register方法, service会调用dao的save方法:
package _02_DI._05_register;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import _02_DI._05_register.action.UserAction;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:_02_DI/_05_register/AppTest.xml")
public class App {
@Autowired
private UserAction userAction;
@Test
public void testDao(){
userAction.execute();
}
}
最后运行成功, 但数据库新增的一行中没有数据(因为没有给User设置属性)