通过编写JDBC的工程代码来分析程序的耦合
首先创建Maven工程,加入mysql的依赖,编写JDBC程序.
①创建一个maven工程
②创建一个maven工程
③在Pom.xml添加mysql的依赖
④创建一张简单的表
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
⑤编写JDBC程序
public class JdbcDemo1 {
public static void main(String[] args) throws SQLException {
//注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//获取连接
Connection Connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","3264421");
//获取操作数据库的预处理对象
PreparedStatement pstm = Connection.prepareStatement("select * from account");
//执行sql语句 得到结果集
ResultSet rs = pstm.executeQuery();
//遍历,封装结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//释放资源
rs.close();
pstm.close();
Connection.close();
}
编译期的依赖
如果我们现在在pom.xml中注释掉了mysql的依赖,那么在编译期间,程序就会报错.
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<!-- <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>-->
</dependencies>
image.png
//注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
↑ 如果我们用以上方式来注册驱动,那么在编译期间就不会报错了.它只是一段字符串,我们不再依赖于某个驱动类.但是不要妄想它能运行,因为类还是不存在的,如果运行,会抛出异常.如果再把注释掉的代码再放开,就能正常运行了.
但以上方式还是会有缺点,这个字符串已经写死了,如果要换数据库,这个驱动还是需要更改的.如果想要解决这个问题,可以通过配置文件
程序的耦合和解耦思路分析
程序的耦合
耦合:程序间的依赖关系
包括:
- 类之间的依赖
- 方法间的依赖
解耦:降低程序类的依赖关系
实际开发中:
应该做到:编译期不依赖,运行时才依赖
解耦思路:
- 第一步:使用反射来创建对象 而避免使用 new 关键字
- 通过读取配置文件读取来获取创建对象的全限定名
从① 编写JDBC的工程代码来分析程序的耦合性里我们知道,在实际开发中如果要降低程序的耦合性,一个是 使用反射来创建对象,而避免使用new关键字,第二个是通过读取配置文件读取来获得类的全限定类名.
/**
①账户业务层接口
**/
public interface IAccountService{
void saveAccount();
}
/**
②账户持久层接口
**/
public interface IAccountDao{
void saveAccount();
}
/**
③账户持久层
**/
public class AccountDaoImpl implements IAccountDao(){
public void saveAccount() {
System.out.println("保存了");
}
}
/**
④账户业务层
**/
public class AccountServiceImpl implements IAccountService(){
//模拟保存账户
IAccountDao dao = new AccountDaoImpl();
dao.saveAccount();
}
//模拟一个表现层
public class Client {
public static void main(String[] args) {
IAccountService as = new AccountServiceImpl();
as.saveAccount();
}
}
所以我们可以看到,在这些地方,有很强的耦合性.
image.png
image.png
image.png
如果类出现改动,变会在编译期间就有了异常.
怎样降低这个依赖关系?
使用工厂模式
Bean:在计算机语言中,有可重用组件的含义
JavaBean:用Java语言编写的可重用组件
JavaBean > 实体类
(Service 和 Dao 可以被重复使用)
它就是为我们创建Service 和 dao的
怎么创建工厂模式?
- 需要一个配置文件来配置 Service 和 Dao
怎么配置: 唯一标识 = 全限定类名(key = value) - 通过读取配置文件中配置的内容,反射创建对象.
创建配置文件 可以是 xml 格式 也可以是properties 格式
properties是比较简单的方式,仅限这个地方.
① 先创建一个bean.properties,放在resource目录下面.(如果是Maven工程的话)
bean.properties
accountService = com.itheima.service.impl
accountDao = com.itheima.dao.impl
②创建一个BeanFactory来读取Bean.properties
public class BeanFactory{
//定义一个properties对象
priavte static Properties props;
//使用静态代码块为properties赋值(?)
static{
try{
//实例化对象 耦合只能被降低而不能被消除 所以这里还是用了new来实例化对象
props = new Properties();
//获取Properties流对象
/**老师说这里不要 写成 InputStream in = new FileInputStream()
因为不知道这里写什么路径,如果写src的话,WEB工程一旦部署,相对路径src就没有了,如果
写绝对路径,也不能保证绝对路径(D盘C盘)都存在
应该写成:
InputStream in = beanFactory.class.getClassLoader().getResourceAsStream("bean.properties")
因为创建在Resrouce文件夹下的文件,会成为类根路径下的文件
**/
InputStream in = BeanFactory.class.getResourceAsStream("bean.properties");
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
//写一个方法,getBean
//思路:第一个方法类型为Object 因为写死一个具体的返回值,这个方法将不够复用;第二个,getBean的时候,要get哪个bean呢,所以需要一个beanName的参数,根据BeanName的名称,获取Bean对象)
public Object getBean(String BeanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
//在我的环境里,newInstance已经弃用了,JAVA9后不推荐使用
bean = Class.forName(beanPath).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
[关于ResourceAsStream的用法] (https://blog.csdn.net/u012557538/article/details/50317663)
然后怎么解耦呢?
我们现在面临的关系 就是程序间耦合的关系 一个是Service 依赖 Dao,一个是UI依赖Service
所以,在④中,把用new创建对象 改成 用工厂模式的getBean方法得到对象,就能完成解耦.
/**
④账户业务层
**/
public class AccountServiceImpl implements IAccountService {
/**
* 模拟保存账户
*/
//private IAccountDao dao = new AccountDaoImpl();
private IAccountDao dao = (IAccountDao) BeanFactory.getBean("accountDao");
public void saveAccount() {
dao.saveAccount();
}
}
分析工厂模式里的问题并改造
在以上的例子中,仍存在问题.
我们把Client 改一下,写了个for循环再打印
/**
* 模拟一个表现层用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//IAccountService as = new AccountServiceImpl();
// as.saveAccount();
for (int i = 0; i <5 ; i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
}
}
}
得到的结果是5个不同的对象,此时的对象是多例的
com.itheima.service.impl.AccountServiceImpl@12edcd21
com.itheima.service.impl.AccountServiceImpl@34c45dca
com.itheima.service.impl.AccountServiceImpl@52cc8049
com.itheima.service.impl.AccountServiceImpl@5b6f7412
com.itheima.service.impl.AccountServiceImpl@27973e9b
那么什么是单例对象呢?
单例对象从始至终都只有一个对象实例,也就是说打印五次,也只有一个实例.(Servlet 就是单例对象)
多例对象和单例对象有什么区别呢?
当我们在Service里 定义一个类成员 private int i = 1,然后打印,同时让 i++
这个时候 i 是一个类成员 在方法里进行++ 也就是说 我们的方法可以操作类成员属性并且改变它的取值
public class AccountServiceImpl implements IAccountService {
/**
* 模拟保存账户
*/
//private IAccountDao dao = new AccountDaoImpl();
private IAccountDao dao = (IAccountDao) BeanFactory.getBean("accountDao");
private int i = 1;
public void saveAccount() {
dao.saveAccount();
System.out.println(i);
i++;
}
}
这个时候再把 Client 里的as.saveAccount放出来
/**
* 模拟一个表现层用于调用业务层
*/
public class Client {
public static void main(String[] args) {
//IAccountService as = new AccountServiceImpl();
for (int i = 0; i <5 ; i++) {
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
}
得到的结果是
再执行过程中,因为每个对象由于都有独立实例,从而保证了类对象在创建时重新初始化类中的属性,所以类中i一直都是1,不管有多少个service对象来访问,由于对象都是新创建的,所以 i 的值不会有变化.
com.itheima.service.impl.AccountServiceImpl@12edcd21
保存了
1
com.itheima.service.impl.AccountServiceImpl@34c45dca
保存了
1
com.itheima.service.impl.AccountServiceImpl@52cc8049
保存了
1
com.itheima.service.impl.AccountServiceImpl@5b6f7412
保存了
1
com.itheima.service.impl.AccountServiceImpl@27973e9b
保存了
1
单例对象的特点
与多例相比,单例对象应该是这样的,这个对象只被创建了一次,从而类中的成员 也只会初始化一次
单例的
所以说 在多个线程访问或者多个对象访问时 由于对象实例只有一个 那这个时候操作类成员的时候 类成员在方法能够进行改变的时候 单例对象有线程问题
多例对象没有这个问题 但多例对象由于被创建多次 执行效率没有单例对象高
在Service 和Dao 的代码中 不存在类属性被方法调用 并且方法能调整的成员变量 所以我们的代码中不存在线程问题的 因为没有在单位对象中可以改变的类成员
(复习:方法每次都能初始化)
所以这样看的话,在这些代码中,由于不存在线程问题,所以不需要多例对象.
但是 由于BeanFactory在创建对象的时候 使用了newInstance,这句话的出场表明了每次都会调用默认构造函数创建对象 所以每次都是新的对象.
经过以上分析 我们并不需要多例模式 我们需要单例模式,因此我们需要对方法进行调整,调整的前提是我们的对象只能newInstance一次
如果创建对象完了不存起来,由于Java的垃圾回收机制,在长时间不用的时候回收,下次再用的时候就没有了.
我们现在只能用一次newInstance ,对象创建出来后应该马上存起来
存到哪儿去呢?我们怎么存呢?
能存这些对象的,我们称之为容器.
BeanFactory.java 改造如下
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.getResourceAsStream("bean.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名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
System.out.println("beanName:"+beanName);
return beans.get(beanName);
}
这里由多例转为单例运行时报了空指针异常,因为Dao 为null
image.png
在评论上看到有人说
image.png
(未解决)
网友评论