这篇文章建议大家在学习spring框架之前先看一下,作者也是在这条路上踩了好多坑。可以说,我是第二次回过头来学习spring框架。之前老师有讲过spring,但那只是教会你如何去用这个东西,那这个东西究竟底层是如何实现的,我觉得作为一个初始这个领域的人来说,非常有必要搞清楚他的底层运作原理。
我们选择用java来开发系统,一个必须要用到的工具就是数据库,毫无疑问,不管你用的是那种数据库,基本的实现思路都是一样的。一般都是注册驱动、创建连接、创建预定义SQL语句、封装结果集,处理返回的数据、关闭连接。大致就是这样的。
我们就拿注册驱动这个过程来说(其他的暂时不考虑):
Class.forName(“com.mysql.jdbc.Driver”);
DriverManager.registerDriver(new Driver());
以上这两个都可以去去注册一个数据库驱动类,为什么我们基本选择第一种方式来加载数据库驱动。原因就是DriverManager创建了两次Driver对象,而Class.foeName()他可以在运行时改变状态。所以推荐使用第一种方式来加载数据库驱动。当然,第一种方式也存在一些问题。代码耦合性的问题,我现在连接的是Mysql数据库,如果我需要Oracle数据库,那么,我又要去修改代码。所以,最好能够以配置文件的形式来修改是最好的。
好,说了这么多废话,引入今天的正题:
怎么削减代码之间的耦合性
我设计了一个简单的demo 来给大家说清楚这个问题:
1.目录结构如下:
这个目录结构就是我们正常遵守的MVC架构的模式。有数据访问层dao、业务逻辑层service、test在这里就代替contorller界面层(因为这个不涉及web),大家可能很好奇,还有一个bean层,这个我们暂时不说,后面讲这个bean层。
我们在一般的开发过程中,都是界面层去调用service层,service层调用dao层,dao在进行对数据库访问。那在这个过程当中我们的界面层需要把Service作为自己的一个属性,同理,在Service层也需要把dao层作为自己的一个属性,就是通过这种关系来实现上层对下层的调用,下层为上层提供服务。即如下:
contorller层把Service层作为自己的一个属性:
Service层把dao层作为自己的属性:
其实主要就两行代码:
private static EntityService entityService = new EntityServiceImpl(); private EntityDao entityDao = new EntityDaoImpl();
我们可以看出,这每个类之间的关联性是很强的。这样,不满足我们软件设计的目标,高内聚,低耦合。这个大家应该都知道。那问题来了,怎么解决这个问题呢?答案当然是反射。其实,光是通过反射是不够的。反射我们都知道他的作用,就是在程序运行期间来动态的编译和加载以及创建一个类。所以,这里我们要用到java当中的一个工厂设计模式(这里对工厂设计模式不作详细解释,可以参考我上一篇文章),工厂就是用来生产对象的。所以,在前面提到bean这个包面有BeanFactory这个类就是用来生产bean对象的。
接下来我们就解析一下通过工厂来创建我们所需要的对象:
首先,我们要准备一个配置文件(.properties)
EntityDao=com.tff.demo.jdbc.dao.impl.EntityDaoImpl
EntityService=com.tff.demo.jdbc.service.impl.EntityServiceImpl
保存为bean.properties。申明一下,文件名随便取,不是中文就ok,扩展名必须是properties这种格式的。我们分析一下这个文件,这种文件就是以键值对形式来获取的,等号左边就是键,右边就是值。这样写的目的就是我们可以通过键来获取相对应的值。我们在看一下BeanFactory这个类:
import java.util.Properties;
public class BeanFactory {
//定义一个properties对象
private static Properties pros;
//使用静态代码块为properties赋值
static {
try {
pros = new Properties();
//获取properties刘对象的
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
pros.load(in);
}catch (Exception e){
e.printStackTrace();
}
}
//根据bean获取bean对象
/*
* tff
* @param beanName 类的全路径名
* @return 返回一个bean对象
* */
public static Object getBean(String beanName){
Object bean = null;
try{
String beanPath = pros.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
简单解释一下:静态代码块中的代码主要完成了对我们bean.properties文件的加载流。保存在变量pros里面;getBean这个方法就是根据我们我们传入的类名来创建这个对象(其实这个类名就是我们bean.properties文件中的键)。
然后我们看一下测试类中的代码:
public class TestMethod {
private static EntityService entityService = null;
public static void main(String[] args) {
//不是单利模式
for (int i =0;i<10;i++){
entityService = (EntityService) BeanFactory.getBean("EntityService");
System.out.println(entityService);
}
}
}
主要说明一下:这次我们创建对象不是之前那样:
EntityService entityService = new EntityServiceImpl();
而是:
entityService = (EntityService) BeanFactory.getBean(“EntityService”);
我们需要创建什么类就可以通过传入的参数类决定。
好了,我们运行一下:结果如下:
很明显,测试代码中,我让他重负了10次,所以他创建了10个对象,但是这10个对象的内存地址都是不一样的,这说明他不是单例对象(单例设计模式也可参考我之前的文章)。
那单例对象和多例对象有什么区别吗?当然有:单例就是程序运行期间这个对象只创建一次,是共享的。不是属于某一个人的。而多例恰好反之,他是不共享内存空间的。所以,对于工厂这种模式来说,就是创建对象的,我们没有必要每个人创建自己的对象,只需要创建一个对象,这个对象共享,我们大家都可以用这个对象来调用方法。这样,就节省了大量的不必要的空间。
那我们来看一下如何改进,我们一起来看一下:
package com.tff.demo.jdbc.bean;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BeanFactory {
//定义一个properties对象
private static Properties pros;
//定义容器来保存创建的对象
private static Map<String,Object> map;
//使用静态代码块为properties赋值
static {
try {
pros = new Properties();
//初始化容器
map = new HashMap<String, Object>();
//获取properties刘对象的
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
pros.load(in);
//取出配置文件中所有key
Enumeration keys = pros.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value值
String beanPath = pros.getProperty(key);
Object value = Class.forName(beanPath).newInstance();
map.put(key,value);
}
}catch (Exception e){
e.printStackTrace();
}
}
public static Object getBean(String beanName){
return map.get(beanName);
}
简单分析一下:就是我们创建了一个map容器,来保存我们创建的对象。map的key就是我们创建对象调用getBean()方法时传入的参数,value就是我们创建的对象那。
我们再看一下运行结果:
这下我们看到所有的地址都是一样的。
详细代码请见码云:
https://gitee.com/tuofafa/spring01