微信搜索我吃你家米了关注公众号
耦合
程序的耦合
什么是耦合
即程序间的依赖关系
- 类之间的依赖关系
- 方法之间的依赖关系
import java.sql.*;
public class JdbcDemo1 {
public static void main(String[] args) throws Exception {
//注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//获取链接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy", "root", "1234");
//获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//执行SQL,得到结果集
ResultSet rs = pstm.executeQuery();
//遍历结果集
while(rs.next()) {
System.out.println(rs.getString("name"));
}
//释放资源
conn.close();
pstm.close();
rs.close();
}
}
一旦我们失去了com.mysql.jdbc
包,程序就会出现编译错误
当我们把驱动注册的代码改成如下形式时,就可以编译成功了
原来的:DriverManager.registerDriver(new com.mysql.jdbc.Driver());
改动后的:Class.forName("com.mysql.jdbc.Driver");
可以就看出来,当我们使用反射的方式来注册驱动时,就可以达到解耦的目的,因为在使用反射创建对象时,我们传递的参数是一个字符串,并没有真正用到com.mysql.jdbc.Driver
类,所以虽然运行失败,但可以编译成功
不过此时也是有弊端的,就是com.mysql.jdbc.Driver
类被硬编码到了程序中,如果我们需要使用其他数据库的驱动程序,就会遇到问题
解耦的思路:
- 使用反射来创建对象,避免使用new关键字
- 通过读取配置文件来获取要创建的对象的全限定类名
我们自己的一个小案例
~
这个案例就体现出了一些亟待解决的耦合问题
我们使用工厂模式对上面的代码进行了一些改进
使用工厂模式改进后的代码
将类的创建工作交给了BeanFactory
类来进行处理
将properties
文件的都区工作放在了BeanFactory
的静态代码块中,这样配置文件的读取工作就只会执行一次,然后我们定义了getBean
方法,结合java的反射来根据传入的类名创建出相应的对象
但是在刚才的工厂模式中还存在需要讨论的问题
我们在ui
包的Client
的main
方法中编写如下代码:
for (int i = 0; i < 5; i++) {
IAccountService as = (IAccountService)BeanFactory.getBean("accountService");
System.out.println(as);
}
输出结果如下:
com.itheima.service.impl.AccountServiceImpl@677327b6
com.itheima.service.impl.AccountServiceImpl@14ae5a5
com.itheima.service.impl.AccountServiceImpl@7f31245a
com.itheima.service.impl.AccountServiceImpl@6d6f6e28
com.itheima.service.impl.AccountServiceImpl@135fbaa4
可以看到,此时是多例模式,每次调用getBean
方法都会生成一个全新的AccountServiceImpl
对象
在我们的例子中,是不存在多线程访问造成的安全问题的,因为我们的持久层和业务层中都不存在更改自己成员变量值的方法,也就意味着不存在需要保护的共享资源
因此我们需要对代码进行更改,使其使用单例模式,这样会使程序的效率大大提高
我们对BeanFactory
对象做了如下改动
package com.itheima.factory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/*
一个创建Bean对象的工厂
Bean在计算机英语中的意思是***可重用组件***\
JavaBean
javabean所代表的的范围远大于实体类
所谓JavaBean就是使用java编写的可重用组件
在我们的例子中,就是创建我们的service和dao对象
降低程序耦合
创建配置文件
全限定类名(就是我们要创建的类的标识符)
在配置文件中就是一个key-->value形式
读取配置文件反射创建bean对象
配置文件的形式有两种选择
xml文件
properties文件
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象,我们称之为***容器***
private static Map<String, Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取Properties文件的流对象
//为了保证程序的可移植性,我们使用getClassLoader来获取路径
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
/***********************************************************************/
beans = new HashMap<String, Object>();
//取出配置文件中所有的key
Enumeration<Object> 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();
beans.put(key, value);
}
/***********************************************************************/
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化propreties文件失败");
}
}
//根据bean的名称获取bean对象
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
IOC(inversion of control) 控制反转
先看下面两种对象的创建方式:
private IAccountDao accountDao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
对于第一种对象创建方式:
在第一种方式中,我们的APP直接和资源产生了联系,体现在程序中就是在代码中使用了new
关键字来创建对象
对于第二种对象创建方式
我们的APP不直接和资源打交道,而是通过工厂来和资源产生联系
现在我们就可以明白,所谓控制反转,就是控制权的转移,由APP转到了工厂,其作用就是降低程序间的依赖关系(耦合)
上面的一系列例子就是为了引进Spring,上面那些创建工厂降低依赖的工作都可以交给Spring框架来做
通过配置xml文件,然后获取核心容器,使用该容器来获取我们在xml文件中配置的对象
/*
获取spring的IOC核心容器,并根据id获取对象
*/
public static void main(String[] args) {
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
//下面演示了两种获取的方式,一种需要强转,一种不需要
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
关于核心容器获取的两种方式
public class Client {
/*
获取spring的IOC核心容器,并根据id获取对象
ApplicationContext的三个常用实现类
- ClassPathXmlApplicationContext
加载类路径下的配置文件,配置文件必须在类路径下,否则加载不了
- FileSystemXmlApplicationContext
加载磁盘中任意位置的配置文件(我们的应用程序必须拥有对该文件的访问权限)
出于可移植性的考虑,我们一般不选用这种读取配置文件的方式
- AnnotationConfigApplicationContext
通过读取注解来创建对象
核心容器的两个接口引出的问题
ApplicationContext
该接口在创建核心容器时,一旦读取完配置文件,就会立即创建对象
采用的策略是***立即加载***
在下面的例子中,我们在AccountServiceImpl的构造函数中输出一句话,然后在
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
的后面创建断点,debug程序,可以发现,该语句执行完之后AccountServiceImpl对象已经创建出来,也就是说,读取完配置文件之后,对象就被创建了出来
BeanFactory
采用的策略是***延时加载***
在程序真正根据id去获取对象的时候才进行创建
***我们要讨论的问题是,我们什么时候应该使用哪个接口***
前者适合单例模式
后这适合多例模式
最后的结论
我们在实际开发工作中,使用最多的其实是前者
因为ApplicationContext的功能相较而言是更强大的,我们可以在配置文件中通过配置
来告知ApplicationContext使用单例模式还是使用多例模式
*/
public static void main(String[] args) {
/******ApplicationContext******/
/*
//获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
//下面演示了两种获取的方式,一种需要强转,一种不需要
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(as);
System.out.println(adao);
*/
/******BeanFactory******/
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService)factory.getBean("accountService");
System.out.println(as);
}
}
spring中bean的三种创建方式
创建bean的三种方式
- 使用默认构造函数创建bean对象
在配置文件中使用bean标签,当该标签中只有id和class属性时,那么即采用类的
默认构造函数创建对象,如果该类中没有默认构造函数,那么对象将无法创建
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
- 使用某个类中的方法创建对象,并存入spring容器
对于第一种创建方式,我们可能会遇到一些问题,比如我们引入了一个jar包,该jar包
中的类并没有默认构造函数,此时我们就无法使用第一种方式来获取对象了
先创建工厂对象,然后再使用该工厂的方法创建对象
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
***这个工厂类可由我们自己编写,然后再在其中定义一个创建jar包中的对象的方法***
- 使用某个类中的静态方法创建对象,并将其存入spring对象
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
***后两种方法就是用来解决jar包中类没有默认构造方法的问题的***
bean的作用范围
bean对象的作用范围
spring的bean对象默认情况下是单例的,也就是说,在不声明其他属性的情况下,
使用spring的容器获取的对象都是同一个对象
我们可以使用scope属性指定bean的作用范围
最常用的就是前两种
singleton
默认值
单例
prototype
多例
request
作用于web的请求范围
session
作用于web应用的会话范围
global-session
作用于集成环境的会话范围
前台负载均衡服务器将请求交给后台的服务器集群
由于每次请求不一定总是交给同一台后台服务器进行处理
引入了global-session,使得后面的所有服务器都能够识别同一用户的会话