Spring IOC/DI

来源:互联网 发布:初音手办淘宝 编辑:程序博客网 时间:2024/05/19 00:40

简介

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>张&amp;三</value>      </property>    </bean><bean id="student" class="org.lzy.entity.Student">     <property name="stuName" value="张&amp;三"></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 一组异常类名,遇到时必须不回滚