目录
2.4 控制反转 - Inversion Of Control
一、Spring 概述
1.1 Spring 是什么
Spring是分层的Java SE/EE应用 full-stack轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
1.2 Spring 的发展历程
可参考 https://spring.io/projects/spring-framework#learn
1.3 Spring 的优势
1:方便解耦,简化开发 (工厂模式)
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2:AOP编程的支持 (jdk动态代理、cglib动态代理)
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
声明式事务的支持可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
3:方便程序的测试(和Junit整合)
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
4:方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Mybatis、Hessian、Quartz、ActivityMQ、Redis等)的直接支持。
5:降低JavaEE API的使用难度 (SSM整合)
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
6.Java源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。
1.4 Spring 的体系结构
二、IoC 的概念和作用
2.1 程序的耦合和解耦
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。
耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
它有如下分类:
(1) 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
(2) 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
(3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
(4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
(5) 标记耦合 。若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合。
(6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
(7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素==,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚:
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.2 解决程序耦合的思路一:反射
使用反射技术创建 JDBC 连接,避免使用 new 关键字:
import java.sql.*;
public class JdbcDemo {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 使用反射来创建对象,而避免使用new关键字。
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/itcastspring","root","root");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL,得到结果集
ResultSet rs = pstm.executeQuery();
//5.遍历结果集
while(rs.next()){
System.out.println(rs.getString("username"));
}
//6.释放资源
rs.close();
pstm.close();
conn.close();
}
}
2.3 解决程序耦合的思路二:工厂模式
使用工厂模式+反射技术创建对象:
bean.properties 配置文件
accountService=com.cpz.service.impl.AccountServiceImpl
accountDao=com.cpz.dao.impl.AccountDaoImpl
ApplicationContext.java(工厂)
package com.cpz.dao.factory;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class ApplicationContext {
// 创建的对象放置到配置文件(xml,properties)
private static Properties properties = null;
// 加载工厂创建的所有的对象,放置Map<String,Object>
private static Map<String,Object> map = null;
// 加载工厂创建的所有的对象,放置Map<String,Object>
static {
properties = new Properties();
InputStream is = ApplicationContext.class.getClassLoader().getResourceAsStream("bean.properties");
map = new HashMap<String, Object>();
try {
properties.load(is);
// 读取properties文件中的key值
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()){
// 读取每个key值
String key = keys.nextElement().toString();
// 通过key值,读取value
String path = properties.get(key).toString();
// 使用反射,创建对象
Object obj = Class.forName(path).newInstance();
// 存放到map中
map.put(key,obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getBean(String name){
Object obj = map.get(name);
return obj;
}
}
AccountService.java 接口
package com.cpz.service;
public interface AccountService {
public void saveAccount();
}
AccountServiceImpl.java 实现类
package com.cpz.service.impl;
import com.cpz.dao.AccountDao;
import com.cpz.dao.factory.ApplicationContext;
import com.cpz.service.AccountService;
public class AccountServiceImpl implements AccountService {
public void saveAccount() {
System.out.println("执行AccountServiceImpl中的saveAccount方法!");
AccountDao accountDao = (AccountDao) ApplicationContext.getBean("accountDao");
accountDao.saveAccount();
}
}
AccountDao.java 接口
package com.cpz.dao;
public interface AccountDao {
public void saveAccount();
}
AccountDaoImpl.java 实现类
package com.cpz.dao.impl;
import com.cpz.dao.AccountDao;
public class AccountDaoImpl implements AccountDao {
public void saveAccount() {
System.out.println("执行AccountDaoImpl类中的saveAccount方法!");
}
}
Main.java 测试类
package com.cpz.main;
import com.cpz.dao.factory.ApplicationContext;
import com.cpz.service.AccountService;
public class Client {
public static void main(String[] args) {
AccountService accountService = (AccountService) ApplicationContext.getBean("accountService");
accountService.saveAccount();
}
}
2.4 控制反转 - Inversion Of Control
原来:我们在获取对象时,都是采用new的方式。是主动的。
现在:我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。这种被动接收的方式获取对象的思想就是控制反转(IOC),它是spring框架的核心之一。
明确IOC的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系),将对象的创建和调用都交给spring容器去处理
2.5 单例对象
2.5.1 测试单例对象
Client.java
package com.cpz.main;
import com.cpz.dao.factory.ApplicationContext;
import com.cpz.service.AccountService;
public class Client {
public static void main(String[] args) {
AccountService accountService1 = (AccountService) ApplicationContext.getBean("accountService");
accountService1.saveAccount();
AccountService accountService2 = (AccountService) ApplicationContext.getBean("accountService");
accountService2.saveAccount();
AccountService accountService3 = (AccountService) ApplicationContext.getBean("accountService");
accountService3.saveAccount();
System.out.println(accountService1);
System.out.println(accountService2);
System.out.println(accountService3);
}
}
结果显示:
由图我们可以看出,调用了三次getBean方法所产生的三个对象其实是一个对象,他们的地址值完全相同。
2.5.2 单例对象的变量问题
AccountServiceImpl.java
首先,我们在 AccountServiceImpl.java 文件中加入一个全局变量 i ,并在 saveAccount 方法中使 i 自增1。
public class AccountServiceImpl implements AccountService {
AccountDao accountDao = (AccountDao) ApplicationContext.getBean("accountDao");
int i = 1;
public void saveAccount() {
System.out.println("执行AccountServiceImpl中的saveAccount方法! i = " + i);
accountDao.saveAccount();
i++;
}
}
Client.java
然后在主方法中循环调用 getBean 方法,不断创建 AccountService 对象,并用对象调用 saveAccount 方法,看 i 的值的变化。
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
AccountService accountService = (AccountService) ApplicationContext.getBean("accountService");
accountService.saveAccount();
}
}
}
结果显示:
由图可知,当其为单例对象时,使用全局变量会导致全局变量的值不断被修改。所以在单例对象模式下,我们只能使用局部变量,即将 AccountServiceImpl.java 中的 i 放入 saveAccount 方法中。
总结:单例对象,不要使用全局变量,只能使用局部变量,因为全局变量的值会被修改
2.6 多例对象
2.6.1 测试多例对象
我们可以修改工厂类中的 getBean 方法来使单例对象变为多例对象,其核心思想为每次调用方法都会重新创建一个工厂对象,这样每次得到的对象就不同了。
ApplicationContext.java
// 多例对象
public static Object getBean(String name) {
Object obj = null;
String path = properties.getProperty(name);
try {
obj = Class.forName(path).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
结果显示:
由图可知,调用方法后每次返回的对象不再是同一个对象,则为多例对象。