王小码

导航

Spring(3)使用 spring 的IOC解决程序耦合

一、环境搭建

  此处拿账户的业务层和持久层的依赖关系举例(简单的模拟,不涉及增删改查操作)解决它们之间的依赖。 

1.创建Java的maven工程

(1)选择File → New → MavenProject 开始创建Maven项目

 

 (2)选择要创建的Maven项目原型

 

(3)输入创建Maven项目所必须的参数

2.测试代码编写

(1)引入需要的jar包,修改pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.xhbjava</groupId>
  <artifactId>Spring02</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>Spring02</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>

(2)创建业务层接口类和实现类

package com.xhbjava.service;

/**
 * 账户的业务层接口
 * 
 * @author mr.wang
 *
 */
public interface IAccountService {
    /**
     * 保存账户
     */
    void saveAccount();

}
package com.xhbjava.service.impl;

import com.xhbjava.dao.IAccoutDao;
import com.xhbjava.dao.impl.AccountDaoImpl;
import com.xhbjava.service.IAccountService;
/**
 * 账户业务层接口实现类
 * @author mr.wang
 *
 */
public class AccountServiceImpl implements IAccountService {
    //此处依赖有待解决
    private IAccoutDao accountDao = new AccountDaoImpl();
    public void saveAccount() {
        accountDao.saveAccount();
    }

}

(3)创建账户的持久层接口和实现类

package com.xhbjava.dao;

/**
 * 账户持久层接口
 * 
 * @author mr.wang
 *
 */
public interface IAccoutDao {
    /**
     * 保存账户
     */
    void saveAccount();

}
package com.xhbjava.dao.impl;

import com.xhbjava.dao.IAccoutDao;

/**
 * 用户持久层接口实现类
 * 
 * @author mr.wang
 *
 */
public class AccountDaoImpl implements IAccoutDao {
    /**
     * 保存账户
     */
    public void saveAccount() {
        System.out.println("保存了账户");
    }

}

二、基于XML配置

1.工程根目录创建bean.xml(该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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    
</beans>

2.让Spring管理资源,在配置文件中配置Service和Dao

<?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">
<bean id="accountService" class="com.xhbjava.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.xhbjava.dao.impl.AccountDaoImpl"></bean>
    
</beans>

3.编写测试类进程测试

package com.xhbjava.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xhbjava.dao.IAccoutDao;
import com.xhbjava.service.IAccountService;

public class testSpring {
    
    public static void main(String args[]) {
        //1.使用ApplicationContest接口获取srping容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据beanid获取对象
        IAccountService accountService = (IAccountService) ac.getBean("accountService");
        System.out.println(accountService);
        
        IAccoutDao accountDao = (IAccoutDao) ac.getBean("accountDao");
        System.out.println(accountDao);
        
    }

}

 

 三、Spring基于XML的IOC细节

1.Spring 中工厂的类结构

 

 

 BeanFactory 是Srping容器中顶层的接口(适用于多例模式),ApplicationContext是其子接口(适用于单例模式)。

 

 

 (1)BeanFactory 和 和 ApplicationContext 的区别

  创建对象时间点不一样:

  BeanFactory:什么使用什么时候创建对象。

  ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。

(2)ApplicationContext 接口的实现类

  ClassPathXmlApplicationContext :它是从类的根路径下加载配置文件 推荐使用这种

  FileSystemXmlApplicationContext :它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

  AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
2.
IOC中bean标签和管理对象细节

(1)bean标签

  作用:用于配置对象让Spring来创建。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

(2)bean标签属性介绍

<bean 
     --给对象在容器中提供一个唯一标识。用于获取对象。
    id="accountDao"
    --指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数
    class="com.xhbjava.dao.impl.AccountDaoImpl"
     -- 指定对象的作用范围:取值及其含义如下
        -- singleton: 默认值,单例的【常用】
        -- prototype: 多例的
        -- request: web项目中,Spring创建一个Bean对象,将对象存入request域中,请求范围
        -- session: web项目中,Spring创建一个Bean对象,将对象存入session域中,会话范围
        -- golbal-session: web项目中,【应用在Protlet环境,集群环境】,如果没有Protlet
                 环境,那么golbalSession相当于session
    scope="prototype"
     -- 指定类中的初始化方法的名称
    init-method="init"
    -- 延迟加载,默认false
    lazy-init="true"
    -- 指定类中销毁方法的名称
    destroy-method="destory"
    
    ></bean>

(3)bean 的作用范围和生命周期

       单例对象:属性scope="singleton"

一个应用只有一个对象的实例。作用范围就是整个引用

  • 生命周期
    • 对象出生:当应用加载,创建容器时,对象就被创建了。
    • 对象活着:容器在,对象在
    • 对象拜拜:应用卸载、容器销毁时。

      多例对象:属性scope="prototype"

每次访问对象时,都会重新创建对象实例;销毁方法就不会被执行了!控制权交给GC了

  • 生命周期
    • 对象出生:当使用对象时
    • 对象活着:对象在被使用中
    • 对象拜拜:长时间不使用,被java的垃圾回收机制GC回收了

3. 实例化Bean的三种方式

(1)使用默认无参构造函数

  在默认情况下,会根据默认无参构造函数来创建类对象,如果没有默认无参构造函数,将会创建失败。

<bean id="accountService"    class="com.xhbjava.service.impl.AccountServiceImpl"></bean>

(2) spring管理【静态】工厂-使用静态工厂的方式创建对象【使用jar包中的类】

   创建模拟工厂类:

package com.xhbjava.factory;

import com.xhbjava.service.IAccountService;
import com.xhbjava.service.impl.AccountServiceImpl;
/**
 * 模拟静态工厂,创建业务层实现类
 * @author mr.wang
 *
 */
public class StaticFactory {
    public static IAccountService createAccountService() {
        return new AccountServiceImpl();
    }

}

修改bean.xml:

使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法

    <bean id="accountService" class="com.xhbjava.factory.StaticFactory" factory-method="createAccountService"></bean>

(3)spring 管理实例工厂- 使用实例工厂的方法创建对象

  创建实例工厂类:

  

/**
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*/
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}

   修改bean.xml:

  该方式是先把工厂的创建交给 spring 来管理。然后在使用工厂的 bean 来调用里面的方法。
  factory-bean 属性:用于指定实例工厂 bean 的 id。
  factory-method 属性:用于指定实例工厂中创建对象的方法。

