IoC的概念和作用
程序的耦合和解耦
什么是程序的耦合
在软件工程中,耦合就是指对象之间的依赖性。
理解:实现一个类时要new 一个对象[其他类]
比如:早期我们的JDBC操作,为什么不使用DriverManger的register方法,而是采用Class.forName的方式
public class JdbcDemo1 {
public static void main(String[] args) {
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
//3.获取预处理 sql 语句对象
//4.获取结果集
//5.遍历结果集
}
}
原因就是:我们的类依赖了数据库的具体驱动类(MySQL),如果这时候更换了数据库品牌(比如Oracle),需要修改源码。
开发中,有些依赖关系是必须的,有些依赖关系是可以通过优化代码来解除的。
再比如:
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种编译器依赖关系,应该在开发中杜绝。
解决程序耦合的思路
我们一般通过反射来注册驱动:
Class.forName("com.mysql.jdbc.Driver");
此时我们类不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译[不可能允许。因为没驱动]。
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改
源码。
解决这个问题也很简单,使用配置文件配置。
工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来使用的时候,直接拿过来用。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
控制反转IoC
上面提到的解耦有两个问题需要解决:
- 1、存到那里去?
分析:因为有很多对象,所以需要一个集合。又因为需要查找对象,因此用map
解答:在应用加载的时候,创建一个map,用于存放三层对象。
这个map就叫做容器。
- 2、工厂是什么?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变:
原来:获取对象是,采用new方式。是主动的。
现在:获取对象时跟工厂要,工厂来查找或者创建对象。是被动的。
这种被动接收的方式获取对象的思想就是控制反转。它是spring框架的核心之一。
控制反转:把创建对象的权力交给框架。包括依赖注入和依赖查找。
**IOC的作用:**消减计算机程序的耦合。
使用spring的IOC解决程序耦合
准备
引入依赖
1、创建一个maven工厂,不使用骨架
2、修改pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
创建业务层接口和实现类
package com.oceanstar.service;
/*
* 操作账户:业务层接口
* */
public interface IAccountService {
/*
* 模拟保存账户
* */
void saveAccount();
}
/**
*
* 账户业务实现
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl(); 此处的依赖关系有待解决
public void saveAccount() {
accountDao.saveAccount();
}
}
创建持久层接口和实现类
/**
* 账户的持久层接口
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
public interface IAccountDao {
/**
* 保存账户
*/
void saveAccount();
}
/**
* 账户的持久层实现类
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存了账户");
} }
基于xml的配置
创建配置文件,并配置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">
<!--把对象的创建交给spring来管理
bean标签:用于配置让spring创建对象,并且存入ioc容器之中
id属性: 对象的唯一标识
class属性: 指定要创建对象的全限定类名
-->
<bean id="accountService" class="com.oceanstar.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.oceanstar.dao.impl.AccountDaoImpl"></bean>
</beans>
测试配置是否成功
/*
* 表现层到业务层
* */
public class Client {
/*
* 获取spring容器的Ioc核心容器,并根据id获取对象
* */
public static void main(String[] args) {
//1、获取spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService = (IAccountService) applicationContext.getBean("accountService");
System.out.println(iAccountService);
IAccountDao iAccountDao = (IAccountDao) applicationContext.getBean("accountDao");
System.out.println(iAccountDao);
}
}
运行结果:
Spring基于xml的IOC细节
spring中工厂的类结构图
、
BeanFactory和ApplicationContext的区别
beanFaactory是Spring容器种的顶层接口
ApplicationContext是他的子接口
BeanFactory和ApplicationContext的区别:创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么时候使用什么时候创建对象
ApplicationConext接口的实现类
- ClassPattXmlApplicationContext: 从类的根路径下加载配置文件
- FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,使用此类对象来创建spring容器。它用来读取注解
IOC中bean标签和管理对象细节
bean标签
作用:
- 用于配置对象让spring来创建的
- 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
- id:给对象在容器中提供一个唯一标识。用于获取对象
- class:指定类的全限定类名。用于反射创建对象。默认情况下创建无参构造函数
- scope:指定对象的作用范围:
- singleton:默认值,单例
- prototype:多例
- request:web项目中,Spring创建一个bean对象,将对象存入到request域中
- session:web项目中,Spring创建一个bean对象,将对象存入到session域中
- global session: web项目中,应用在portlet环境,如果没有portlet环境那么globalsession相当于session
- init-method:指定类中的初始化方法名称
- destory-method:指定类中销毁方法名称
实例化对象的三种方式
- (1) 使用默认无参构造函数
默认情况下,它会根据默认无参构造函数来创建类对象。如果bean种没有默认无参构造函数,将会创建失败
<bean id="accountService" class="com.oceanstar.service.impl.AccountServiceImpl"></bean>
- (2) spring管理静态工厂—使用静态工厂的方法创建对象
/*
* 模拟一个静态工厂,创建业务实例类
* */
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
使用StaticFactory类种的静态方法createAccountService创建对象,并且存入spring容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
<bean id="accountService"
class="com.oceanstar.factory.StaticFactory"
factory-method="createAccountService"></bean>
- (3) spirng管理实例工厂—使用实例工厂的方法创建对象
/*
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
* */
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
实现:先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
<bean id="instanceFactory" class="com.oceanstar.factory.InstanceFactory"></bean>
<bean id="accountService"
factory-bean="instanceFactory"
factory-method="createAccountService"></bean>
spring的依赖注入
依赖注入的概念
依赖注入:是spring框架核心IoC的具体实现
我们的程序在编写时,通过控制反转,把对象的创建交给了spring,但是代码种不可能出现没有依赖的情况。ioc解耦只是降低了他们的依赖关系,但是不会消除。例如,我们的业务层仍然会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。
简单的来说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取了。
构造函数注入
/**
*
* 账户业务实现
*/
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date bithday;
public AccountServiceImpl(String name, Integer age, Date bithday) {
this.name = name;
this.age = age;
this.bithday = bithday;
}
public void saveAccount() {
System.out.printf("name:" + name
+ " age:" + age
+ " bithday:" + bithday);
}
}
使用构造函数的方式,给service中的属性传值。
要求:
类中需要提供一个对应参数列表的构造函数。
涉及的标签:
constructor-arg
属性:
- index:指定参数在构造函数参数列表的索引位置
- type:指定参数在构造函数中的数据类型
- name:指定参数在构造函数中的名称 用这个找给谁赋值
=上面三个都是找给谁赋值,下面两个指的是赋什么值的======== - value:它能赋的值是基本数据类型和 String 类型
- ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
<bean id="accountService" class="com.oceanstar.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="oceanstar"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="bithday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
set方法注入
就是在类中提供需要注入成员的set方法。
/**
*
* 账户业务实现
*/
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date bithday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBithday(Date bithday) {
this.bithday = bithday;
}
public void saveAccount() {
System.out.printf("name:" + name
+ " age:" + age
+ " bithday:" + bithday);
}
}
通过配置文件给 bean 中的属性传值:使用 set 方法的方式
涉及的标签:
property
属性:
- name:找的是类中 set 方法后面的部分
- ref:给属性赋值是其他 bean 类型的
- value:给属性赋值是基本数据类型和 string 类型的
实际开发中,此种方式用的较多。
<bean id="accountService" class="com.oceanstar.service.impl.AccountServiceImpl">
<property name="name" value="oceanstar"></property>
<property name="age" value="18"></property>
<property name="bithday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
使用p名称空间注入数据
此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的
set 方法实现注入功能。
/**
*
* 账户业务实现
*/
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date bithday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBithday(Date bithday) {
this.bithday = bithday;
}
public void saveAccount() {
System.out.printf("name:" + name
+ " age:" + age
+ " bithday:" + bithday);
}
}
<?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="accountService"
class="com.oceanstar.service.impl.AccountServiceImpl"
p:name="oceanstar" p:age="18" p:bithday-ref="now">
</bean>
<bean id="now" class="java.util.Date"></bean>
</beans>
注入集合属性
就是给类中的集合成员传值,也是调用set注入,只不过变量的数据类型都是集合。
/**
*
* 账户业务实现
*/
public class AccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String , String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount() {
System.out.printf(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
注入集合数据
List 结构的:
array,list,set
Map 结构的
map,entry,props,prop
<?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">
<!--把对象的创建交给spring来管理
bean标签:用于配置让spring创建对象,并且存入ioc容器之中
id属性: 对象的唯一标识
class属性: 指定要创建对象的全限定类名
-->
<bean id="accountService"
class="com.oceanstar.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB"> <value>bbb</value></entry>
</map>
</property>
</bean>
</beans>