什么是耦合
JAVA中的异常,主要分为编译时异常和运行时异常两大类,先看一段简单的JDBC代码
public class JdbcDemo{
public static void main(String[]args)throws Exception{
//1.注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2.获取连接
Connection conn = DriverManager.getconnection("jdbc:mysql://localhost:3306/jdbcDemo","root","yourPwd");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select*from account");
//4.执行SOL,得到结果集
ResultSet res = pstm.executeQuery();
//5.遍历结果集
while(res.next()){
System.out.println(res.getString("name"));
}
//6.释放资源
res.close();
pstm.close();
conn.close();
}
}
平常我们在注册驱动这里,更多用的是导包的形式,而不是New一个对象,假如我们这里没有mysql的jar包,点击运行,会报一个编译时异常,这样会使得程序的独立性非常之差,得依赖于另一个类的存在才能运行。
这就是程序的耦合,简单来讲,耦合可以理解为程序间的依赖,除了上面的类之间的依赖,还有方法间的依赖
- 类之间的依赖
- 方法间的依赖
而降低这种现象,则称为解耦,比如上面的注册驱动可以写成这样:
Class.forName("com.mysql.jdbc.Driver");
吧mysql这个jar包变成一个字符串,在运行时在forName这个函数去注册对应的类,这样即使没有该jar包,也不会报编译时异常,从而大大增加了程序的独立性
实际开发中我们要尽可能做到:编译时不依赖,运行时才依赖,因为程序里肯定有类之间的相互调用,所以我们避免不了耦合,消除不了耦合,只能做到降低
如何进行解耦
这里先讲一下类之间的解耦,方法的解耦后续在写一篇
解耦的思路:
使用映射来创建对象,而避免使用new关键字。
采用映射来创建对象,依赖的是字符串,而用new来创建,则需要依赖具体的驱动类,但这样又产生了新的问题:
当我们程序换驱动类时,必须一个个去修改字符串,修改的地方一多,就很繁琐了,这就引出了第二步
通过读取配置文件,来获取创建对象全限定类名
来通过一个小案例了解一下
在IDEA中创建一个maven工程,导入对应的maven依赖,然后创建对应的dao和service,同时包括servlet,像这样:
package com.karasawa.dao;
/**
* @program: SpringJieOu
* @description: 账户持久层实现类
* @author: karasawa
* @create: 2020-02-20 22f:17
**/
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存的账户");
}
}
package com.karasawa.service;
import com.karasawa.dao.AccountDaoImpl;
import com.karasawa.dao.IAccountDao;
/**
* @program: SpringJieOu
* @description: 账户业务层实现类
* @author: karasawa
* @create: 2020-02-20 22:14
**/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public void saveAccount(){
accountDao.saveAccount();
}
}
package com.karasawa.servlet;
import com.karasawa.service.AccountServiceImpl;
import javax.servlet.http.HttpServlet;
/**
* @program: SpringJieOu
* @description: 表现层测试调用业务层
* @author: karasawa
* @create: 2020-02-20 22:33
**/
public class TestSaveAccount extends HttpServlet {
public static void main(String[] args) {
AccountServiceImpl s = new AccountServiceImpl();
s.saveAccount();
}
}
很明显,这里表现层依赖着业务层,业务层又依赖着持久层,整个代码有着很强的依赖性,我们按照上面的步骤来解决一下
第一步:需要一个配置文件来配置我们的service和dao配置的内容:以key = value形式
在名为factory的包下创建一个类BeanFactory,像这样:
配置文件可以是XML和properties,这里就用propertise,因为解析起来相比XML简单一些,可以减少不必要的知识学习成本
在resource下面创建一个Bean.propertise文件,像这样:
accountService=com.karasawa.service.AccountServiceImpl
accountDao=com.karasawa.dao.AccountDaoImpl
第二步:通过读取配置文件中配置的内容,反射创建对象
先读取配置文件
package com.karasawa.factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @program: SpringJieOu
* @description: 一个创建Bean对象的工厂,Bean在计算机行业中有类似「重用」的意思
* @author: karasawa
* @create: 2020-02-20 22:43
**/
public class BeanFactory {
private static Properties prop;
static {
try {
prop = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.propertise");
assert in != null;
prop.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化propertise失败");
}
}
}
嗯?你不是刚说不要用new关键字嘛,怎么还用new Propertise?
上面都说了,我们只能尽量减少耦合,并不能消除,依赖关系定会存在
注意一下在获取InputStream的时候我并没有使用FileInputStream(),这是因为我们web应用在上传到服务器的时候,绝对路径和相对路径(相对当前的开发环境)将变得不靠谱,这点在前面的博客讲过了,所以我在这里使用了类加载器。在try/catch里面,如果捕捉到异常,则初始化失败,ExceptionInInitializerError是一个linkageError,这个错误一出场,后面程序就不会再运行了
我们来创建反射对象,接着上面的文件
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = prop.getProperty(beanName);
bean = Class.forName(beanPath)
.getDeclaredConstructor().newInstance();
} catch (
ClassNotFoundException
| NoSuchMethodException
| InstantiationException
| IllegalAccessException
| InvocationTargetException e) {
e.printStackTrace();
}
return bean;
}
因为对象类型的不确定性,所以这里返回类型用了一个Object,整个过程呢,就叫工厂模式来创建对象,OK,我们工厂写好了,返回上面的小案例,做一下修改:
package com.karasawa.service;
import com.karasawa.dao.IAccountDao;
import com.karasawa.factory.BeanFactory;
/**
* @program: SpringJieOu
* @description: 账户业务层实现类
* @author: karasawa
* @create: 2020-02-20 22:14
**/
public class AccountServiceImpl implements IAccountService {
// private IAccountDao accountDao = new AccountDaoImpl();
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
public void saveAccount() {
accountDao.saveAccount();
}
}
package com.karasawa.servlet;
import com.karasawa.factory.BeanFactory;
import com.karasawa.service.AccountServiceImpl;
import javax.servlet.http.HttpServlet;
/**
* @program: SpringJieOu
* @description: 表现层测试调用业务层
* @author: karasawa
* @create: 2020-02-20 22:33
**/
public class TestSaveAccount extends HttpServlet {
public static void main(String[] args) {
// AccountServiceImpl s = new AccountServiceImpl();
AccountServiceImpl s = (AccountServiceImpl) BeanFactory.getBean("accountService");
s.saveAccount();
}
}
运行后和之前的结果一样,是否能解耦呢?尝试看看
把IAccountService删掉(记得copy一份),然后运行servlet,可以看到运行起来了,当然,还是会报错,不过这会报的错是runtimeException了,也就是运行时的异常了,成功解耦!
可以了吗?其实代码还可以更好!!
我们知道,多例模式执行效率低,而单例模式有线程问题
回顾我们上面的案例,并没有线程问题,那我们案例用单例会比较好,执行效率高,修改一下servlet,循环多执行几次,再把对象输出,来看看三个是同一个对象还是不同对象
public class TestSaveAccount extends HttpServlet {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
AccountServiceImpl s = (AccountServiceImpl) BeanFactory.getBean("accountService");
System.out.println(s);
}
}
}
运行的时候查看控制台
看到三个首地址都是不一样的,确定了我们此时是多例模式
继续优化,思路是在BeanFactory初始化的时候,就把所有的类加载完成,放进一个Map里面,每次拿的时候再从map里返回实例就好了,修改代码如下:
public class BeanFactory {
private static Properties prop;
private static Map<String,Object> beans;
static {
try {
prop = new Properties();
beans = new HashMap<String,Object>();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.propertise");
assert in != null;
prop.load(in);
Enumeration keys = prop.keys();
while (keys.hasMoreElements()){
String key = keys.nextElement().toString();
String beanPath = prop.getProperty(key);
Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
beans.put(key,value);
}
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new ExceptionInInitializerError("初始化propertise失败");
}
}
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
运行,查看控制台,输出三个首地址一样
到这就初始了程序的耦合与解耦了,明天开始学习Spring,听说IOC和AOP是核心,得先看看耦合和解耦的知识,学Spring起来会轻松很多,就有了此笔记,冲冲冲,明天冲Spring!!