Spring 框架介绍
一、Spring 框架介绍
1. 框架概述
Spring 是 2003 年兴起的轻量级 Java 开源框架,由 Rod Johnson 创建,核心为控制反转(IOC)和面向切面(AOP),用于解决企业应用开发复杂性,是分层的 JavaSE/EE 全栈框架。
2. 框架优点
- 解耦简化开发,IOC 管理对象创建和依赖关系。
 - 支持 AOP 编程,实现权限拦截、运行监控等功能。
 - 提供声明式事务管理,无需手动编程。
 - 方便程序测试,支持 Junit4 注解测试。
 - 可集成 Struts2、Hibernate 等优秀框架。
 - 封装 JavaEE 难用 API,降低使用难度。
 
二、Spring 的 IOC 核心技术
1. IOC 概念
IOC 即控制反转,将对象创建权力交给 Spring 框架,用于降低代码耦合度,由 Spring 工厂读取配置文件管理对象。
2. IOC 程序入门
- 导入 spring-context、commons-logging 等依赖坐标。
 - 编写接口及实现类,定义业务方法。
 - 创建 Spring 配置文件 applicationContext.xml,配置 Bean 标签管理对象。
 - 编写测试类,通过 ApplicationContext 加载配置文件获取 Bean 对象并调用方法。
 
3. IOC 技术总结
- ApplicationContext 为工厂接口,实现类有 ClassPathXmlApplicationContext(加载类路径配置文件)和 FileSystemXmlApplicationContext(加载本地磁盘配置文件)。
 
4. Bean 管理的配置文件方式
- id 属性唯一标识 Bean,class 属性指定 Bean 全路径。
 - scope 属性定义作用范围,包括 singleton(单例,默认)、prototype(多例)、request、session。
 - init-method 和 destroy-method 属性配置 Bean 初始化和销毁方法,单例 Bean 随容器关闭销毁,多例 Bean 由垃圾回收机制回收。
 
5. 实例化 Bean 的三种方式
- 无参数构造方法(默认方式):直接配置 Bean 标签。
 - 静态工厂实例化:配置 Bean 标签指定工厂类和静态工厂方法。
 - 实例工厂实例化:先配置工厂 Bean,再配置目标 Bean 指定工厂 Bean 和实例方法。
 
一、单例模式的核心定义
单例模式(Singleton Pattern) 是一种创建型设计模式,其核心目标是确保一个类在整个应用程序中仅有一个实例对象,并提供一个全局唯一的访问点来获取该实例。
简单来说,就是让某个类 “一生只能 new 一次”,无论在程序的哪个位置调用,拿到的都是同一个对象实例。
二、单例模式的核心特点
- 唯一性:类的实例在全局范围内唯一,避免重复创建多个对象造成资源浪费。
 
- 全局访问性:提供统一的静态方法(如getInstance()),方便程序任何位置获取实例。
 
- 私有构造器:通过私有化构造方法(private 构造方法名()),禁止外部通过new关键字创建实例。
 
- 延迟初始化或饿汉式初始化:实例的创建时机分为 “需要时再创建”(延迟)和 “程序启动时就创建”(饿汉)。
 
三、单例模式的适用场景
- 资源密集型对象:创建成本高、消耗资源多的对象,如数据库连接池、线程池、日志工厂。
 
- 全局共享状态:需要统一管理全局状态的场景,如配置信息类、计数器。
 
- 工具类:无状态的工具类,如日志工具、日期工具,避免重复创建浪费资源。
 
- 系统核心组件:整个系统依赖的核心组件,如缓存管理器、会话管理器,确保数据一致性。
 