<bean id="instancFactory" class="om.xhbjava.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"></bean>

4.Spring的依赖注入

(1)概念

  依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。

  通过前面学习我们了解了控制反转,通过控制反转我们将创建对象的任务交给了Spring,我们的代码中不会没有依赖的情况,通过ioc解耦只是降低各个代码之间的依赖关系,但是不会消除。在使用了Spring之后我们处理依赖关系就让Spring来维护。

  在实际环境中实现IoC容器的方式主要有两类,一类是依赖查找,即通过资源定位把对应的资源找回来;另一类则是依赖注入,Spring主要使用的就是依赖注入。一般而言依赖注入分为以下3中方式:

  • 构造器(构造函数)注入
  • setter注入
  • 接口注入

  构造器注入和setter注入是主要的方式,而接口注入是从别的地方注入的方式,比如web工程中配置数据源通常是通过服务器去配置,这个时候可以用JNDI的形式通过接口将它注入Spring IOC容器中。

(2)构造函数注入

  就是通过类中的构造函数给成员变量赋值,这个赋值操是通过配置的方式由Spring框架来进行注入。

  具体代码如下:

  类中需要提供一个对应参数列表的构造函数。

package com.xhbjava.pojo;

import java.util.Date;

public class Account {
    private String name;
    private String age;
    private Date birthday;
    
    public Account(String name, String age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Account [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }

}
<?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">
<bean id="account" class="com.xhbjava.pojo.Account">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="17"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
</beans>

  涉及标签的属性:

  constructor-arg

    index:指定参数在构造函数参数列表的索引位置

    type:指定参数在构造函数中的数据类型

    name:指定参数在构造函数中的名称

    value:它能赋的值是基本数据类型和 String 类型

    ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean

测试类测试:

package com.xhbjava.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xhbjava.pojo.Account;

public class testSpring {
    
    public static void main(String args[]) {
        //1.使用ApplicationContest接口获取srping容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据beanid获取对象
        Account account = (Account) ac.getBean("account");
        System.out.println(account);
        
    }

}

 

 (3)使用setter注入

  1)setter注入

  setter注入是Spring中最主流的注入方式,它利用Java Bean规范所定义的setter方法来完成注入,灵活且可读性高。它消除了使用构造器注入时出现多个参数的可能性,首先可以把构造方法声明为无参数的,然后利用setter注入为其设置对应的值,其实也是通过Java反射实现的。

  代码示例如下:

package com.xhbjava.pojo;

import java.util.Date;

