Spring 概述
1. 程序的耦合
耦合:程序间的依赖关系
解耦:降低
程序间的依赖关系
分类
- 类之间的依赖
- 方法之间的依赖
2. 类之间的依赖问题的实例
2.1 JDBC操作数据库存在的耦合问题
首先正常编写一个jdbc查询操作的类
package com.itheima.jdbc;
import java.sql.*;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
//1. 注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://xxx", "root", "123456");
//3. 获取操作的数据库的预处理对象
PreparedStatement preparedStatement = connection.prepareStatement("select * from account");
//4. 执行SQL,得到结果集
ResultSet resultSet = preparedStatement.executeQuery();
//5. 遍历结果集
while (resultSet.next()){
System.out.println(resultSet.getString("money"));
}
//6. 释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
查询结果是正常的
很容易看出在注册驱动时,这个类是依赖于com.mysql.jdbc.Driver
这个类的,这就是类之间的依赖问题,那这种情况下会导致什么问题出现呢
试想,假如com.mysql.jdbc.Driver
这个类不存在,那么程序在编译期就会报错
我们期望的是,编译期是正常进行的,直到程序执行时才报错。
注册驱动时做如下修改
可以看到编译期是正常进行的,这样就做到了编译期不依赖,运行期才依赖,这里就用到了反射。
2.2 解耦的思路
- 使用反射来创建对象,而避免用new关键字
但是使用反射传入的字符串在上述的例子中是写死的,这样如果更改驱动就需要修改代码,于是就引出了第二条思路 - 通过读配置文件来获取要创建的对象全限定类名
有了这两条思路,再来看下实际的Web开发中一个类之间依赖的实例
2.3 Web开发中的类之间依赖的实例
表现层
业务层
持久层
在上述过程中,表现层和业务层都通过new
关键字获取到了下层的对象,这也是类之间的依赖问题,也就是耦合问题。
那我们的思路跟JDBC注册驱动是一样的,反射和配置文件结合,从而引出了工厂模式。
- 需要一个配置文件来配置需要创建的service和dao
配置的内容:唯一标识=全限定类名(key=value) - 通过读取配置文件中配置的内容,反射创建对象
配置文件可以是xml,也可以是properties
2.4 工厂模式解耦
- 创建配置文件,对应解耦思路的第二条,通过读配置文件来获取要创建的对象全限定类名,这个配置文件的内容就是用来配置用于获取指定类对象的标识和指定类对象的全限定类名。
- 单独创建一个工厂类,用于解析配置文件,能够根据调用者给出的标识获取到标识对应的全限定类名,同时通过反射拿到该类的对象,并返回给调用者
package com.itheima.factory;
import java.io.InputStream;
import java.util.Properties;
public class Factory {
private static Properties props;
static {
try {
//生成一个properties文件实例
props = new Properties();
InputStream in = Factory.class.getClassLoader().getResourceAsStream("factory.properties");
//加载我们自己的配置文件
props.load(in);
}catch (Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
/**
*
* @param name 配置文件中的标识,用于获取指定类的全限定类名
* @return
*/
public static Object getBean(String name){
//bean 要返回给调用者的对象,后面是通过反射创建出来的
Object bean = null;
try {
//根据标识获取到标识对应的全限定类名
String property = props.getProperty(name);
//根据解析出来的全限定类名通过反射得到指定的类并生成该类的一个对象
bean = Class.forName(property).newInstance();
}catch (Exception e){
e.printStackTrace();
}
//将反射生成的对象返回给调用者
return bean;
}
}
- 修改表现层和业务层的代码
业务层
表现层
至此,就实现了Web实际开发中的编译期不依赖,运行期才依赖
2.5 工厂模式改进
上述的工厂模式中,其实是多例的,就是调用一次产生一个新的对象,那么,如何实现单例的工厂模式呢
思路是这样的:
工厂类在初始化时,将配置文件中涉及的所有类都创建一个对象,将对象存入仓库,调用者需要对象的时候直接从仓库取即可。
持久层、业务层、表现层的代码不用动,只修改工厂类的静态代码块和方法即可
package com.itheima.factory;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class Factory {
private static Properties props;
private static Map<String, Object> ck;
static {
try {
//生成一个properties文件实例
props = new Properties();
InputStream in = Factory.class.getClassLoader().getResourceAsStream("factory.properties");
//加载我们自己的配置文件
props.load(in);
//实例化一个仓库
ck = new HashMap<String, Object>();
//获取配置文件中的所有键
Enumeration keys = props.keys();
//根据键遍历所有值,为每个值通过反射实例化对象并存入仓库
while (keys.hasMoreElements()){
String s = keys.nextElement().toString();
String property = props.getProperty(s);
Object o = Class.forName(property).newInstance();
ck.put(s, o);
}
}catch (Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
public static Object getBean(String name){
//调用者调用时直接返回仓库中的对象
return ck.get(name);
}
}
至此,就实现了单例的工厂模式
3. IOC 控制反转
被动接收方式获取对象的思想就是控制反转
补充:工厂模式中iUserDao获取对象就是被动接收的
4. Spring中的一些概念
组件:对应到Web应用中,就是Web应用的每一个部分,比如表现层,业务层,持久层
Bean:可重用组件
JavaBean:用Java语言编写的可重用组件
注:JavaBean的范围大于实体类的范围,像Service、Dao等一切可重用的组件都是JavaBean的范畴
容器:即工厂模式中的工厂
maven工程的配置文件是放在resource下的,resource在编译后会放到class目录下
单例对象有线程问题(类的成员变量)
表现层 请求参数封装
IOC:解决程序间的依赖关系
持久层:Dao
业务层:Service
表现层:Controller