Spring Boot实战指南
Spring Boot是一个快速开发Java应用的框架,它通过自动配置和约定优于配置的理念,大大简化了Spring应用的开发过程。本文将带你从零开始学习Spring Boot,掌握核心概念和最佳实践。
Spring Boot介绍
什么是Spring Boot
Spring Boot是由Pivotal团队开发的一个基于Spring框架的项目,旨在简化Spring应用的初始化、配置和开发过程。它主要有以下特点:
- 无需XML配置:告别繁琐的XML配置文件
- 内嵌Web服务器:可以打包成可独立运行的JAR文件
- 自动配置:根据依赖自动配置应用
- 起步依赖:简化Maven/Gradle依赖配置
- 生产就绪特性:如监控、健康检查等
- 丰富的插件生态:满足各种开发需求
Spring Boot与Spring Framework的关系
Spring Boot建立在Spring Framework之上,提供了更快速和便捷的开发体验:
- Spring Boot使用了Spring的核心功能,如依赖注入、AOP等
- Spring Boot通过自动配置简化了Spring的配置
- Spring Boot是一种使用Spring Framework的方式,而不是替代品
适用场景
Spring Boot适用于以下场景:
- 微服务开发
- RESTful API服务
- Web应用开发
- 批处理应用
- 企业级应用开发
- 云原生应用开发
开发环境搭建
必备工具
- JDK 8+ (推荐JDK 11或JDK 17)
- Maven 3.2+ 或 Gradle 7.x+
- 集成开发环境(IDE):推荐IntelliJ IDEA或Spring Tool Suite
- Git (可选,用于版本控制)
Windows环境配置
安装JDK:
- 下载并安装JDK
- 设置JAVA_HOME环境变量
- 添加%JAVA_HOME%\bin到PATH
安装Maven:
- 下载Maven二进制包
- 解压到合适目录
- 设置M2_HOME环境变量
- 添加%M2_HOME%\bin到PATH
安装IDE:
- 下载并安装IntelliJ IDEA
- 安装Spring Boot插件
macOS环境配置
可以使用Homebrew简化安装:
# 安装JDK
brew install openjdk@17
# 安装Maven
brew install maven
# 安装IntelliJ IDEA
brew install --cask intellij-idea
Linux环境配置
Ubuntu/Debian系统:
# 安装JDK
sudo apt update
sudo apt install openjdk-17-jdk
# 安装Maven
sudo apt install maven
# 安装IDEA (通过官网下载或使用snap)
sudo snap install intellij-idea-community --classic
创建第一个Spring Boot应用
使用Spring Initializr
- 访问 Spring Initializr
- 选择项目类型:Maven/Gradle
- 选择语言:Java
- 选择Spring Boot版本:如2.7.x或3.x
- 填写项目元数据:
- Group: com.example
- Artifact: demo
- Name: demo
- Description: Demo project for Spring Boot
- Package name: com.example.demo
- 添加依赖:如Spring Web, Spring Data JPA, H2 Database
- 生成并下载项目
使用命令行创建
# 使用Spring Boot CLI
spring init --dependencies=web,data-jpa,h2 --build=maven my-project
# 使用Maven命令
mvn archetype:generate -DgroupId=com.example -DartifactId=my-project -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
使用IDE创建
IntelliJ IDEA:
- File -> New -> Project
- 选择Spring Initializr
- 填写项目信息
- 选择依赖
- 点击Finish
Eclipse/STS:
- File -> New -> Spring Starter Project
- 填写项目信息
- 选择依赖
- 点击Finish
项目结构解析
创建好的典型Spring Boot项目结构:
my-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── demo/
│ │ │ ├── DemoApplication.java # 主启动类
│ │ │ ├── controller/ # 控制器
│ │ │ ├── service/ # 服务层
│ │ │ ├── repository/ # 数据访问层
│ │ │ ├── entity/ # 实体类
│ │ │ └── config/ # 配置类
│ │ └── resources/
│ │ ├── application.properties # 应用配置文件
│ │ ├── static/ # 静态资源
│ │ └── templates/ # 模板文件
│ └── test/ # 测试代码
├── pom.xml # Maven配置
└── README.md # 项目说明
启动类解析
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
注解是一个复合注解,包含:
@Configuration
:标识配置类@EnableAutoConfiguration
:启用自动配置@ComponentScan
:组件扫描
Spring Boot核心特性
自动配置
Spring Boot根据添加的依赖自动配置应用程序,无需手动配置。例如,添加spring-boot-starter-web
依赖后,自动配置Web相关组件。
禁用特定自动配置:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DemoApplication {
// ...
}
外部化配置
Spring Boot允许使用属性文件、YAML文件、环境变量和命令行参数配置应用。配置优先级从高到低:
- 命令行参数
- 操作系统环境变量
- 特定profile的application属性
- application.properties/yml
配置示例:
application.properties:
# 服务器配置
server.port=8080
server.servlet.context-path=/api
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
application.yml:
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
配置属性绑定
可以将配置属性绑定到Java对象:
@Component
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
private String host;
private int port;
private String username;
private String password;
// getters and setters
}
application.properties:
mail.host=smtp.gmail.com
mail.port=587
mail.username=user@example.com
mail.password=secret
Profile配置
使用不同的Profile可以针对不同环境加载不同的配置:
application-dev.properties:
logging.level.root=DEBUG
spring.datasource.url=jdbc:h2:mem:testdb
application-prod.properties:
logging.level.root=WARN
spring.datasource.url=jdbc:mysql://prod-server:3306/mydb
激活Profile:
spring.profiles.active=dev
或者使用命令行:
java -jar myapp.jar --spring.profiles.active=prod
Web应用开发
创建REST API
使用@RestController
创建RESTful API:
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity.created(URI.create("/api/users/" + savedUser.getId()))
.body(savedUser);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.delete(id)) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
请求参数处理
Spring Boot提供多种方式处理请求参数:
@GetMapping("/search")
public List<User> searchUsers(
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return userService.search(name, page, size);
}
异常处理
全局异常处理:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
System.currentTimeMillis()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"发生未知错误",
System.currentTimeMillis()
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
文件上传
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
String fileName = storageService.store(file);
return ResponseEntity.ok("文件上传成功: " + fileName);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("文件上传失败: " + e.getMessage());
}
}
静态资源处理
Spring Boot默认从以下路径提供静态资源:
- /static
- /public
- /resources
- /META-INF/resources
例如,放置在src/main/resources/static/images/logo.png
的资源可通过http://localhost:8080/images/logo.png
访问。
配置CORS
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
数据访问
整合Spring Data JPA
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
创建实体类:
@Entity
@Table(name = "users")
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;
@Column(nullable = false)
private String password;
// getters and setters
}
创建Repository:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String name);
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword% OR u.email LIKE %:keyword%")
List<User> search(@Param("keyword") String keyword);
}
使用MyBatis
添加依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
创建Mapper接口:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users")
List<User> findAll();
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);
@Insert("INSERT INTO users(name, email, password) VALUES(#{name}, #{email}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET name = #{name}, email = #{email}, password = #{password} WHERE id = #{id}")
int update(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
int delete(Long id);
}
事务管理
Spring Boot默认开启事务管理,只需使用@Transactional
注解:
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public User registerUser(User user) {
// 检查邮箱是否已存在
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
throw new DuplicateEmailException("Email already exists");
}
// 加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
// 保存用户
return userRepository.save(user);
}
}
整合Redis缓存
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置Redis:
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
启用缓存:
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}
使用缓存:
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Cacheable(value = "users", key = "#id")
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
@Override
@CachePut(value = "users", key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
@Override
@CacheEvict(value = "users", key = "#id")
public boolean delete(Long id) {
if (userRepository.existsById(id)) {
userRepository.deleteById(id);
return true;
}
return false;
}
}
安全与认证
整合Spring Security
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
基本配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
JWT认证
添加依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JWT工具类:
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
final Date expiration = getClaimFromToken(token, Claims::getExpiration);
return expiration.before(new Date());
}
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
}
JWT过滤器:
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwt);
} catch (Exception e) {
// Token不合法或过期
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
测试
单元测试
使用JUnit 5和Mockito进行单元测试:
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserServiceImpl userService;
@Test
void findById_WhenUserExists_ReturnUser() {
// Arrange
Long userId = 1L;
User expectedUser = new User();
expectedUser.setId(userId);
expectedUser.setName("Test User");
when(userRepository.findById(userId)).thenReturn(Optional.of(expectedUser));
// Act
Optional<User> result = userService.findById(userId);
// Assert
assertTrue(result.isPresent());
assertEquals(expectedUser.getId(), result.get().getId());
assertEquals(expectedUser.getName(), result.get().getName());
verify(userRepository).findById(userId);
}
@Test
void findById_WhenUserDoesNotExist_ReturnEmpty() {
// Arrange
Long userId = 1L;
when(userRepository.findById(userId)).thenReturn(Optional.empty());
// Act
Optional<User> result = userService.findById(userId);
// Assert
assertFalse(result.isPresent());
verify(userRepository).findById(userId);
}
}
集成测试
使用@SpringBootTest
进行集成测试:
@SpringBootTest
class UserControllerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setup() {
userRepository.deleteAll();
}
@Test
void createUser_WhenValidInput_ReturnCreatedUser() {
// Arrange
User user = new User();
user.setName("Test User");
user.setEmail("test@example.com");
user.setPassword("password");
// Act
ResponseEntity<User> response = restTemplate.postForEntity(
"/api/users",
user,
User.class);
// Assert
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertNotNull(response.getBody());
assertNotNull(response.getBody().getId());
assertEquals(user.getName(), response.getBody().getName());
assertEquals(user.getEmail(), response.getBody().getEmail());
// Verify in database
Optional<User> savedUser = userRepository.findById(response.getBody().getId());
assertTrue(savedUser.isPresent());
}
}
Web层测试
使用@WebMvcTest
测试控制器:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
@Test
void getUserById_WhenUserExists_ReturnUser() throws Exception {
// Arrange
Long userId = 1L;
User user = new User();
user.setId(userId);
user.setName("Test User");
user.setEmail("test@example.com");
when(userService.findById(userId)).thenReturn(Optional.of(user));
// Act & Assert
mockMvc.perform(get("/api/users/{id}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(userId))
.andExpect(jsonPath("$.name").value("Test User"))
.andExpect(jsonPath("$.email").value("test@example.com"));
verify(userService).findById(userId);
}
@Test
void getUserById_WhenUserDoesNotExist_Return404() throws Exception {
// Arrange
Long userId = 1L;
when(userService.findById(userId)).thenReturn(Optional.empty());
// Act & Assert
mockMvc.perform(get("/api/users/{id}", userId))
.andExpect(status().isNotFound());
verify(userService).findById(userId);
}
}
部署与监控
构建可执行JAR
使用Maven构建:
mvn clean package
运行应用:
java -jar target/myapp-0.0.1-SNAPSHOT.jar
Docker部署
创建Dockerfile:
FROM openjdk:17-jdk-slim
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
构建和运行Docker镜像:
docker build -t myapp .
docker run -p 8080:8080 myapp
应用监控
添加Actuator依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置Actuator端点:
# 启用所有端点
management.endpoints.web.exposure.include=*
# 显示详细健康信息
management.endpoint.health.show-details=always
访问监控端点:
- http://localhost:8080/actuator/health - 健康状态
- http://localhost:8080/actuator/info - 应用信息
- http://localhost:8080/actuator/metrics - 指标
- http://localhost:8080/actuator/env - 环境变量
添加Prometheus和Grafana监控
添加依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
配置Prometheus端点:
management.endpoints.web.exposure.include=prometheus,health,info
Spring Boot最佳实践
代码组织
- 遵循分层架构:控制器、服务、数据访问
- 使用合适的包结构:按功能或领域划分
- 保持代码简洁,每个类和方法只做一件事
配置管理
- 区分不同环境的配置:dev, test, prod
- 敏感信息使用环境变量或外部配置
- 配置参数化,避免硬编码
错误处理
- 集中处理异常,提供统一的错误响应格式
- 记录详细的错误日志
- 区分用户错误和系统错误
性能优化
- 使用缓存减少数据库访问
- 合理配置连接池
- 异步处理长时间运行的任务
- 定期进行性能测试和优化
安全措施
- 所有用户输入进行验证
- 敏感数据加密
- 定期更新依赖,修复安全漏洞
- 使用HTTPS保护通信
常见问题与解决方案
自动配置不生效
问题: 添加了依赖但自动配置不生效
解决方案:
- 检查依赖是否正确添加
- 确认是否有排除自动配置的代码
- 查看启动日志,了解自动配置情况
数据库连接问题
问题: 无法连接到数据库
解决方案:
- 检查数据库连接参数
- 确认数据库服务是否运行
- 测试网络连接
- 检查用户权限
内存溢出
问题: 应用运行时出现内存溢出
解决方案:
- 增加JVM堆内存:
java -Xmx512m -jar app.jar
- 排查内存泄漏
- 优化大对象使用
循环依赖
问题: Bean循环依赖导致应用启动失败
解决方案:
- 重新设计类结构,消除循环
- 使用
@Lazy
注解延迟初始化 - 使用setter注入代替构造器注入
总结
Spring Boot极大地简化了Spring应用的开发流程,通过自动配置、起步依赖和内嵌服务器等特性,让开发者能够快速构建企业级应用。本文介绍了Spring Boot的基本概念、核心特性以及实际开发中的最佳实践,希望能够帮助你在项目中更好地使用Spring Boot。
随着经验的积累,你可以进一步探索Spring Cloud微服务生态系统,将应用拆分为多个协同工作的微服务,构建更加灵活和可扩展的系统架构。
如有任何Spring Boot相关问题或讨论,欢迎联系我进行交流。