public class Account1 {
    private String name;
    private String age;
    private Date birthday;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Account [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }

}
<?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">
<bean id="account" class="com.xhbjava.pojo.Account">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="17"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
</beans>

测试类进行测试:

  2)使用 p 名称空间注入数据(本质还是调用 setter方法)

  此种方式是通过在 xml中导入 p名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能。

  代码示例如下:

  

package com.xhbjava.pojo;

import java.util.Date;

public class Account2 {
    private String name;
    private String age;
    private Date birthday;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Account [name=" + name + ", age=" + age + ", birthday=" + birthday + "]";
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
    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">
<!-- <bean id="account" class="com.xhbjava.pojo.Account">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="17"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean> -->
<bean id="account" class="com.xhbjava.pojo.Account2"  p:name="test" p:age="20" p:birthday-ref="now"/>
<bean id="now" class="java.util.Date"></bean>
</beans>

测试类测试:

3)注入集合属性

  就是给类的集合成员传值,其本质也是setter方法注入,只不变量的数据类型都是集合。

  这里介绍注入数组,List,Set,Map和Properties。

  代码示例如下:

  

package com.xhbjava.pojo;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Account {
    private String [] myStr;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;
    public String[] getMyStr() {
        return myStr;
    }
    public void setMyStr(String[] myStr) {
        this.myStr = myStr;
    }
    public List<String> getMyList() {
        return myList;
    }
    public void setMyList(List<String> myList) {
        this.myList = myList;
    }
    public Set<String> getMySet() {
        return mySet;
    }
    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }
    public Map<String, String> getMyMap() {
        return myMap;
    }
    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }
    public Properties getMyProps() {
        return myProps;
    }
    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }
    @Override
    public String toString() {
        return "Account [myStr=" + Arrays.toString(myStr) + ", myList=" + myList + ", mySet=" + mySet + ", myMap="
                + myMap + ", myProps=" + myProps + "]";
    }
    
    

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    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">
    <bean id="account" class="com.xhbjava.pojo.Account">
    <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
        <property name="myStr">
            <set>
                <value>aaa</value>
                <value>bbb</value>
            </set>
        </property>
        <!-- 注入 list 集合数据 -->
        <property name="myList">
            <array>
                <value>123</value>
                <value>123</value>
            </array>
        </property>
        <!-- 注入 set 集合数据 -->
        <property name="mySet">
            <list>
                <value>234</value>
            </list>
        </property>
        <!-- 注入 Map 数据 -->
        <property name="myMap">
            <props>
                <prop key="a">aaa</prop>
                <prop key="b">bbb</prop>
            </props>
        </property>
        <!-- 注入 properties 数据 -->
        <property name="myProps">
            <map>
                <entry key="a" value="a"></entry>
                <entry key="b">
                    <value>b</value>
                </entry>
            </map>
        </property>
    </bean>

</beans>

测试类进行测试:

  

 

 

(4)接口注入

   有时候我们需要的资源并非来自系统本身,而是来自外界,比如数据库连接资源在Tomcat下配置,然后通过JNDI形式去获取,这样数据库连接资源是属于工程外的资源,此时我们可以采用接口注入的方式来获取,比如在Tomcat中可以配置数据源,在Eclipse中配置Tomcat后,可以打开context.xml.如图:

  

 

   我们在context.xml中添加我们自己的一个资源:

  

<?xml version="1.0" encoding="UTF-8"?>
<Context>

  <Resource name="jdbc/ssm" auth="Container" type="javax.sql.DataSource" dirverClassName="com.mysql.jdbc.Driver"
  url="jdbc:mysql://localhost:3306/ssm?zeroDateTimeBehavior=convertToNull" username="root" password="root">
  
  </Resource>
    
</Context>

  如果我们配置了相应数据库连接,那么eclipse会把数据库的驱动包复制到对应的Tomcat的lib文件夹下,否则需要自己手复制驱动包放到Tomcat的工作目录下,启动Tomca,此时数据库资源也会在Tomcat启动的时候被其加载进来。

  如果Tomcat的Web工程使用了Spring可以通过Spring机制,用JNDI获取Tomcat启动时的数据库连接池,具体代码如下:

  

<?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">

    <bean id="dataSource"
        class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:com/xhbjava/jdbc/ssm</value>
        </property>
</bean> </beans>

  此时我们就可以在Spring的IoC容器中获取Tomcat所管理的数据库连接池了,这就是一种接口注入的形式。

 

posted on 2020-05-20 13:01  王小码  阅读(228)  评论(0编辑  收藏  举报