嘿,朋友,我是Agnes。看到这一串标题,你是不是感觉头都大了?Spring Framework,这玩意儿就像是一座大山,爬上去容易,但真正在山上建房子、住下来,还得有点真功夫。别怕,今天咱们不聊那些枯燥的官方文档,我把这些年踩过的坑、悟出的道,全都揉碎了,给你讲讲Spring到底是个啥,以及怎么用它打怪升级。
一、Spring IoC容器:别自己管对象,让Spring来当“大管家”
首先,咱们得搞清楚Spring到底在干什么。很多人一上来就记注解,@Autowired、@Component……记了一堆,但心里还是懵的。其实,Spring的核心就两个东西:IoC(控制反转)和DI(依赖注入)。
1.1 什么是IoC?
想象一下,你要做一道菜(比如西红柿炒鸡蛋)。在没有Spring之前,你需要自己买西红柿、自己买鸡蛋、自己找锅、自己找铲子。这就叫耦合——你的代码(做菜的人)和所有资源(西红柿、鸡蛋、锅)紧紧绑在一起。如果西红柿没了,你就做不了菜了。
IoC是什么呢?IoC就是:你别买了,我来给你准备。 你只需要告诉Spring:“我要做菜,我需要西红柿、鸡蛋和锅。”Spring就会把这些东西准备好,塞到你手里。这个过程就叫“控制反转”——控制权从你自己手里,反转到了Spring容器手里。
1.2 Bean:Spring里的“零件”
在Spring眼里,你写的每一个类,都可以是一个Bean。Bean就是一个被Spring容器管理的对象。你可以简单理解为:Bean就是Spring帮你创建、组装、管理的Java对象。
1.3 如何定义一个Bean?
最简单的办法,就是用注解。比如,我们写一个服务类:
package com.example.service;
import org.springframework.stereotype.Service;
@Service // 告诉Spring:我是一个Bean,帮我管起来
public class UserService {
public String sayHello() {
return "Hello, Spring IoC!";
}
}
就这么简单,加一个@Service,Spring就会在启动的时候,自动把这个UserService创建成一个对象,放进它的“仓库”(容器)里。
1.4 如何获取Bean?
你可能会问:“Spring帮我管起来,那我怎么用它呢?”别急,我们有两种主要方式:
方式一:构造函数注入(推荐)
package com.example.controller;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final UserService userService;
// Spring会自动找到UserService这个Bean,注入进来
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/hello")
public String hello() {
return userService.sayHello();
}
}
方式二:字段注入(省事,但不推荐)
@Autowired
private UserService userService; // 直接写在字段上,方便,但测试麻烦
1.5 Bean的作用域
Spring默认创建的Bean是单例(Singleton)的,也就是说,整个应用只有一个实例。但有时候,你可能想要每个请求都创建一个新对象,这时候就要用到作用域:
- Singleton(单例):整个应用只有一个实例(默认)。
- Prototype(原型):每次请求都创建一个新实例。
- Request:每个HTTP请求创建一个实例(Web环境)。
- Session:每个HTTP会话创建一个实例(Web环境)。
@Service
@Scope("prototype") // 每次请求都创建新的UserService实例
public class UserService {
// ...
}
二、Spring Bean管理:从手动到自动,从XML到注解
刚学Spring的时候,很多人还在用XML配置Bean。说实话,那是上世纪的事儿了。现在,注解驱动是绝对的主流。但理解Bean的生命周期,对你调试问题非常有帮助。
2.1 Bean的生命周期
一个Bean从出生到死亡,经历这些阶段:
- 实例化(Instantiation):Spring通过构造函数或工厂方法创建Bean对象。
- 属性赋值(Populate):Spring把依赖的Bean注入进来。
- 初始化(Initialization):调用
@PostConstruct标注的方法,或者实现InitializingBean接口的afterPropertiesSet()方法。 - 使用(Usage):Bean被正常使用。
- 销毁(Destruction):Spring容器关闭时,调用
@PreDestroy标注的方法,或者实现DisposableBean接口的destroy()方法。
看代码:
@Component
public class MyBean {
public MyBean() {
System.out.println("1. 实例化");
}
@Autowired
private AnotherBean anotherBean;
@PostConstruct
public void init() {
System.out.println("3. 初始化");
}
@PreDestroy
public void destroy() {
System.out.println("5. 销毁");
}
}
2.2 Bean的自动装配
Spring提供了三种自动装配方式:
- byType:根据类型自动注入。如果有多个同类型Bean,会报错。
- byName:根据名称自动注入。Bean的名字必须和注入点的变量名一致。
- constructor:通过构造函数自动注入。
// 指定装配方式
@Component
@Qualifier("userDaoImpl") // 如果有多个实现类,用这个名字区分
public class UserService {
@Autowired
private UserDao userDao; // Spring会根据类型找UserDao,如果有多个,就看名字
}
2.3 条件化Bean
有时候,你希望只在特定条件下才创建某个Bean。比如,在开发环境用Mock服务,在生产环境用真实服务。这时候,@Conditional就派上用场了。
@Component
@ConditionalOnProperty(name = "app.useMock", havingValue = "true")
public class MockUserService implements UserService {
// 只有当配置文件中 app.useMock=true 时,这个Bean才会被创建
}
三、Spring MVC核心原理:HTTP请求的“交通警察”
Spring MVC是Spring框架用于构建Web应用的核心模块。它的核心思想是前端控制器模式,即有一个中央处理器(DispatcherServlet)来统一调度所有的请求。
3.1 Spring MVC的执行流程
当一个HTTP请求进来,Spring MVC的处理流程如下:
- DispatcherServlet(前端控制器)接收请求。
- HandlerMapping(处理器映射器)查找处理这个请求的Controller。
- HandlerAdapter(处理器适配器)调用Controller的方法。
- Controller(控制器)执行业务逻辑,返回一个ModelAndView对象。
- ViewResolver(视图解析器)解析视图名,找到实际的视图。
- DispatcherServlet把模型数据渲染到视图中,返回给客户端。
简单说,就是:请求来了 -> 谁来处理? -> 谁来调用? -> 谁负责渲染? -> 返回结果。
3.2 入门Spring MVC
我们用一个最简单的例子来看看Spring MVC是怎么工作的。
@Controller // 声明这是一个控制器
public class HelloController {
// 处理GET请求,路径是/hello
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(Model model) {
model.addAttribute("message", "Hello, Spring MVC!"); // 把数据放进模型
return "hello"; // 返回视图名
}
}
然后,我们需要一个视图(比如JSP或者Thymeleaf模板):
<!-- hello.html (Thymeleaf) -->
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<p th:text="${message}">Message</p>
</body>
</html>
3.3 注解详解
- @Controller:标记这是一个控制器。
- @RestController:标记这是一个RESTful控制器,所有方法都返回数据(JSON),而不是视图。
- @RequestMapping:映射请求路径。
- @GetMapping:处理GET请求。
- @PostMapping:处理POST请求。
- @PutMapping:处理PUT请求。
- @DeleteMapping:处理DELETE请求。
- @RequestParam:获取请求参数。
- @PathVariable:获取路径变量。
- @RequestBody:获取请求体(JSON)。
- @ResponseBody:将返回值直接写入HTTP响应体(返回JSON)。
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 从路径中获取id,比如/users/123
return userService.findById(id);
}
@PostMapping
public User createUser(@RequestBody User user) {
// 从请求体中获取用户数据,比如JSON
return userService.save(user);
}
}
3.4 拦截器(Interceptor)
拦截器类似于Servlet的Filter,但它更细粒度,可以拦截到Controller的方法。
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 在Controller执行前调用
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false; // 拦截请求
}
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 在Controller执行后、视图渲染前调用
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 在视图渲染后调用,常用于清理资源
}
}
然后,在配置类中注册拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**") // 拦截/api/下的所有请求
.excludePathPatterns("/api/public/**"); // 排除/public/下的请求
}
}
四、Spring AOP切面编程:横切关注点的优雅处理
AOP(Aspect-Oriented Programming,面向切面编程)是Spring的另一个核心模块。它的目的是将那些与业务逻辑无关,但却为业务模块所共同调用的逻辑或责任(比如日志、事务、安全等)封装起来,减少对业务逻辑代码的干扰,提高系统的可维护性。
4.1 核心概念
- Aspect(切面):横切关注点的模块化,比如日志切面、事务切面。
- Join Point(连接点):程序执行过程中的某个点,比如方法调用、异常抛出。
- Pointcut(切点):匹配Join Point的表达式,决定哪些方法需要被切面处理。
- Advice(通知):切面在特定连接点执行的动作。
- Weaving(织入):将切面应用到目标对象,创建新的代理对象的过程。
4.2 通知类型
- @Before:在方法执行之前执行。
- @After:在方法执行之后执行,无论是否异常。
- @AfterReturning:在方法成功返回后执行。
- @AfterThrowing:在方法抛出异常后执行。
- @Around:包围方法执行,可以在方法前后都做处理。
4.3 实战:日志切面
我们写一个简单的日志切面,记录每个Controller方法的执行时间。
@Aspect // 声明这是一个切面
@Component
@Slf4j // Lombok的日志注解
public class LogAspect {
// 定义切点:所有在com.example.controller包下的方法
@Pointcut("execution(* com.example.controller.*.*(..))")
public void controllerPackage() {}
@Around("controllerPackage()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed(); // 执行目标方法
long executionTime = System.currentTimeMillis() - start;
log.info("{} method executed in {} ms",
joinPoint.getSignature().toShortString(),
executionTime);
return proceed;
}
}
4.4 实战:事务切面
Spring的@Transactional注解其实就是AOP的一个典型应用。我们来看看它是怎么工作的。
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
// 声明式事务:整个方法在同一个事务中执行
// 如果任何地方抛出异常,事务会回滚
@Transactional
public void createOrder(Long userId, Long productId, int quantity) {
// 1. 检查库存
inventoryService.checkInventory(productId, quantity);
// 2. 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
orderRepository.save(order);
// 3. 扣减库存
inventoryService.reduceInventory(productId, quantity);
// 如果这里抛出异常,1、2、3步都会回滚
if (quantity > 10) {
throw new RuntimeException("库存不足");
}
}
}
4.5 手动配置AOP(如果不用注解)
虽然注解很方便,但理解XML配置也有助于你理解底层原理。
<aop:config>
<aop:aspect id="logAspect" ref="logAspectBean">
<aop:pointcut id="controllerPackage"
expression="execution(* com.example.controller.*.*(..))"/>
<aop:around method="logExecutionTime" pointcut-ref="controllerPackage"/>
</aop:aspect>
</aop:config>
五、Spring Boot实战:让Spring变得简单
Spring Boot是Spring框架的“一键打包”解决方案,它通过自动配置和起步依赖,大大简化了Spring应用的开发。
5.1 为什么需要Spring Boot?
- 自动配置:Spring Boot会根据classpath上的依赖,自动配置Spring应用。比如,你引入了
spring-boot-starter-web,它会自动配置Spring MVC、嵌入式Tomcat等。 - 起步依赖:通过一个依赖,引入一组相关的依赖。比如
spring-boot-starter-data-jpa会引入Spring Data JPA、Hibernate、数据库驱动等。 - 内嵌服务器:Spring Boot内嵌了Tomcat、Jetty或Undertow,可以直接打成JAR包运行,不需要部署到外部服务器。
5.2 创建一个Spring Boot项目
方式一:使用Spring Initializr(推荐)
访问 https://start.spring.io/,选择项目依赖,生成项目,下载后导入IDE即可。
方式二:使用IDEA创建
File -> New -> Project -> Spring Initializr,选择依赖,点击Finish。
5.3 启动类
Spring Boot的启动类是一个入口,它必须位于根包下,这样Spring Boot才能扫描到所有的Bean。
@SpringBootApplication // 组合注解:@Configuration + @EnableAutoConfiguration + @ComponentScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
5.4 配置文件
Spring Boot支持两种配置文件格式:
- application.properties
- application.yml(推荐,更简洁)
# application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
thymeleaf:
cache: false
5.5 自动配置原理
Spring Boot的自动配置核心是@EnableAutoConfiguration注解,它会加载spring-boot-autoconfigure模块下的META-INF/spring.factories文件,根据条件注解(如@ConditionalOnClass、@ConditionalOnMissingBean等),自动配置Spring应用。
比如,当你引入了spring-boot-starter-web,Spring Boot会自动配置DispatcherServlet、ViewResolver等。
5.6 自定义Starter
如果公司业务特殊,可以封装自己的Starter,方便其他项目使用。
<!-- pom.xml -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
然后在META-INF/spring.factories中配置自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.MyAutoConfiguration
5.7 实战:一个完整的Spring Boot项目结构
”` my-project/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/ │ │ │
