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所管理的数据库连接池了,这就是一种接口注入的形式。