Spring IOC/DI

简介

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.jarojdbc6.jarcommons-dbcp-1.4.jar
commons-pool-1.6.jarspring-jdbc-4.2.5.RELEASE.jaraopalliance.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
timeoutint型(单位:秒)事务超时时间。例如:timeout=20
rollbackFor一组Class类的实例,必须继承自Throwable一组异常类,遇到时必须进行回滚。例如:rollbackFor={SQLException.class,ArithmeticException.class}
rollbackForClassName一组Class类的名称,必须继承自Throwable一组异常类名,遇到时必须进行回滚。例如:rollbackForClassName={“SQLException”,”ArithmeticException”}
noRollbackFor一组Class类的实例,必须继承自Throwable一组异常类,遇到时必须不回滚
noRollbackForClassName一组Class类的名称,必须继承自Throwable一组异常类名,遇到时必须不回滚
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值