嘿,朋友,我是Agnes。很高兴见到你!既然你点开了这篇文章,我猜你大概是个正在编程世界里摸索的新手,或者是一个想要系统梳理Spring Boot知识的开发者。别担心,Spring Boot这东西听起来高大上,其实剥开那些复杂的术语外壳,它的核心逻辑非常朴实,甚至可以说有点“懒人友好”。
今天我不打算给你堆砌晦涩的定义,咱们就像聊天一样,从为什么用Spring开始,一路聊到怎么动手写代码,再到那些老鸟才知道的实战技巧。我会把每个知识点掰开了、揉碎了讲,保证你能听得懂,而且记得住。
为什么我们要用Spring Boot?
在深入代码之前,先搞清楚一件事:Spring Boot存在的意义是什么?
如果你用过早期的Spring(也就是那个需要配置无数个XML文件的年代),你就会知道那种痛苦。你要配置Bean,要配置数据源,要配置Web容器……光是搭环境就能让人怀疑人生。
Spring Boot的出现,就是为了“约定优于配置”。它就像是一个贴心的管家,帮你把90%的基础配置都默认设置好了。你只需要关心业务逻辑,剩下的,Spring Boot会自己搞定。
举个例子:
- 传统的Spring MVC项目,你可能需要写几十个配置文件。
- 用Spring Boot,你只需要一个
application.yml,甚至不需要配置文件,全靠自动配置。
这就是为什么它是现在Java开发的主流。
第一个Spring Boot项目:Hello World
别急,咱们先从最基础的“Hello World”开始。这能帮你建立信心。
假设你已经安装好了JDK 17(Spring Boot 3.x推荐JDK 17及以上)和Maven,我们可以用Spring Initializr(https://start.spring.io)快速生成项目。
1. 项目结构简介
生成项目后,你会看到这样的结构:
src
├── main
│ ├── java
│ │ └── com.example.demo
│ │ └── DemoApplication.java // 启动类
│ │ ├── controller // 控制器层
│ │ │ └── HelloController.java
│ │ ├── service // 服务层
│ │ └── repository // 数据访问层
│ └── resources
│ └── application.yml // 配置文件
└── test
└── java
└── com.example.demo
└── DemoApplicationTests.java
2. 启动类:@SpringBootApplication
DemoApplication.java是入口,它长这样:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注意那个@SpringBootApplication注解,它是三个注解的组合:
@SpringBootConfiguration:表明这是一个配置类。@EnableAutoConfiguration:开启自动配置。@ComponentScan:扫描当前包及子包下的组件。
3. 第一个控制器
在controller包下创建一个HelloController.java:
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot! 欢迎来到Java后端开发的世界。";
}
}
@RestController相当于@Controller + @ResponseBody的合体,意味着这个类里的所有方法都会直接返回JSON或纯文本,而不是跳转页面。
@GetMapping("/hello")表示这个方法处理GET请求,路径是/hello。
启动项目,访问http://localhost:8080/hello,你会看到熟悉的文字。恭喜你,第一步迈出去了!
Spring Boot的核心注解全家桶
光会写Hello World还不够,你需要理解Spring Boot里那些无处不在的注解。这些注解是Spring Boot的“语法”,掌握了它们,你就掌握了Spring Boot的一半。
1. 控制反转(IoC)与依赖注入(DI)
Spring的核心思想是IoC,意思是“把对象的创建权交给Spring容器”。而DI是IoC的一种实现方式,Spring帮你把依赖的对象“注入”进来。
常用的注解有:
@Component:标记一个类为Spring管理的组件。@Service:标记业务逻辑层组件。@Repository:标记数据访问层组件(带异常翻译功能)。@Controller:标记控制层组件。@Autowired:自动注入依赖。
举个例子:
@Service
public class UserService {
public String getUserInfo() {
return "张三,28岁,程序员";
}
}
然后在Controller里注入:
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user")
public String getUser() {
return userService.getUserInfo();
}
}
这里@Autowired会自动找到UserService的实例注入进来。你不需要手动new,Spring容器会帮你搞定一切。
2. 配置类与属性注入
有时候你需要读取配置文件里的值,比如数据库连接、第三方API密钥等。
在application.yml里:
app:
name: MySpringBootApp
version: 1.0.0
api-key: abc123xyz
然后在Java类里注入:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String version;
@Value("${app.api-key}")
private String apiKey;
public void printConfig() {
System.out.println("应用名称:" + appName);
System.out.println("版本号:" + version);
System.out.println("API密钥:" + apiKey);
}
}
3. 条件注解:@ConditionalOn…
Spring Boot的自动配置之所以强大,是因为它可以根据条件来决定是否启用某个Bean。
比如,只有当classpath下有mysql-connector-java时,才会自动配置数据源。
你自己写代码时也可以用:
@Component
@ConditionalOnProperty(name = "app.debug", havingValue = "true")
public class DebugService {
public void log(String msg) {
System.err.println("[DEBUG] " + msg);
}
}
这样,只有在application.yml里设置了app.debug=true时,DebugService才会被创建。
Spring Boot与数据库交互:JPA与MyBatis
后端开发离不开数据库。Spring Boot支持两种主流的ORM框架:JPA(Spring Data JPA)和MyBatis。咱们今天重点讲JPA,因为它配置更简单,适合新手。
1. 引入依赖
在pom.xml里添加:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
这里用H2作为内存数据库,方便测试。实际项目中可以换成MySQL或PostgreSQL。
2. 配置数据源
在application.yml里:
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
ddl-auto: update表示自动根据实体类创建或更新表结构,非常适合开发阶段。
3. 定义实体类
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column(nullable = false, unique = true)
private String email;
private Integer age;
}
注意:这里用了Lombok的注解,@Data会自动生成getter/setter/toString等,@NoArgsConstructor和@AllArgsConstructor生成构造方法。如果你没用Lombok,需要手动写这些方法。
4. 定义Repository
import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 自定义查询方法,Spring Data JPA会根据方法名自动生成SQL
List<User> findByName(String name);
List<User> findByAgeGreaterThan(Integer age);
}
JpaRepository已经提供了基本的CRUD操作,你不需要写任何实现类。
5. 编写Service和Controller
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public List<User> getAllUsers() {
return userRepository.findAll();
}
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
public User createUser(User user) {
return userRepository.save(user);
}
public User updateUser(Long id, User user) {
User existing = userRepository.findById(id).orElse(null);
if (existing != null) {
existing.setName(user.getName());
existing.setEmail(user.getEmail());
existing.setAge(user.getAge());
return userRepository.save(existing);
}
return null;
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
Controller:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public List<User> getAll() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping
public User create(@RequestBody User user) {
return userService.createUser(user);
}
@PutMapping("/{id}")
public User update(@PathVariable Long id, @RequestBody User user) {
return userService.updateUser(id, user);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
userService.deleteUser(id);
}
}
这样,一个简单的用户管理REST API就完成了。你可以用Postman测试一下。
全局异常处理:让错误信息更友好
在实际项目中,异常处理非常重要。如果不处理,用户会看到一堆堆栈信息,既不友好也不安全。
Spring Boot提供了@ControllerAdvice和@ExceptionHandler来实现全局异常处理。
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public Map<String, Object> handleResourceNotFound(ResourceNotFoundException ex) {
Map<String, Object> body = new HashMap<>();
body.put("status", HttpStatus.NOT_FOUND.value());
body.put("error", "Not Found");
body.put("message", ex.getMessage());
return body;
}
@ExceptionHandler(Exception.class)
public Map<String, Object> handleGeneralException(Exception ex) {
Map<String, Object> body = new HashMap<>();
body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
body.put("error", "Internal Server Error");
body.put("message", "服务器内部错误,请稍后再试");
return body;
}
}
然后,你可以抛出自定义异常:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
在Service里这样用:
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("用户不存在,ID: " + id));
}
这样,当用户ID不存在时,返回给前端的JSON会是:
{
"status": 404,
"error": "Not Found",
"message": "用户不存在,ID: 123"
}
非常清晰。
拦截器与过滤器:请求的生命周期
有时候你需要在所有请求前后做一些统一处理,比如记录日志、检查登录状态、计算响应时间等。这时候就要用到拦截器(Interceptor)和过滤器(Filter)。
1. 过滤器(Filter)
Filter是Servlet规范的一部分,它在请求到达Servlet之前就生效。
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
@Order(1)
public class RequestLogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
long startTime = System.currentTimeMillis();
System.out.println("[Filter] 请求开始: " + request.getMethod() + " " + request.getRequestURI());
chain.doFilter(request, response); // 放行请求
long duration = System.currentTimeMillis() - startTime;
System.out.println("[Filter] 请求结束: " + duration + "ms");
}
}
2. 拦截器(Interceptor)
Interceptor是Spring MVC的组件,它比Filter更“Spring”,可以访问Spring容器的Bean。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("未授权,请登录后再访问");
return false; // 拦截请求
}
// 这里可以验证token是否有效
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 请求处理后,响应返回前
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
// 整个请求完成后,通常用于清理资源
}
}
然后注册拦截器:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public WebConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 只对/api/下的路径生效
.excludePathPatterns("/api/public/**"); // 排除不需要鉴权的路径
}
}
事务管理:保证数据一致性
数据库操作往往涉及多个步骤,比如转账:从A账户扣钱,向B账户加钱。这两个操作要么同时成功,要么同时失败。这就需要事务管理。
Spring Boot用@Transactional注解来简化事务管理。
@Service
public class TransferService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transfer(Long fromId, Long toId, Double amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
if (from.getBalance() < amount) {
throw new InsufficientBalanceException("余额不足");
}
from.setBalance(from.getBalance() - amount);
to.setBalance(to.getBalance() + amount);
accountRepository.save(from);
accountRepository.save(to);
}
}
加了@Transactional后,Spring会在方法执行前开启事务,方法正常结束则提交事务,如果抛出异常则回滚事务。
注意:@Transactional只能用在public方法上,而且必须是通过Spring代理调用的,自己调用自己不会生效。
缓存:提升性能的秘密武器
频繁查询数据库会拖慢系统速度。Spring Boot提供了简单的缓存抽象,让你轻松加上缓存功能。
1. 开启缓存
在启动类加@EnableCaching:
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. 使用缓存注解
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Cacheable(value = "products", key = "#id")
public Product getProduct(Long id) {
System.out.println("查询数据库...");
return productRepository.findById(id).orElse(null);
}
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
}
- `@Cache
