8.4Springboot
发展阶段
一阶段SSH
servlet+jsp :web开发的初级阶段.
mvc三层架构
m:model ,v:view ,c:controller
struct 1.0 mvc, .do
spring 1.0 :专注于创建对象
hibernate 1.0 :orm对象关系映射 ,将数据库中的结果集转换成java对象,无需写sql
二阶段SSH2
structs 2.0:.action
spring 2.0+: aop功能,面向切面编程
ibatis/mybatis:orm,轻量级,需要程序员自己写sql
hibernate :2.0
三阶段SSM:需要使用xml进行配置
springmvc: 解决mvc,替代了structs 2.0
spring3.0+: 增加更多功能,模块
mybatis:3.0+,替代了hibernate
微服务
SpringBoot:不是一个框架,而是一个快速开发脚手架 快速开发,在一定程度上限制了自由行,有固定的目录结构.有约定俗成的配置,约定大于配置
Springboot整合SSM
以spring-boot- starter开头的包,叫启动器
Springboot配置文件:
-
application.properties只支持一种编码
-
application.yml支持中文编码
写法不同,难度大体相同
Spring两项核心功能:
1.控制反转:将创建对象的权限交给Spring框架,成为控制反转,即为ioc Inverse Of Control
2.依赖注入:Spring框架自动将容器中的实例,自动赋值给需要的类 :Dependcy Injection ,DI
Spring Bean单例模式
1.默认单例
@Service // 默认是单例模式
public class StudentServiceImpl implements StudentService {
// 整个应用中只有一个实例
}
2.单例的优势
节省内存:避免创建多个相同实例 提高性能:无需重复创建和销毁对象 状态共享:可以在整个应用中共享状态(谨慎使用)
3.Component及相关注解
@Component @Service // @Service是@Component的特化 @Repository // @Repository是@Component的特化 @Controller // @Controller是@Component的特化
这些注解告诉Spring:
-
这个类需要被Spring容器管理
-
需要创建并维护这个类的实例
-
这个实例可以被注入到其他类中
@Autowired注解:
-
默认按类型匹配:使用频率比较高,当有多个匹配时,可以使用@Primary注解首先使用哪个实现类
-
按名称匹配(@Component("i0")-----@Qualifier("i0")) 如果不手动起名,自动用首字母小写的具体实现类名命名(搭配@Qualifier注解,同时制定bean的名称,bean名称可以在@Component中指定)
依赖注入的三种方式
通常注入接口而不是具体实现类(更好的扩展性,耦合性)
字段注入 (不推荐,但不是不能用,还有@Resource等)
@Autowired StudentService studentService;
构造器注入 (可以省略@Autowired)
@Autowired
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
setter注入(推荐)
@Autowired
public void setStudentService (StudentService studentService) {
this.studentService = studentService;
}
用于标识 Bean 并被 Spring 容器管理的注解
-
@Component 组件,Spring框架会扫描根目录下所有带有这个注解的文件然后创建一个唯一实例放在ac,ac放到全局域里面去
-
@Service 专门用于业务逻辑层(Service 层)
-
@Repository 用于数据访问层(Repository/Dao 层)
-
@Controller 用于Web 控制层(Controller 层)
-
@RestController 是
@Controller与@ResponseBody的组合注解,专为RESTful API 开发设计(如前后端分离场景)。标识类处理请求后,会自动将返回值转换为 JSON/XML 等数据格式,无需额外标注@ResponseBody。 -
@Configuration 用于标识配置类,替代传统的 XML 配置文件。类中可以通过
@Bean注解定义其他 Bean 的创建逻辑,Spring 会扫描并加载这些配置,管理其中定义的 Bean。 -
@ControllerAdvice 用于全局异常处理、全局数据绑定、全局请求参数预处理等场景,是
@Component的派生注解。它能对所有@Controller标注的类进行增强,集中处理跨控制器的通用逻辑(如统一异常捕获)。
这些注解与 @Component 的核心共性是:都能被 Spring 组件扫描发现并纳入 IoC 容器管理(默认单例),支持依赖注入。区别在于语义和附加功能
在Controller中,一个方法只有指定@XXXMapping("/"), 才能接收并处理请求
@GetMapping : 接收get类型请求
@PostMapping : 接收post类型请求
@PutMapping : 接收put类型请求
@PatchMapping : 接收patch类型请求
@DeleteMapping : 接收delete类型请求
@RequestMapping : 接收任意类型请求,但可以手动指定响应类型
@RequestMapping(value = "/student/list", method = RequestMethod.GET)
produces:指定响应内容类型
@XXXMapping也可以添加到类上,@RequestMappping(value="/student") ,表示类中所有方法都以这个路径为前缀
控制器(Controller)方法的参数类型、参数绑定规则以及返回值类型的处理规则,主要用于明确在编写 Controller 层代码时,方法可以接收哪些类型的参数、参数如何与请求数据绑定,以及不同返回值会被如何处理并响应给客户端。
参数可以是 (数量,顺序可以任意):
1.HttpServletRequest,HttpServletResponse,HttpSession
2.基本数据类型, 以及包装类,BigInteger,BigDecimal, String, 用于接收请求中的参数(参数名相同)
搭配@RequestParam(),, 搭配@PathVariable(路径变量)
3.javabean 普通类, 将请求参数通过反射,设置到javabean实例中
4.map,Model,ModelMap: 充当请求域 ,map最常用
5.加@RequestMapping注解的map,此map不在作为请求域
6.以上类型的任意组合
返回值:
1.字符串且响应内容类型为text/html,返回值即模板文件名
在满足1的基础上,返回"forward:/XXX",表示请求转发
在满足1的基础上,返回"redirect:XX",表示重定向
2.ModelAndView: 既可以指定视图名称,同时可以当请求域使用,前三种本质上也会被包装成ModelAndView
3.如果指定响应内容类型为json,,并且添加了@ResponseBody注解,无论返回值是什么类型,都会序列化为json字符串
4.返回ResponseEntity类型,仅限于响应json格式,同时封装了业务数据 ,以及响应的状态码
8.5MyBatis
orm工具,半自动化orm工具(Object relational Mapping 对象关系映射)
Springboot如何整合mybatis
1.添加依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>
2.mybatis配置
#配置mybatis mybatis: configuration: #在映射为java对象,将表中的下划线命名自动转驼峰式命名 map-underscore-to-camel-case: true #日志前缀,可选 log-prefix: mybatis. #日志实现类,可选 log-impl: org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl #动态sql文件存储位置 mapper-locations: classpath:/mapper/**/*.xml #配置日志显示sql logging: level: #指定日志前缀 mybatis: debug
application.properties
spring.application.name=demo03 #数据源配置 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/students?serverTimezone=Asia/Shanghai&characterEncoding=utf-8 spring.datasource.username=root spring.datasource.password=1234 #Jackson配置 - 解决Lombok序列化问题 spring.jackson.serialization.fail-on-empty-beans=false #配置mybatis #开启驼峰命名转换,将数据库中的下划线命名自动转换为 Java 的驼峰命名, mybatis.configuration.map-underscore-to-camel-case=true #日志前缀,可选 mybatis.configuration.log-prefix=mybatis. #日志实现类,可选 mybatis.configuration.log-impl=org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl #动态sql文件存储位置 mybatis.mapper-locations=classpath:/mapper/**/*.xml #可以使用简称的包 type.aliases-package:com.situ.tms2024.model #配置日志显示sql,方便开发调试 logging.level.mybatis=debug
3.编写mybatis的mapper接口
添加@Mapper注解
4.编写动态sql文件:mapper文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.situ.tms2024.dao.StudentDAO">
<select id="findAll" resultType="com.situ.tms2024.model.Student">
select id, name ,pinyin ,sex,birthday,height,weight from t_student limit 20
</select>
</mapper>
5.安装mybatis-x插件(不是必须)
动态sql如何使用参数?OGNL表达式,以反射的形式取数据。 1.单个参数(javabean或map类型),在xml中可直接通过其属性访问。
2.多个参数。必须指定参数名。也可以手动指定名称。
3.如果指定了参数名称,则必须通过参数名引用。
模型查询:
<!-- resultType表示数据库表字段名和model中类名自动映射
resultMap表示手动映射,与id绑定:
<resultMap id="StudentMap" type="Student">
<result property="py" column="pinyin"/>
</resultMap>
-->
<select id="findAll" resultType="Student">
select id,
stu_id,
name,
pinyin as py,
sex,
birthday,
height,
weight,
email,
phone,
description
from t_student
<where>
<!-- 普通查询 -->
<if test="stuId!=null and stuId!=''">
and stu_id=#{stuId}
</if>
<!-- 模糊查询 -->
<if test="name!=null and name!=''">
<bind name="nameLike" value="'%'+ name+'%'"/>
and name like #{nameLike}
</if>
</where>
</select>
或者字符串拼接,不推荐(sql注入风险)
and `name` like '%${nameLike}%'
手动映射
一.类型转换器
二. 关联查询
一对一关联:
一对多关联:
什么是N+1问题:
-
1次查询获取父实体列表(例如获取所有作者)
-
N次查询为每个父实体获取关联的子实体(例如获取每个作者的所有书籍)
N+1问题解决性能问题:
1、一级缓存(MyBatis 的一级缓存是自带的,默认开启。)
:指在同一个会话(SqlSession)期间,如果有相同的sql查询,则进行缓存。
/*SqlSession sqlSession = null;//会话
try {
sqlSession = sqlSessionFactory.openSession(true);
List<Student> students = sqlSession.selectList("com.situ.tms2024.dao.StudentMapper.findAll",
ssb);
return students;
} finally {
sqlSession.close();
}*/
//上面等同于下面写法:
return studentMapper.findAll(ssb);
2、延迟加载:用到的时候,才去进行关联查询。每次使用到要延迟加载的属性时,会发出一个全新的会话。一级缓存失效。
-
配置文件中添加:#lazy-loading-enable:true -------开启延迟加载,默认不开启
-
在model类上添加@JsonIgnoreProperties("handler") ,不进行序列化
3、二级缓存:开启延迟加载的情况下 3.1 打开开头cache.enabled=true 3.2 引入依赖。 3.3 在动态sql文件中定义cache标记。 3.4 在select标记上声明useCache,在update、insert、delete上声明flushCache。在整个应用期间都有效,有效范围是整个。
二级缓存:
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-caffeine -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-caffeine</artifactId>
<version>1.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.2</version>
</dependency>
自动分页插件
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
在api中创建一个分页对象
PaginateInfo pi = PaginateInfo.of(pageNo, pageSize);
然后调用Service的方法的时候把分页对象当做参数传进去
List<Student> students = studentService.findAll(pi, ssb);
serviceImpl
//自动分页
try (Page<?> p = PageHelper.startPage(pi.getPageNo(), pi.getPageSize())) {
return studentMapper.findAll(ssb);
}
然后在api接口中返回的对象就已经被分页处理过了
//获取分页信息
PageInfo<Student> pageInfo = new PageInfo<>(students);
return Map.of("pi", pageInfo, "success", true);
使用@RestController的方法都不返回页面,响应json数据(微服务api)
8.6 Spring AOP
面向切面编程(Aspect Orinted Programming)
传统:纵向编程
切面:横向编程,批量操作.
SpringAOP核心机制就是代理模式,代理对象包裹目标对象,在方法调用前后插入切面逻辑。这是典型的代理模式应用。
使用原理:动态代理,基于代理模式(对目标业务进行改变和增强)
代理:
一.静态代理
-
手动编码
-
使用jaspect注解自动生成
二、动态代理。在程序运行期间,创建目标类的代理实例。
-
JDK动态代理:无需引入第三方库,jdk默认支持。要求:代理类必须实现接口。
-
Cglib动态代理:原理基于继承。要求目标类以及目标方法不能是final的。
使用AOP
1.引入AOP
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.定义切面类,切入点和通知
package com.situ.tms2024.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
//表示定义切面类
//如果 com.situ.tms2024.service.impl 包下的类实现了接口,Spring 默认会使用 JDK 动态代理
//如果这些类没有实现接口,Spring 会使用 CGLIB 代理
@Aspect
public class TimeStateAspect {
//指定切入点 下面指:任意类任意方法任意参数 任意返回值(*表示任意参数)
@Pointcut("execution(* com.situ.tms2024.service.impl.*.*(..))")
public void pc(){
//空方法
}
//前置通知,即函数执行前执行.括号里面是切入点
@Before("pc()")
public void f1(){
System.out.println("开始执行");
}
//后置通知,即函数执行后执行.括号里面是切入点
@After("pc()")
public void f2(){
System.out.println("结束执行");
}
//环绕通知,即函数执行前后执行.括号里面是切入点
@Around("pc()")
public Object f3(ProceedingJoinPoint pjp){
long start = System.currentTimeMillis();
try {
//执行目标方法
Object rtn = pjp.proceed();
long end = System.currentTimeMillis();
String method=pjp.getSignature().getName();
long time = end - start;
System.out.println("方法执行耗时:" + time);
return rtn;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
//返回后通知,即函数正常执行结束.括号里面是切入点
@AfterReturning("pc()")
public void f4(){
System.out.println("正常返回");
}
//异常后通知,即函数异常执行结束.括号里面是切入点
@AfterThrowing("pc()")
public void f5(){
System.out.println("异常返回");
}
}
//执行顺序:
//@Around 前半部分 → @Before → 目标方法 → @Around 后半部分 → @After → @AfterReturning/@AfterThrowing
三.事务处理
事务:数据库的概念 ,,一个sql批量执行不可分割的整体(至少有两条sql),要么同时执行成功,要么同时失败 场景:银行转账,他账户余额减少,你增加
四大特性ACID : 原一隔持
-
原子性: 一个事务中的所有操作要么全部成功,要么全部失败
-
一致性:事务执行之前和事务执行之后数据是相同的
-
隔离性: 多个事务之间不能相互干扰
-
持久性: 事务执行成功后数据就持久化到数据库中
Connection conn = DrvierManager.getConnection()
conn.setAutoCommit(false); //设置不自动提交
try {
....
conn.commit();//手动提交
} catch(Exception e) {
conn.rollback();//回滚
}
使用事务:
-
引入spring-boot-starter引入依赖
-
在主启动类或者配置类上添加@EnableTransactionManagement注解
-
在要开启事务的方法上添加@TransAction注解
@Transactional(rollbackFor = Exception.class,
isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)//不定义冒泡机制,默认使用required
isolation:并发事务隔离级别,取决于数据库的支持
-
读未提交:脏读
-
读已提交:不可重复读,同一事务中多次读取同一数据可能得到不同结果
-
可重复读:确保同一事务中多次读取同一数据结果一致(同一个sql数据一样) 危险:幻读
-
串行化:排队执行
从上往下,安全性递增,性能递减
冒泡(传播)机制:和数据库无关,是spring有关。
七种传播机制: propagation=Propagation.(前两个常用)
-
REQUIRED(默认):需要事务,如果上层调用者已经开启事务,则不再开,也就是外层N个sql和内层M个sql,M+N个共同组成一个事务,通常在写(增删改)方法上
-
SUPPORTS:支持事务. 不会主动开启事务,如果上层有事务,则不做任何操作,通常定义在读方法上
-
MANDATORY:如果上层有实物,则加入,如果没有,抛出异常
-
REQUIRES_NEW:需要食物,创建新事物,如果上层有事务,则挂起(停掉)
-
NOT_SUPPORTED:不支持事务,如果上层有事务,则挂起
-
NEVER:不支持事务,如果上层有事务,则抛出异常
-
NESTED:以嵌套方式运行事务
四.Spring缓存(基于Spring aop)
Spring缓存:业务数据缓存
Caffcine是单机缓存,redis是分布式缓存中间件,缓存数据库
TTL:Time To Live
Redis五大基础数据类型(重要) 键是字符串类型,值有五大类型
-
String: 字符串
-
List :列表,有序可重复的列表
-
Set : 无序集合,不可重复
-
Zset : 有序集合类,顺序不是指插入顺序,而是指每个元素可以指定一个权重
-
Hash :哈希,存储键值对
Springboot整合Redis,实现缓存功能
一.引入依赖
<!--aop启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Spring缓存-->
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
二.配置
#redis配置
data:
redis:
# 默认值:host,port,database: 0
host: 127.0.0.1
port: 6379
password: 1234
database: 0
#读写操作的超时时间
timeout: 1s
#连接超时时间
connect-timeout: 3s
cache:
redis:
#缓存过期时间
time-to-live: 4h
三.使用Redis读写数据(示例),使用RedisTemplate
//RedisTemplate: 操作redis的模板类
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {this.redisTemplate = redisTemplate;}
//放入redis中
redisTemplate.opsForValue().set("captcha", captcha.text().toLowerCase(), Duration.ofMinutes(1));
//取出验证码并删除
String sessionCaptcha = (String) redisTemplate.opsForValue().getAndDelete("captcha");
四. 使用Springboot读写缓存
频繁地会查询而又不怎么改变的适合去缓存
引入依赖 Spring缓存,spring aop
配置
#Spring缓存配置
cache:
redis:
#缓存过期时间
time-to-live: 4h
#指定Spring缓存的实现类
type: redis
使用Spring cache的注解对业务类进行标注
@EnabCaching(主启动类上或配置类上)
//@CacheAble放在方法上,表示缓存数据,key表示缓存的key,cacheNames表示缓存的缓存空间名称, @Cacheable(key="#id", cacheNames="class")
与缓存相关的注解:
@Cacheable:表示这个方法的返回值加入到缓存中
@CachePut更新缓存
@CacheEvict(allEntried=true) 删除缓存
@CacheConfig 配置缓存
Spring缓存与mybatis缓存的区别:
mybatis缓存的事原始数据
Spring缓存缓存的事业务层数据,是在原始数据上进行加工过的
#######################################################
拦截器是DispatcherServlet到controller层之间拦截
Springboot优先使用拦截器(拦截器是SpringMVC的组件,控制层之前拦截)
拦截器有两个方法,返回boolean类型代表拦截成功与否
preHandle(到达之前拦截)
postHandler 回去的时候拦截
@Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
//克隆一个新的ObjectMapper实例
ObjectMapper om = new ObjectMapper();
//添加对jdk8日期类型的支持,需要在pom文件中引入jackson-datatype-jsr310,spring-boot-starter-web已默认引入
om.registerModule(new JavaTimeModule());
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//必须进行以下配置,否则会出现java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx的异常
//详见:https://knife.blog.youkuaiyun.com/article/details/122427607
//配置在序列化时,将类型序列化到json中,所以能在反序列化时,自动根据类型进行反序列化
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//配置对于无法反序列化的字段,不报错。比如有getter方法,但无setter方法或者无对应字段,则在反序列化时,不报错
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式:JdkSerializationRedisSerializer)
return new Jackson2JsonRedisSerializer<>(om, Object.class);
}
@Bean
public RedisTemplate<String, Object> RedisTemplate(RedisSerializer<Object> serializer) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
return redisTemplate;
}
8.8Vue3
MVVM框架:vue React,Angular,
单页面应用——Single Page Application: SPA 就是一个首页,其余的全部放到js中
使用前提:安装node.js(帮助编译vue 里面的组件)
Node.js 是一个开源、跨平台的 JavaScript 运行时环境,它让开发者能够使用 JavaScript 来编写服务器端的代码。它的核心价值在于打破了 JavaScript 只能运行在浏览器中的限制。拥有极其丰富的开源生态系统 (npm)
npm: node package manager包管理器 与node捆绑安装,是仓库
cnpm:阿里开发的包管理器
pnpm:国外
yarn:包管理器
配置:
vite.config.js
//没啥用因为有默认的
server:{
host:"localhost",
port: 5000,
//是否运行项目自动打开窗口
open: true
}
问题:
单例类可以依赖注入其他单例类。让我解释一下原因:
1.单例模式与依赖注入的兼容性
单例类的特点:
-
整个应用中只有一个实例
-
生命周期由Spring容器管理
-
线程安全(在正确实现的情况下)
依赖注入的特点:
-
由Spring容器负责创建和管理Bean
-
自动解析和注入依赖关系
-
支持各种注入方式(构造函数、setter、字段)
2. 为什么单例可以注入单例
Spring容器的管理机制:
@Component // 默认是单例
public class ServiceA {
// ServiceA是单例
}
@Component // 默认是单例
public class ServiceB {
@Autowired
private ServiceA serviceA; // 可以注入另一个单例
// Spring会确保注入的是同一个实例
}
工作原理:
-
容器启动时:Spring创建所有单例Bean的实例
-
依赖解析:Spring分析Bean之间的依赖关系
-
注入过程:将已创建的单例实例注入到需要的地方
-
生命周期管理:所有单例共享相同的生命周期
3. 实际示例
// 数据访问层 - 单例
@Repository
public class UserDao {
public User findById(Long id) {
// 数据库查询逻辑
return user;
}
}
// 服务层 - 单例
@Service
public class UserService {
@Autowired
private UserDao userDao; // 注入单例的UserDao
public User getUser(Long id) {
return userDao.findById(id); // 使用注入的单例
}
}
// 控制层 - 单例
@Controller
public class UserController {
@Autowired
private UserService userService; // 注入单例的UserService
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id); // 使用注入的单例
}
}
4. 优势
-
资源共享:
@Service
public class CacheService {
private Map<String, Object> cache = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
}
@Service
public class BusinessService {
@Autowired
private CacheService cacheService; // 所有BusinessService实例共享同一个缓存
public void doSomething() {
cacheService.put("key", "value");
}
}
-
状态一致性:
-
所有地方使用的都是同一个实例
-
避免了状态不一致的问题
-
便于维护全局状态
5. 注意事项
线程安全:
@Service
public class CounterService {
private int count = 0;
// 需要考虑线程安全
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
避免循环依赖:
// 错误示例 - 循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
总结
单例类可以依赖注入其他单例类,因为:
-
Spring容器统一管理:所有单例Bean都由Spring容器创建和管理
-
生命周期一致:单例Bean具有相同的生命周期
-
资源共享:单例实例可以在整个应用中共享
-
依赖解析机制:Spring能正确解析和注入单例依赖
这是Spring框架的核心特性之一,使得组件之间的耦合度降低,同时保证了高效的资源共享。
1896

被折叠的 条评论
为什么被折叠?



