Spring框架与应用
核心优势:
- 快速开发: 通过大量的“起步依赖”(Starters),极大地简化了Maven/Gradle的依赖配置。
- 简化配置: 遵循“约定大于配置”的原则,提供了大量自动配置,让开发者可以专注于业务逻辑。
- 内嵌服务器: 内嵌了Tomcat、Jetty等服务器,无需部署WAR包,可以直接通过
java -jar
命令运行。 - 易于监控: 提供
spring-boot-starter-actuator
,可以轻松地对应用进行健康检查、指标收集等监控。
自动配置原理:
这是Spring Boot的魔法核心。
@SpringBootApplication
注解: 这个注解是入口,它其实是一个复合注解,其中最重要的一个是@EnableAutoConfiguration
。@EnableAutoConfiguration
注解: 它启用了自动配置功能。它会利用SpringFactoriesLoader
机制,去META-INF/spring.factories
文件中加载所有自动配置类(AutoConfiguration classes)。- 自动配置类: 这些类(例如
DataSourceAutoConfiguration
)本身就是普通的@Configuration
配置类,它们通过@ConditionalOnClass
、@ConditionalOnBean
、@ConditionalOnProperty
等条件注解来判断自己是否应该生效。 - 判断逻辑: 举个例子,
DataSourceAutoConfiguration
会检查classpath下是否存在DataSource.class
和JdbcTemplate.class
,如果存在,它就会尝试自动配置一个数据源(DataSource)的Bean。如果你自己定义了一个DataSource Bean,那么根据@ConditionalOnMissingBean
注解,Spring Boot的自动配置就会失效,转而使用你定义的Bean。
IOC (Inversion of Control) 控制反转:
- 是什么: IOC是一种设计思想,意思是将你设计好的对象(Bean)的创建和依赖关系的管理权,交由第三方容器(Spring IOC容器)来控制。最常见的实现方式是依赖注入(DI - Dependency Injection)。
- 解决了什么问题: 主要解决了代码间的强耦合问题。在没有IOC之前,一个类如果需要另一个类的实例,通常需要自己
new
一个出来。有了IOC,你只需要在类中声明需要哪个依赖,Spring容器会自动帮你“注入”进来,你无需关心它的创建过程。
AOP (Aspect-Oriented Programming) 面向切面编程:
- 是什么: AOP是一种编程思想,它允许开发者动态地将代码“横切”入现有的类和方法中,而无需修改原始代码。它关注的是那些分散在各个业务模块中的通用功能,如日志记录、事务管理等。
- 解决了什么问题: 主要解决了重复代码和逻辑分散的问题。通过AOP,我们可以将这些通用功能定义为一个“切面”(Aspect),然后定义在“何处”(Pointcut)以及“何时”(Advice)应用这个切面。
一个Bean从创建到销毁主要经历以下关键步骤:
- 实例化 (Instantiation): Spring容器根据配置通过反射创建Bean的实例。
- 属性填充 (Populate Properties): Spring容器为Bean的属性赋值。
- Aware接口调用: 如果Bean实现了
BeanNameAware
等接口,Spring会调用相应的方法。 - BeanPostProcessor (前置处理): 调用
postProcessBeforeInitialization
方法。 - 初始化 (Initialization): 调用
afterPropertiesSet
方法或init-method
。 - BeanPostProcessor (后置处理): 调用
postProcessAfterInitialization
方法。AOP代理通常在此步生成。 - Bean可用: Bean准备就绪,可以被使用了。
- 销毁 (Destruction): 当容器关闭时,调用
destroy
方法或destroy-method
。
#{}
(预编译处理):
#{}
会将传入的参数当做一个值,在SQL执行前,会替换为?
占位符。- 优点: 能够有效防止SQL注入。
- 使用场景: 绝大多数情况下都应该使用
#{}
。
${}
(字符串替换):
${}
会将传入的参数直接当做字符串拼接到SQL语句中。- 缺点: 存在严重的SQL注入风险。
- 使用场景: 仅在需要动态指定表名、列名、
order by
等场景下使用。
中间件与数据库
- String (字符串): 缓存用户信息、Session、分布式计数器、限流器。
- Hash (哈希): 缓存对象(如购物车),修改部分属性时无需序列化整个对象。
- List (列表): 简单的消息队列、文章列表、关注列表。
- Set (集合): 计算共同好友、共同关注(交集)、抽奖系统、黑白名单。
- ZSet (有序集合): 实现排行榜(积分榜、热销榜)、延时队列。
实现分布式锁的关键是利用Redis命令的原子性。最常用的命令是 SET key value NX PX milliseconds
。
NX
: 只在key不存在时才设置成功。PX milliseconds
: 设置key的过期时间,防止死锁。
基本步骤:
- 加锁: 客户端执行
SET lock_key random_value NX PX 30000
。random_value
用于标识客户端。 - 执行业务逻辑。
- 解锁: 为了防止误删,通过Lua脚本保证原子性地判断value是否匹配再删除。
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
- 缓存穿透:
现象: 查询一个数据库和缓存中都不存在的数据。
解决: 缓存空对象;使用布隆过滤器。 - 缓存击穿:
现象: 一个热点Key在缓存中失效的瞬间,大量并发请求涌入数据库。
解决: 使用互斥锁/分布式锁;热点数据永不过期。 - 缓存雪崩:
现象: 大量Key在同一时间集体失效,或Redis宕机。
解决: 过期时间加随机值;多级缓存;Redis集群高可用。
MongoDB是一个NoSQL文档型数据库,主要在以下场景中表现出色:
- 非结构化/半结构化数据: 数据结构不固定或经常变化(如用户标签、日志)。
- 高写入、高扩展性需求: 天然支持水平扩展(分片)。
- 快速迭代的业务: Schema-less特性使开发效率更高。
- 存储内嵌文档: 对于一对多的内嵌关系(如文章和评论),查询性能高,避免JOIN。
消息队列主要解决三大问题:
- 异步处理 (Asynchronous): 对于耗时的非核心操作,主流程将消息扔到MQ后立即返回,降低响应时间。
- 应用解耦 (Decoupling): 生产者和消费者通过MQ通信,互不知道对方存在,提高系统可维护性和扩展性。
- 流量削峰 (Traffic Shaping): 在高并发场景下(如秒杀),将请求先写入队列,后端服务平稳消费,保护下游系统。
微服务与分布式
特性 | Spring Cloud | Dubbo |
---|---|---|
定位 | 一站式微服务解决方案 | 一个高性能的RPC框架 |
通信方式 | 默认基于HTTP/RESTful | 默认基于RPC (Netty+TCP) |
集成度 | 与Spring生态无缝集成 | 现在也提供了很好的Spring Boot支持 |
生态 | 生态庞大,但可能存在版本兼容问题 | 由阿里开源,生态稳定,社区活跃 |
总结 | 像一个“全家桶”,相对“重” | 像一个“利器”,RPC性能更优 |
- 服务注册与发现 (Eureka / Nacos): 服务地址的注册与发现,是服务治理的基础。
- 服务调用 (OpenFeign): 声明式的HTTP客户端,让远程调用像调用本地方法一样。
- 负载均衡 (Ribbon / LoadBalancer): 将请求分发到服务的多个实例上,提高可用性。
- 服务网关 (Gateway): 所有微服务的统一入口,负责路由、认证、限流等。
- 配置中心 (Config / Nacos): 集中管理和动态刷新配置。
- 服务熔断与降级 (Hystrix / Sentinel): 防止单个服务故障导致整个系统雪崩。
Java核心与基础
`synchronized` vs `ReentrantLock`:
- 来源:
synchronized
是Java的关键字(JVM实现)。ReentrantLock
是一个类(JDK实现)。 - 锁的获取与释放:
synchronized
是隐式的,自动释放。ReentrantLock
需要手动在`try-finally`块中调用`lock()`和`unlock()`。 - 功能特性:
ReentrantLock
功能更强大,支持可中断等待、公平锁、绑定多个条件。
JVM运行时数据区:
- 线程私有区: 程序计数器、Java虚拟机栈、本地方法栈。
- 线程共享区: 堆 (Heap)、方法区 (Method Area)。JDK 8后用元空间实现方法区。
Java内存模型 (JMM):
是一个抽象概念,定义了多线程环境下变量的访问规则,保证可见性、原子性和有序性。规定所有变量都存储在主内存中,每个线程有自己的**工作内存**。`volatile`、`synchronized`等关键字就是围绕JMM来实现线程安全的。
区别:
- 安全性: HTTP是明文传输,不安全。HTTPS通过SSL/TLS协议对数据进行加密传输,是安全的。
- 端口: HTTP默认使用80端口,HTTPS默认使用443端口。
- 证书: HTTPS需要向CA申请数字证书来验证服务器的身份。
HTTPS工作原理(SSL/TLS握手):
- 客户端请求: 客户端发送支持的加密套件和随机数C1。
- 服务器响应: 服务器返回选择的加密套件、数字证书和随机数S1。
- 客户端验证与密钥生成: 客户端验证证书,生成随机数Pre-master Secret (PMS),并用**证书中的公钥**加密后发送给服务器。
- 服务器解密与会话密钥生成: 服务器用自己的私钥解密得到PMS。双方根据C1、S1、PMS生成一个**对称加密的会话密钥**。
- 加密通信: 后续的所有通信都使用这个会话密钥进行对称加密。
JDK 7及以前:
底层是数组 + 链表的结构。当发生哈希冲突时,新元素以链表的形式追加到数组的索引位置。
JDK 8及以后(重要优化):
底层是数组 + 链表 + 红黑树的结构。当同一个索引位置的链表长度达到阈值(默认为8),并且数组总长度也达到阈值(默认为64)时,这个链表就会被转换成红黑树,将查询时间复杂度从O(n)优化到O(log n)。
数据结构与算法
思路:
使用三个指针:`prev` (前一个节点), `curr` (当前节点), `next` (临时保存下一个节点)。遍历链表,在循环中不断将`curr`的`next`指针指向`prev`,然后整体向后移动指针。
Java代码实现:
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public class Solution {
public ListNode reverseList(ListNode head) {
// prev 指向反转后链表的头,初始为 null
ListNode prev = null;
// curr 指向当前要处理的节点
ListNode curr = head;
while (curr != null) {
// 1. 临时保存下一个节点
ListNode nextTemp = curr.next;
// 2. 将当前节点的 next 指向前一个节点
curr.next = prev;
// 3. 移动 prev 和 curr 指针
prev = curr;
curr = nextTemp;
}
// 循环结束后,prev 就是新的头节点
return prev;
}
}