简介
SpringIOC可以帮我们完成对象的创建,以及给对象的属性赋值。
SpringIOC的设计出发点是最大化降低对象之间的关系(耦合度),提高代码的可重用性和可移植性。
IOC(Inversion of Control)控制反转的简称。以前我们经常用new来创建对象,并用setter方法设置对象与对象之间的依赖关系(例如一个学生对象student和一个老师对象teacher,我们通过student.setTeacher(teacher)绑定老师和学生对象之间的关系),坏处:对象和类(接口)、对象和对象之间太强的耦合关系。而IOC的本质就是存放那些对象创建、赋值放到IOC容器,在IOC容器中进行对象之间的依赖关系注入(DI)。IOC容器负责管理对象之间的依赖管理。
IOC容器获取
new出来的对象依赖于具体类,通过工厂模式获取对象依赖工厂。
实体类:
public interface ICourse
{
public abstract void learn();
}
public class JavaCourse implements ICourse
{
@Override
public void learn()
{
System.out.println("学习JAVA课程");
}
}
public class OracleCourse implements ICourse
{
@Override
public void learn()
{
System.out.println("学习Oracle课程");
}
}
一、对象的创建放到Spring配置文件(applicatoinContext.xml)中进行。如果需要使用对象,只需要现在Spring配置此对象(SpringIOC容器会帮助我们自动创建对象),然后直接从SpringIOC容器获取.
例如:
applicatoinContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans …>
…
<bean id="javaCourse" class="org.lzy.iocinstance.JavaCourse">
</bean>
<bean id="oracleCourse"
class="org.lzy.iocinstance.OracleCourse">
</bean>
</beans>
配置后,SpringIOC容器会替我们自动创建这两个对象(根据class值所代表的类型,创建相应的对象,并用id来标识该对象)。以后要使用两个对象时,可以通过ApplicationContext对象的getBean()方法,根据标识符的id,直接从SpringIOC容器获取。
如:
Student.java
public class Student
{
// 通过参数给getBean()方法传入不同的id值 来获取不同的课程对象
public void learnCourse()
{
ApplicationContext context
= new ClassPathXmlApplicationContext("applicatoinContext.xml");
ICourse course =(ICourse)context.getBean("oracleCourse");
course.learn();
}
}
SpringIOC容器的本质就是一个大工厂,交给Spring去维护,我们需要做就是Spring配置文件进行对象配置,然后通过getBean()方法从SpringIOC容器中获取。
控制反转:“控制”(对象创建)从Student类转移到SpringIOC容器。
依赖注入
DI:将SpringIOC容器中的资源,注入到某些对象之中。
实体类:
public class Teacher
{
private String name ;
private int age ;
//省略setter、getter
}
public class Course
{
private String courseName; //课程名
private int courseHours; //课时
private Teacher teacher; //授课老师
//省略setter、getter
public void showInfo()
{
System.out.println("课程名:"+courseName+"\t课时:
"+courseHours+"\t\t授课老师:"+teacher.getName());
}
}
applicatoinContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans …>
<bean id="teacher" class="org.lzy.dicinstance.Teacher">
<property name="name" value="李四"></property>
<property name="age" value="20"></property>
</bean>
<bean id="course" class="org.lzy.diinstance.Course">
<property name="courseName" value="JAVA"></property>
<property name="courseHours" value="100"></property>
<property name="teacher" ref="teacher"></property>
</bean>
</beans>
属性 | 简介 |
---|---|
value | 给name所表示的“简单类型”的属性赋值(如String、int) |
ref | 给name所表示的自定义类型的对象赋值(如Teacher类型的属性)。ref的值是另一个<bean> 的id值,从而实现多个<bean> 之间相互引用、相互依赖的关系 |
DI:SpringIOC容器不仅通过<property>
的value属性,给String courseName、 int courseHours这些类型的属性赋值,而且还可以通过<property>
的ref属性,给Teacher teacher这种类型对象也赋值。对象的值是通过SpringIOC容器注入进去。即依赖注入.
依赖注入的方式
常见的三种依赖注入方式:setter注入、构造器注入、p命名空间注入
(1)setter注入
本质通过反射机制,调用对象的setter方法,对属性进行赋值操作:
<bean id="course" class="org.lzy.diinstance.Course">
<property name="courseName" value="JAVA"></property>
<property name="teacher" ref="teacher"></property>
…
</bean>
在Course对象中寻找setCourseName()方法,如果存在,则将<property>
中的value的值“java”传入参数中,即调用对象的setter方法进行赋值。如果不存在该方法,则产生异常。
采用value或ref 这种setter方式给属性赋值,需有相应的setter方法。
(2)构造器注入
注意:手工编写有参构造方法后,JVM不在提供默认的无参构造方法,需编写。
applicationContext.xml
<bean id="teacher" class="org.lzy.diinstance.Teacher">
<!—-给构造方法的第0个参数赋值-->
<constructor-arg value="zy"></constructor-arg>
<!—-给构造方法的第1个参数赋值-->
<constructor-arg value="22"></constructor-arg>
</bean>
<constructor-arg>
顺序和构造方法中参数的顺序一致。
不一致,可用index或name属性指定
①使用index来指定参数的位置索引
<bean id="teacher" class="org.lzy.diinstance.Teacher">
<!—-通过index属性,指定给构造方法中的第1个参数赋值-->
<constructor-arg value="22" index="1"></constructor-arg>
<!—-通过index属性,指定给构造方法中的第0个参数赋值-->
<constructor-arg value="zy" index="0"></constructor-arg>
</bean>
②使用name属性来指定参数的属性名
<bean id="teacher" class="org.lzy.diinstance.Teacher">
<!—-通过name属性,指定给构造方法中的“age”参数赋值-->
<constructor-arg value="22" name="age"></constructor-arg>
<!—-通过name属性,指定给构造方法中的“name”参数赋值-->
<constructor-arg value="zy" name="name"></constructor-arg>
</bean>
如果A类有String str和int num两个属性,并且只有以下两个构造方法,public A(String str){…}和public A(int num) {…},该如何通过构造方法赋值?
例子:
<bean id="a" class="A">
<constructor-arg value="123" ></constructor-arg>
</bean>
说明:Spring无法知道”123”是String类型还是int类型(Spiring将所有“简单类型”的变量值,都写在value值的双引号中)。
解决方法<constructor-arg>
标签中的name或type属性解决。
①使用name属性来指定参数的属性名。
②使用type属性来指定参数值的类型。如下,
bean id="a" class="A">
<constructor-arg value="123" type="java.lang.String">
</constructor-arg>
</bean>
(3)p命名空间注入
必须在spring配置文件中引入“p命名空间”即xmlns:p=”http://www.springframework.org/schema/p”
例子
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="…" …>
</beans>
p命名空间注入的特点:直接使用“p”给对象属性赋值,简化配置代码。
<bean id="teacher" class="org.lzy.diinstance.Teacher"
p:name="zy" p:age="22">
</bean>
<bean id="course" class="org.lzy.diinstance.Course"
p:courseName="JAVA"
p:courseHours="680" p:teacher-ref="teacher">
</bean>
注入各种数据类型的属性
Spring提供了不同的标签来实现不同类型参数的注入
(1)使用setter设置注入方式,注入各种类型的属性
①注入“简单类型”的属性值
<bean id="teacher" class="org.lzy.dinstance.Teacher">
<!-- 使用子元素<value>注入 -->
<property name="name">
<value type="java.lang.String">lzy</value>
</property>
<!-- 使用value属性注入 -->
<property name="age" value="22"></property>
</bean>
两种注入参数值方式的区别
使用子元素<value> 注入 | 而使用value属性注入 | |
---|---|---|
参数位置 | 写在首尾标签(<value></value> )的中间(不加双引号) | 写在value的属性值中(必须加双引号) |
type属性 | 有(可选) 可以通过type属性指定数据类型 | 无 |
参数值包含特殊字符(<, &)时的处理方法 | 两种处理方法。 一、使用<![CDATA[ ]]> 标记 二、使用XML预定义的实体引用 | 一种处理方法。即使用XML预定义的实体引用 |
其中,XML预定义的实体引用,如下:
实体引用 | 表示的符号 |
---|---|
&It; | < |
& | & |
示例:
<bean id="student" class="org.lzy.entity.Student">
<property name="stuName">
<value><![CDATA[张&三]]></value>
</property>
</bean>
或
<bean id="student" class="org.lzy.entity.Student">
<property name="stuName">
<value>张&三</value>
</property>
</bean>
或
<bean id="student" class="org.lzy.entity.Student">
<property name="stuName" value="张&三"></property>
</bean>
给Student对象的stuName属性赋值为”张&;三”
②输入对象类型的属性值
使用<ref>
属性以外,还可以使用<ref>
子元素:
<bean id="course" class="org.lzy.diinstance.Course">
…
<property name="teacher" >
<ref bean="teacher"/>
</property>
</bean>
通过<ref>
子元素的bean属性,来指定需要引用的<bean>
的id值(即需要引用的对象)
特殊情况:如果需要引用的对象仅仅只需要使用一次,则可以使用“内部Bean”的方式来引用(类似于JAVA的“内部类”),如下:
<bean id="course" class="org.lzy.diinstance.Course">
…
<property name="teacher" >
<bean class="org.lzy.diinstance.Teacher">
<property name="name" value="lzy"></property>
<property name="age" value="22" ></property>
</bean>
</property>
</bean>
以上,就是使用“内部Bean”的方式,给Course对象注入了一个Teacher类型的属性。ps:“内部Bean”Teacher类所在的中并没有“id”属性。
③注入集合类型的属性值
对于集合或数组类型的属性,我们可以使用如下标签来注入属性值
类型 | 使用的标签 |
---|---|
List或数组 | 外层用<list> :内层用<value>或<ref> |
Set | 外层用<set> :内层用<value>或<ref> |
Map | 外层用<map> :中间层用<entry> ;内层中键用<key><value>…</value></key> ,值用<value>…</value> 或<ref>…</ref> |
Properties | 外层用<props> ;内层中键写在<prop key=”..”>..</prop> 的key值中,值写在<prop>..</prop> 中间。Properties中的键和值通常都是字符串类型。 |
具体示例如下:
AllConnectionType.java 包含各种集合类型的属性
public class AllConnectionType {
private List<String> list;
private String[] array;
private Set<String> set;
private Map<String, String> map;
private Properties props;
//省略setter、getter
//输出所有属性值
public void showInfo()
{
System.out.println("List属性:" + this.list);
System.out.print("数组属性:");
for (String arr : this.array)
{
System.out.print(arr+"\t");
}
System.out.println("\nSet属性:" + this.set);
System.out.println("Map属性:" + this.map);
System.out.println("Properties属性:" + this.props);
}
}
通过配置文件,注入全部的属性值,如下
applicationContext.xml
<bean id="connType" class="org.lanqiao.test.AllConnectionType" >
<!-- 注入List类型 -->
<property name="list">
<list>
<!-- 定义List中的元素 -->
<value>苹果</value>
<value>橘子</value>
</list>
</property>
<!-- 注入数组类型 -->
<property name="array">
<list>
<!-- 定义数组中的元素 -->
<value>苹果</value>
<value>橘子</value>
</list>
</property>
<!-- 注入Set类型 -->
<property name="set">
<list>
<!-- 定义Set或数组中的元素 -->
<value>苹果</value>
<value>橘子</value>
</list>
</property>
<!-- 注入Map类型 -->
<property name="map">
<map>
<!-- 定义Map中的键值对 -->
<entry>
<key>
<value>apple</value>
</key>
<value>苹果</value>
</entry>
<entry>
<key>
<value>orange</value>
</key>
<value>橘子</value>
</entry>
</map>
</property>
<!-- 注入Properties类型 -->
<property name="props">
<props>
<!-- 定义Properties中的键值对 -->
<prop key="apple">苹果</prop>
<prop key="orange">橘子</prop>
</props>
</property>
</bean>
如果集合的属性值包含对象类型,只需要把<value>改成<ref bean=""/>.
④注入null和空字符串
注入的值 | 使用的标签 |
---|---|
空字符串(如String comment = “”) | <value></value> ,即在<value> 标签中不写任何值 |
null(如Teacher =null) | <null> |
表示给Course对象的courseName属性赋值为空字符串,给teacher属性赋值为null
<bean id="course" class="org.lzy.diinstance.Course">
<property name="courseName">
<value></value>
</property>
<property name="teacher" ><null/></property>
</bean>
使用“构造器注入”方式,注入各种类型的属性
与 “setter设置注入”方式类似,只需要把上述<list>、<map>、< props >
等标签放入<constructor-age>和</constructor-age>
中间即可。
自动装配
使用IOC/DI后,对象与对象之间的关系是通过配置文件(ref属性)组织在一起,而不再是通过硬编码的方式耦合在一起了。弊端:需要额外编写大量配置文件。为了简化配置,可以使用Mybatis中“约定优于配置”原则。“自动装配”,适用对象类型(引用类型)的属性(即通过ref属性注入的<Bean>与<Bean>
之间的关系),而不适用简单类型(基本类型和String类型)。
applicationContext.xml
setter设值注入
<bean id="teacher" class="org.lzy.diinstance.Teacher" >
…
</bean>
<bean id="course" class="org.lzy.diinstance.Course">
…
<property name="courseHours" value="680"></property>
<property name="teacher" ref="teacher"></property>
</bean>
具体就是通过value为“简单类型”赋值,通过ref为对象类型赋值。而如果事先遵循一定的“约定”,就可以省略id为“course”的<bean>
中,使用<property>
为对象类型(Teacher对象)赋值的过程。
根据属性名自动装配
<bean id="teacher" class="org.lzy.diinstance.Teacher" >
…
</bean>
<bean id="course" class="org.lzy.diinstance.Course"
autowire="byName" />
给id=”course”的<bean>
中,加上 autowire=”byName”,就是为了告诉Spring这个<bean>
符合一定的“约定”,可以自动为对象类型的属性(即teacher)赋值。之后,Spring就会自动在其他<bean>
中,寻找id值与属性名“teacher”一致的<bean>
。如果找到,就会将找到的<bean>
注入到teacher属性之中,这就是根据“属性名”自动装配的约定。
通过autowire属性值来指定具体方式
autowrite属性值 | 自动装配方式 |
---|---|
no | 不使用自动装配。必须通过<property> 的ref属性来指定对象之间的依赖关系。 |
byName | 根据属性名自动装配。如果某一个<bean> 的id值,与当前<bean> 的某一个属性名相同,则自动注入;如果没有找到,则什么也不做。(本质是寻找属性名的setter方法) |
byType | 根据属性类型自动装配。如果某一个<bean> 的类型,恰好与当前<bean> 的某一个属性的类型相同,则主动注入;如果有多个 <bean> 的类型都与当前<bean> 的某一个属性的类型相同,则Spring将无法决定注入哪一个<bean> ,就会抛出一个异常;如果没有找到,则什么也不做。 |
constructor | 根据构造器自动装配。与byType类似,区别是它需要使用构造方法。如果Spring没有找到与构造方法参数列表一致的<bean> ,则会抛出异常。 |
根据属性类型自动装配
<bean id="teacher" class="org.lzy.diinstance.Teacher" >
…
</bean>
<bean id="course" class="org.lzy.diinstance.Course"
autowire="byType" />
autowire设置为“byType”以后,Spring就会在其他所有<bean>
中,寻找与Course中的teacher属性类型相同的<bean>
(即找Teacher类型的<bean>
),找到之后就会自动注入给teacher属性。
根据构造器自动装配
<bean id="teacher" class="org.lzy.diinstance.Teacher" >
…
</bean>
<bean id="course" class="org.lzy.diinstance.Course"
autowire="constructor" />
使用构造器自动装配,必须在Course类中先提供相应的构造方法。比如,本例是想通过构造方法,给Course类中的teacher属性赋值,则就必须在Course类中提供以下构造方法,
Course.java
public class Course
{
…
private Teacher teacher;
public Course(Teacher teacher)
{
this.teacher = teacher;
}
…
}
说明:
1.如果配置文件中所有的<bean>
都要使用自动装配,则除了在每一个<bean>
中设置autowire属性以外,还可以设置一个全局的“default-autowire”,用于给所有的<bean>
都注册一个默认的自动装配类型。设置方法是在配置文件里,<beans>
的属性中加入“default-autowire”属性,如下,
<beans xmlns="…"
xmlns:xsi="…"
xmlns:p="…"
xsi:schemaLocation="…"
default-autowire="byName">
<bean …> …</bean>
…
</beans>
表示给所有的<bean>
都设置成了“根据属性名自动装配”。当然,设置全局的“default-autowire”以后,还可以在单独的<bean>
中再次设置自己的“autowire”用来覆盖全局设置。
2.我们虽然可以通过自动装配,来减少Spring的配置编码。但是过多的自动装配,会降低程序的可读性。因此,对于大型的项目来说,并不鼓励使用自动装配。
三种自动装配的可读性为:byName>byType>constructor
基于注解形式IoC配置
对于Dao层、Service层、Contoller层中的类,还可以通过注解的形式来实现Spring IoC。
使用注解定义Bean
StudentDaoImpl.java
import org.springframework.stereotype.Component;
@Component("studentDao")
public class StudentDaoImpl implements IStudentDao
{
@Override
public void addStudent(Student student)
{
System.out.println("模拟增加学生操作...");
}
}
以上通过@Component定义了一个名为studentDao的Bean,@Component(“studentDao”)的作用等价于XML形式的<bean id="studentDao" class="org.lanqiao.dao.StudentDaoImpl" />。
@Component可以作用在DAO层、Service层、Controller层等任一层的类中,范围较广。此外,还可以使用以下3个细化的注解:
注解 | 范围 |
---|---|
@Repository | 用于标注DAO层的类 |
@Service | 用于标注Service层的类 |
@Controller | 用于标注Controller层的类 |
使用注解实现自动装配
可以使用@Autowrited注解实现多个Bean之间的自动装配:
@Service("studentService")
public class StudentServiceImpl implements IStudentService
{
//@Autowired标识的属性,默认会按“属性类型”自动装配
@Autowired
private IStudentDao studentDao ;
public void setStudentDao(IStudentDao studentDao)
{
this.studentDao = studentDao;
}
@Override
public void addStudent(Student student)
{
studentDao.addStudent(student);
}
}
通过@Service标识了一个业务Bean,并且使用Autowired为studentDao属性自动装配。@Autowired默认采用按“属性类型”自动装配,可以通过@Qualifier设置为按“属性名”自动装配,如下:
@Service("studentService")
public class StudentServiceImpl implements IStudentService
{
//指定@Autowired标识的属性,按“属性名”自动装配
@Autowired
@Qualifier("studentDao")
private IStudentDao studentDao ;
…
}
@Autowired除了可以对属性标识以外,还可以对setter方法进行标识,作用与对属性标识是相同的,如下:
@Service("studentService")
public class StudentServiceImpl implements IStudentService
{
private IStudentDao studentDao ;
@Autowired
public void setStudentDao(IStudentDao studentDao)
{
this.studentDao = studentDao;
}
…
}
扫描注解定义的Bean
使用@Controller、@Service、@Repository、@Component等标识完类(Bean)以后,还需要将这些类所在的包通过component-scan扫描后才能加载到Spring IoC容器之中,如下:
<beans…>
<context:component-scan base-package="org.lzy.dao,
org.lzy.service">
</context:component-scan>
</beans>
通过base-package属性指定需要扫描的基准包是org.lzy.dao和org.lzy.service,之后Spring就会扫描这两个包中的所有类(含子包中的类),将其中用@Service等标识的类加入到SpringIoC容器之中。此处在定义扫描包时用到了context,所以在使用前需要导入context命名空间
使用注解实现事务
@Transactional注解在Spring中配置声明式事务,需导入一下jar包:
spring-tx-4.2.5.RELEASE.jar | ojdbc6.jar | commons-dbcp-1.4.jar |
---|---|---|
commons-pool-1.6.jar | spring-jdbc-4.2.5.RELEASE.jar | aopalliance.jar |
并在Sring配置文件中配置数据源、事务管理类,以及添加对注解配置事务的支持:
<beans…>
…
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName"
value="oracle.jdbc.OracleDriver"/>
<property name="url"
value="jdbc:oracle:thin:@127.0.0.1:1521:XE"/>
<property name="username" value="system"/>
<property name="password" value="123"/>
<property name="maxActive" value="10"/>
<property name="maxIdle" value="5"/>
</bean>
<!-- 配置事务管理类 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource
.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 增加对注解配置事务的支持 -->
<tx:annotation-driven transaction-manager="txManager" />
</beans>
程序中使用@Transactional来配置事务
@Service("studentService")
public class StudentServiceImpl implements IStudentService
{
…
@Transactional(readOnly=false,
propagation=Propagation.REQUIRES_NEW)
@Override
public void addStudent(Student student)
{
studentDao.addStudent(student);
}
}
@Transactional的常用属性:
属性 | 类型 | 说明 |
---|---|---|
propagation | 枚举型:Propagation | (可选)事务传播行为。例如:propagation=Propagation.REQUIRES_NEW |
readOnly | 布尔型 | 是否为只读型事务。例如:readOnly=false |
isolation | 枚举型:isolation | (可选)事务隔离级别。例如:isolation=Isolation.READ_COMMITTED |
timeout | int型(单位:秒) | 事务超时时间。例如:timeout=20 |
rollbackFor | 一组Class类的实例,必须继承自Throwable | 一组异常类,遇到时必须进行回滚。例如:rollbackFor={SQLException.class,ArithmeticException.class} |
rollbackForClassName | 一组Class类的名称,必须继承自Throwable | 一组异常类名,遇到时必须进行回滚。例如:rollbackForClassName={“SQLException”,”ArithmeticException”} |
noRollbackFor | 一组Class类的实例,必须继承自Throwable | 一组异常类,遇到时必须不回滚 |
noRollbackForClassName | 一组Class类的名称,必须继承自Throwable | 一组异常类名,遇到时必须不回滚 |