什么是耦合
首先创建一个maven项目
在配置文件中添加jdbc的依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
创建一个jdbcDemo_1类,使用Driver对象创建数据库连接,并遍历“User”表内的数据
package com.tianqicode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class jdbcDemo_1 {
public static void main(String[] args) throws Exception{
//1、连接驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2、获取连接
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/javaStudy", "root", "admin");
//3、获取操作数据库的预处理对象
PreparedStatement ps = con.prepareStatement("select * from user");
//4、执行SQL,得到结果集
ResultSet rs = ps.executeQuery();
//5、遍历结果集
while (rs.next()) {
System.out.println(rs.getString("user"));
}
//6、释放资源
rs.close();
ps.close();
con.close();
}
}
此时可以正常运行,遍历出相应表内的数据。
此时我们将配置文件中的jdbc依赖注释掉发现
控制台报了编译错误
其实这就是程序之间的耦合,当其中一个对象的类不存在后,程序将无法通过编译。
但是我们知道,其实我们在连接驱动时,并不是使用
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
的方式。
而是使用
Class.forName("com.mysql.jdbc.Driver");
这种方式获取连接。
在这种方式下,即使我们去掉了配置文件中的jdbc依赖,依然可以通过编译,在运行时抛出异常。
因为在这个类中,它获取连接时,仅仅是依赖了一行字符串。
这就是解耦合。
我们要做到的是:编译时不依赖,运行时才依赖。
但是此时又发现了另一个问题:那就是字符串在类中被写死,如果我们需要修改为其他的数据库,我们必须要修改每一处获取数据库连接的语句。
那么我们应该怎么做呢?
如何解耦合
第一步:通过读取配置文件来获取要创建的对象全限定类名。
第二步:使用反射来创建对象,而避免使用new关键字创建对象实例。
这时候引入,利用工厂类解耦的概念。
这时候准备一个项目,以便于更好的理解如何利用工厂类来进行解耦。
首先还是创建一个maven项目
创建一个UserService接口,来模拟业务层接口
package com.tianqicode.service;
public interface UserService {
void saveUser();
}
同时创建它的实现类,UserServiceImpl,来模拟业务层
package com.tianqicode.service.impl;
import com.tianqicode.dao.UserDao;
import com.tianqicode.dao.impl.UserDaoImpl;
import com.tianqicode.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public void saveUser() {
userDao.saveUser();
}
}
这时候在业务层调用了持久层的对象,所以创建一个UserDao接口,来模拟持久层接口
package com.tianqicode.dao;
public interface UserDao {
void saveUser();
}
同时,添加持久层的实现类UserDaoImpl
package com.tianqicode.dao.impl;
import com.tianqicode.dao.UserDao;
public class UserDaoImpl implements UserDao {
public void saveUser() {
System.out.println("保存用户成功!");
}
}
最后再创建一个Client类,来模拟表现层,用来调用业务层
package com.tianqicode.ui;
import com.tianqicode.service.UserService;
import com.tianqicode.service.impl.UserServiceImpl;
public class Client {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.saveUser();
}
}
这时候,一个小的应用就创建完成了。
但是,这个应用依然有高耦合的毛病,比如此时我们将Dao的实现类从项目中删除,点击运行依然会出现像上一个Demo运行时的编译时错误。
这时候,试着使用工厂类进行解耦合。
首先根据之前的思想,准备一个配置文件bean.properties
userService=com.tianqicode.service.impl.UserServiceImpl
userDao=com.tianqicode.dao.impl.UserDaoImpl
然后创建一个BeanFactory类
package com.tianqicode.factory;
import java.io.InputStream;
import java.util.Properties;
/**
* 一个创建Bean的工厂
*
* Bean在计算机语言中有可重用组件的意思
*
* JavaBean是使用Java语言编写的可重用组件
*
* 在这里,它就是创建我们的Service和Dao对象的
*
* 第一个,需要一个配置文件来配置我们的service和dao
* 配置的内容:全限定类名。
* 第二个,通过读取配置文件中的配置内容,反射创建对象
*
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties properties;
//使用静态代码块,为properties对象赋值
static {
try {
//实例化对象
properties = new Properties();
//获取properties文件流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
} catch (Exception e) {
throw new ExceptionInInitializerError("配置文件加载失败!");
}
}
public static Object getBean(String beanName) {
Object bean = null;
try {
String beanPath = properties.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
对外部提供一个getBean(String beanName)的方法。
这时候就可以改变获取对象的方式,通过工厂类获取对象。
首先改变业务层获取持久层的方式。
private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
其次改变表现层获取业务层对象的方式。
UserService userService = (UserService) BeanFactory.getBean("userService");
点击运行,输出正常。
再将持久层实现类删除,点击运行,编译通过,运行抛出异常。
这时候也就实现了使用工厂类解耦。业务层不再直接访问持久层,而是间接通过工厂类,获取相应的对象。
容器的概念
但这时候依然存在问题,这时候每次调用一次持久层或者是业务层,工厂类都将创建一个新的实例。显然这不是我们想要的,因为一旦项目过大,需要多次使用组件时,会创建更多的对象实例,这时候显然效率会降低。
所以我们依然要对BeanFactory进行改造。
package com.tianqicode.factory;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 一个创建Bean的工厂
* <p>
* Bean在计算机语言中有可重用组件的意思
* <p>
* JavaBean是使用Java语言编写的可重用组件
* <p>
* 在这里,它就是创建我们的Service和Dao对象的
* <p>
* 第一个,需要一个配置文件来配置我们的service和dao
* 配置的内容:全限定类名。
* 第二个,通过读取配置文件中的配置内容,反射创建对象
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties properties;
//定义一个Map用来存放组件对象。我们把它称之为容器
private static Map<String, Object> beans;
//使用静态代码块,为properties对象赋值
static {
try {
//实例化对象
properties = new Properties();
//获取properties文件流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中的所有keys
Enumeration keys = properties.keys();
//遍历枚举
while (keys.hasMoreElements()) {
//取出每个key
String key = keys.nextElement().toString();
//通过取出的key,获取beanPath
String beanPath = properties.getProperty(key);
//通过beanPath,反射获取对象并存入容器
Object bean = Class.forName(beanPath).newInstance();
beans.put(key, bean);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("配置文件加载失败!");
}
}
public static Object getBean(String beanName) {
Object bean = beans.get(beanName);
// try {
// String beanPath = properties.getProperty(beanName);
// bean = Class.forName(beanPath).newInstance();
// } catch (Exception e) {
// e.printStackTrace();
// }
return bean;
}
}
这时候,再通过工厂类获取到的对象就是单实例的了。
至此这个小项目也就结束了。
IOC的概念
通过这个项目,也能更好的理解IOC的概念
改造之前我们获取对象的方式是通过new关键字创建对象实例,这时候我们的业务层直接访问持久层。
而使用工厂类后,各个层之间不再直接接触,而是通过工厂类间接获取对象,而不再需要自己动手实例化对象。将对象的生命周期交给工厂类管理。这时候我们可以更加专注于业务逻辑而不需要再去创建各个类的实例对象。
而IOC的的最重要的作用,也就是削减计算机程序中的耦合。