1.耦合概念以及解耦
耦合: 简单来说就是指程序间的依赖关系。一般包括:类之间的关系,方法间的关系。
解耦: 目的是为了降低程序间的依赖关系。实际开发中应该做到:编译时不依赖,运行时才依赖。
1.1.两个例子:JDBC连接以及持久层业务层调用关系
创建一个demo类,在不导入mysql-connector-java.jar的情况下,执行程序。
(在导入jar包后,都可正常运行)
尝试通过Class.forName(“驱动类全类名”)(反射机制)获取驱动类:此时即使驱动类不存在,也不会在编译的时候报错
持久层和业务层的调用关系:此时若不存在持久层类,将无法通过编译,这就是编译期依赖。
1.2.解耦的思路:通过工厂模式解耦(手动实现)
用工厂模式,通过反射机制,创建对象
第一步: 创建一个配置文件来配置我们的service和dao。内容为:唯一标识 = 全限定类名(key - value)
第二步: 通过读取配置文件中配置的内容,通过反射创建对象。
配置文件:
手写的BeanFactory工厂类(实现单例模式):
PS:单例模式与多例模式:
单例模式: 多次创建(new)同一对象时,为相同的对象,拥有相同的地址。
多例模式: 多次创建(new)同一对象时,为不同的对象,拥有的地址不同。
本例实现单例模式的思路: 在类中新建一个Map用于存储<String,Object>,在第一次运行的时候,将配置文件中的多个对象一次性创建并且加到Map中,之后在调用getBean方法的时候通过传入的类名从Map中取出一开始就创建好的对象即可。
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String, Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");//读取配置文件
props.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中所有的key 一次性创建完成
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* 且从Map中获取对象,实现了单例模式
* */
public static Object getBean(String beanName) {
return beans.get(beanName);//通过类名找到对应的对象地址
}
}
业务层使用getBean()方法从工厂中获取对应类名的对象。
工厂模式优点:
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。
工厂模式缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
2.使用SpringIOC解决耦合(将对象的创建交给spring)
使用spring的方法获取类,不再需要自己写工厂。
2.1.基本实现
1.创建一个maven工程。pom.xml导入spring相关jar包
2.创建xml配置文件
导入spring约束,配置唯一标识id与相应类的对应关系。使得可以根据id获得对应的对象。
3.创建测试类:从上到下代表了持久层,业务层,表现层的调用关系。
包和类的结构如下:
持久层:
/**
* 账户的持久层实现类
**/
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存了账户");
}
}
/**
* 持久层接口
**/
public interface IAccountDao {
/*
* 模拟保存账户
* */
void saveAccount();
}
业务层:
/**
* 业务层实现类
**/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public void saveAccount() {
accountDao.saveAccount();
}
}
/**
* 业务层的接口
**/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
}
表现层:
/**
* 表现层调用业务层,业务层调用持久层
* 模拟一个表现层,用于调用业务层
**/
public class Client {
/**
* 获取spring的Ioc核心容器,并根据id获取对象
* **/
public static void main(String[] args) {
//1,获取核心容器对象。通过容器创建对象.通过核心容器的getBean()方法获取具体对象.
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");//根据唯一标识获得对象
System.out.println(as);//尝试输出对象,默认情况为单例模式
}
}
2.1.1.ApplicationContext的三个实现类
ClassPathXmlApplicationContext: 它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了。
FileSystemXmlApplicationContest: 它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContest: 读取注解创建容器。
2.1.2.核心容器BeanFactory和ApplicationContext有什么区别
ApplicationContext: 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建文件中配置的对象。(单例对象适用)
BeanFactory: 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。(多例对象适用)
PS:没有特殊要求的情况下,应该使用ApplicationContext完成。因为BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近现在开发的功能。
3.XML文件的配置
使用配置文件实现IOC,要将托管给spring的类写进bean.xml配置文件中。
3.1.bean标签
作用: 配置托管给spring的对象,默认情况下调用类的无参构造函数,若果没有无参构造函数则不能创建成功。
属性:
1.id: 指定对象在容器中的标识,将其作为参数传入getBean()方法可以获取获取对应对象.
2.class: 指定类的全类名,默认情况下调用无参构造函数。
3.scope: 指定对象的作用范围,可选值如下:
3.1.singleton:单例模式。(默认)
3.2.prototype: 多例模式。
3.3.request: 将对象存入到web项目的request域中。
3.4.session: 将对象存入到web项目的session域中。
3.5.global session: 作用于集群环境的会话范围(全局会话范围),当不是集群环境时,他就是session
4.init-method: 指定类中的初始化方法名称,在对象创建成功之后执行
5.destroy-method: 指定类中销毁方法名称,对prototype多例对象没有作用,因为多利对象的销毁时机不受容器控制。
3.2.bean的作用范围和生命周期
1.单例对象: scope="singleton"
作用范围: 每个应用只有一个该对象的实例,它的作用范围就是整个应用。
生命周期: 单例对象的创建与销毁 和 容器的创建与销毁时机一致。
-------------出生:当容器创建时,对象出生。
-------------活着:只要容器存在,对象一直活着。
-------------死亡:容器销毁,对象消亡
2.多例对象: scope="prototype"
作用范围: 每次访问对象时,都会重新创建对象实例。
生命周期: 多例对象的创建与销毁时机不受容器控制。
-------------出生:当我们使用对象时,spring框架为我们创建。
-------------活着:对象只要是在使用过程中就一直活着。
-------------死亡:当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收。
3.3.实例化bean的三种方式
1.构造器实例化: 用默认构造函数创建。
在spring的配合文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数的bean对象,此时如果类没有默认构造函数,则对象无法创建。(类没有被重载的情况)
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>//xml文件
//id为标识符,class为要通过getBean()创建的类
2.静态工厂实例化: 使用静态工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)(静态方法可以直接用类获取。)
// 标识符 指定类 直接指定类中的静态方法
<bean id="accountService" class="factory.StaticFactory" factory-method="getAccountService"></bean>
PS:类的构造函数也是静态方法,因此默认无参构造函数也可以看作一种静态工厂方法
3.实例工厂实例化: 使用普通工厂中的方法创建对象(使用某个类中的非静态方法创建对象,并存入spring容器)。
/**
* 模拟一个实例工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
**/
public class InstanceFactory {
public IAccountService getAccountService(){//不是静态方法
return new AccountServiceImpl();
}
}
目的:先创建实例工厂instanceFactory
,然后调用getAccountService()
方法创建对象。
//标识符 指定类
<bean id="instanceFactory" class="factory.InstanceFactory"></bean>
//标识符 指定实例工厂的id 指定实例工厂中生产对象的方法
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
4.Spring的依赖注入(DI)
4.1.依赖注入(Dependency Injection)
在通过Spring创建对象的时候,因为使用的时反射机制而不是使用new关键字,因此我们需要在配置文件指定创建出对象各字段的取值。
能注入的数据有三类:
第一种:基本数据类型和String。
第二种:其他bean类型(在配置文件中或者注解配置过的bean)
第三种:复杂/集合类型
注入的方式有三种:
第一种:使用构造函数提供。
第二种:使用set方法提供(最常用)
第三种:使用注解提供(笔记二整理)
4.2.使用构造函数进行注入
首先是一个类:目的是在类创建的时候,指定字段name,age和birthday的值
public class AccountServiceImpl{
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name,Integer age,Date birthday){//重写的构造函数,必须赋值才可创建成功
this.name = name;
this.age = age;
this.birthday = birthday;
}
}
使用构造器注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:
用于指定要注入数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:
用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:
用于指定给构造函数中指定名称的参数赋值 (常用!这里用的也是name)
上面三个用来指定给构造函数中的哪个参数赋值
value:
用于提供基本类型和String类型的数据
ref:
用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:在获取bean对象时,必须注入操作,否则对象无法创建成功。
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
<bean id="accountService" class="service.impl.AccountServiceImpl">
// name设置对应字段,下面同理 value为设置的初始值 ref为引用的是bean标签(为了调用Date类生成日期)
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--配置一个日期对象,可以使用ref引用关联的bean对象-->
<bean id="now" class="java.util.Date"></bean>
4.3.使用set方法注入
首先是一个类:要生成对应的set方法
public class AccountServiceImpl2{
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public void setName(String name){this.name = name;}
public void setAge(Integer age){this.age = age;}
public void setBirthday(Date birthday){this.birthday = birthday;}
}
使用set方法注入:
使用的标签:property
出现的位置:bean标签的内部
标签的属性:
name:
用于指定主时所调用的set方法名称。
value:
用于提供基本类型和String类型的数据。
ref:
用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象。
优势:创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:如果有某个成员必须有值,则获取对象时,有可能set方法没有执行。
<bean id="accountService2" class="service.impl.AccountServiceImpl2">
<property name="age" value="21"></property>//相当方便
<property name="name" value="TEST"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
4.4.复杂类型的注入/集合类型的注入
准备一个演示类:
public class AccountServiceImpl3{
private String[] myStrs;//数组类型
private List<String> myList;//集合类型
private Set<String> mySet;//set类型
private Map<String,String> myMap;//hash表类型
private Properties myPros;//也是类似hash表类型
//各自生成set方法
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 setMyPros(Properties myPros) { this.myPros = myPros; }
}
各自的标签以及方法:
<bean id="accountService3" class="service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</array>
</property>
<property name="myList">
<list>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</list>
</property>
<property name="mySet">
<set>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myPros">
<props>
<prop key="testC"></prop>
<prop key="testD"></prop>
</props>
</property>
</bean>
注意:总体来说分为两大结构,List和Map,相同结构的标签内容可以替换使用。
用于给List结构集合注入的标签:list,array,set
用于给Map结构集合注入的标签:map,props
4.5.使用注解进行注入
待续。