四、单例模式的常见实现方式
(一)饿汉式(立即加载)
1. 实现原理
程序启动时(类加载阶段)就创建实例,无论后续是否使用该实例。
2. 代码示例
public class HungrySingleton {
// 类加载时直接创建实例(线程安全,类加载机制保证唯一)
private static final HungrySingleton INSTANCE = new HungrySingleton();
// 私有构造器,禁止外部new
private HungrySingleton() {}
// 全局访问点
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
3. 优缺点
- 优点:实现简单,天然线程安全(类加载由 JVM 保证,仅执行一次),获取实例速度快。
 
- 缺点:资源浪费,若实例长时间未使用,提前创建会占用内存;无法实现延迟加载。
 
(二)懒汉式(延迟加载)
1. 基础版(非线程安全)
(1)实现原理
仅在第一次调用getInstance()时创建实例,后续调用直接返回已创建的实例。
(2)代码示例
public class LazySingleton {
// 初始为null,延迟创建
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
// 第一次调用时创建实例
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
(3)优缺点
- 优点:延迟加载,节省内存,仅在需要时创建实例。
 
- 缺点:线程不安全,多线程环境下可能创建多个实例(如两个线程同时进入if (instance == null)判断)。
 
2. 线程安全版(加锁同步)
(1)实现原理
在getInstance()方法上添加synchronized关键字,保证多线程环境下仅一个线程能进入方法创建实例。
(2)代码示例
public class SafeLazySingleton {
private static SafeLazySingleton instance;
private SafeLazySingleton() {}
// 方法加锁,保证线程安全
public static synchronized SafeLazySingleton getInstance() {
if (instance == null) {
instance = new SafeLazySingleton();
}
return instance;
}
}
(3)优缺点
- 优点:线程安全,实现延迟加载。
 
- 缺点:性能低下,每次调用getInstance()都需要加锁解锁,即使实例已创建(锁竞争导致效率低)。
 
3. 双重检查锁定(DCL,Double-Check Locking)
(1)实现原理
优化线程安全版的性能问题,通过 “双重判断 + volatile 关键字”,仅在实例未创建时加锁,后续直接返回实例。
(2)代码示例
public class DclSingleton {
// volatile关键字防止指令重排
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
// 第一次检查:避免已创建实例时的锁竞争
if (instance == null) {
// 加锁,保证同一时刻仅一个线程进入
synchronized (DclSingleton.class) {
// 第二次检查:防止多个线程等待锁时重复创建
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
}
(3)关键说明
- volatile 关键字:instance必须用volatile修饰,否则可能出现 “指令重排” 导致的线程安全问题。
 
原因:instance = new DclSingleton()可拆解为 3 步:①分配内存;②初始化对象;③将instance指向内存地址。JVM 可能重排为①→③→②,若线程 A 执行完①→③,线程 B 第一次检查instance != null,直接返回未初始化的对象,导致空指针异常。
(4)优缺点
- 优点:线程安全,延迟加载,性能高效(仅第一次创建实例时加锁)。
 
- 缺点:实现较复杂,需注意volatile关键字的使用。
 
(三)静态内部类式(推荐)
1. 实现原理
利用 Java 静态内部类的特性:静态内部类不会随外部类加载而初始化,仅在第一次被调用时初始化,且初始化过程由 JVM 保证线程安全。
2. 代码示例
public class StaticInnerClassSingleton {
// 私有构造器
private StaticInnerClassSingleton() {}
// 静态内部类,存储单例实例
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
// 全局访问点,调用时触发内部类初始化
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
3. 优缺点
- 优点:线程安全(JVM 保证内部类初始化唯一),延迟加载(内部类按需初始化),性能高效(无锁竞争),实现简洁。
 
- 缺点:无法通过反射破坏单例(反射可调用私有构造器,需额外处理)。
 
(四)枚举式(最优,防反射、防序列化)
1. 实现原理
利用 Java 枚举的特性:枚举类的实例是天然的单例,由 JVM 保证唯一,且默认防止反射创建实例、防止序列化破坏单例。
2. 代码示例
public enum EnumSingleton {
// 唯一实例(枚举常量)
INSTANCE;
// 枚举类可添加自定义方法
public void doSomething() {
System.out.println("枚举单例执行操作");
}
}
3. 使用方式
// 获取实例
EnumSingleton singleton = EnumSingleton.INSTANCE;
// 调用方法
singleton.doSomething();
4. 核心优势
- 天然线程安全:枚举类的实例在类加载时创建,JVM 保证唯一。
 
- 防反射破坏:Java 反射机制明确禁止反射创建枚举实例,避免通过Constructor.newInstance()创建多个实例。
 
- 防序列化破坏:枚举类默认重写readResolve()方法,序列化后反序列化仍返回原实例(普通单例需手动重写readResolve())。
 
- 实现极简:无需手动处理构造器、锁、volatile 等,代码简洁高效。
 
五、单例模式的常见问题与解决方案
(一)反射破坏单例
1. 问题描述
对于非枚举单例(如饿汉式、懒汉式),可通过 Java 反射调用私有构造器创建新实例,破坏单例唯一性。
2. 解决方案
在私有构造器中添加判断,若实例已存在则抛出异常:
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
// 防止反射创建实例
if (INSTANCE != null) {
throw new IllegalStateException("单例实例已存在,禁止重复创建");
}
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
(二)序列化破坏单例
1. 问题描述
普通单例类实现Serializable接口后,序列化存储实例,反序列化时会创建新实例,破坏单例。
2. 解决方案
重写readResolve()方法,反序列化时返回已有的单例实例:
import java.io.Serializable;
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile SerializableSingleton instance;
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
if (instance == null) {
synchronized (SerializableSingleton.class) {
if (instance == null) {
instance = new SerializableSingleton();
}
}
}
return instance;
}
// 反序列化时返回原实例
private Object readResolve() {
return getInstance();
}
}
六、单例模式的总结
实现方式  | 线程安全  | 延迟加载  | 防反射  | 防序列化  | 性能  | 推荐度  | 
饿汉式  | 是  | 否  | 否  | 否  | 高  | ★★★☆☆  | 
懒汉式(基础版)  | 否  | 是  | 否  | 否  | 高  | ★☆☆☆☆  | 
懒汉式(加锁)  | 是  | 是  | 否  | 否  | 低  | ★★☆☆☆  | 
双重检查锁定  | 是  | 是  | 否  | 否  | 高  | ★★★★☆  | 
静态内部类式  | 是  | 是  | 否  | 否  | 高  | ★★★★★  | 
枚举式  | 是  | 否  | 是  | 是  | 高  | ★★★★★  | 
推荐选择
- 若需延迟加载:优先静态内部类式。
 
- 若需绝对安全(防反射、防序列化):优先枚举式。
 
- 若无需延迟加载且实现简单:可选择饿汉式。
 
