目录
第一部分:Java 8 核心特性
1. Lambda 表达式
1.1 什么是 Lambda 表达式
Lambda 表达式是 Java 8 引入的一种简洁的函数式编程语法,允许将函数作为方法参数传递。
语法格式:
(parameters) -> expression
或
(parameters) -> { statements; }1.2 Lambda 表达式示例
// 传统方式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
// Lambda 方式
Runnable r2 = () -> System.out.println("Hello World!");
// 带参数的 Lambda
Comparator<Integer> cmp1 = (a, b) -> a.compareTo(b);
// 代码块形式
Comparator<Integer> cmp2 = (a, b) -> {
System.out.println("Comparing: " + a + " and " + b);
return a.compareTo(b);
};1.3 变量作用域
Lambda 表达式可以访问外部变量,但外部变量必须是 final 或 effectively final 的:
String prefix = "Hello, ";
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 正确:prefix 是 effectively final
names.forEach(name -> System.out.println(prefix + name));
// 错误:不能修改外部变量
// prefix = "Hi, "; // 编译错误2. 函数式接口
2.1 函数式接口定义
函数式接口是只包含一个抽象方法的接口,使用 @FunctionalInterface 注解标记。
@FunctionalInterface
interface MyFunction {
void apply(String s);
}2.2 Java 8 内置函数式接口
| 接口 | 抽象方法 | 描述 |
|---|---|---|
Predicate<T> | boolean test(T t) | 断言型接口 |
Function<T, R> | R apply(T t) | 函数型接口 |
Consumer<T> | void accept(T t) | 消费型接口 |
Supplier<T> | T get() | 供给型接口 |
2.3 内置函数式接口示例
// Predicate:判断
Predicate<String> isLongerThan5 = s -> s.length() > 5;
System.out.println(isLongerThan5.test("Hello")); // false
// Function:转换
Function<String, Integer> stringToLength = s -> s.length();
System.out.println(stringToLength.apply("Hello")); // 5
// Consumer:消费
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello World");
// Supplier:供给
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println(randomSupplier.get());2.4 方法引用
方法引用是 Lambda 表达式的简写形式:
// 静态方法引用
Function<String, Integer> parseInt = Integer::parseInt;
// 实例方法引用
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(System.out::println);
// 对象方法引用
String str = "Hello";
Predicate<String> startsWith = str::startsWith;
// 构造方法引用
Supplier<List<String>> listSupplier = ArrayList::new;3. Stream API
3.1 Stream 概述
Stream 是 Java 8 中处理集合的关键抽象概念,用于对集合进行函数式操作。
Stream 操作流程:
- 创建 Stream
- 中间操作(返回 Stream)
- 终端操作(产生结果)
3.2 创建 Stream
// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
// 使用 Stream.of
Stream<String> stream3 = Stream.of("a", "b", "c");
// 无限流
Stream<Integer> stream4 = Stream.iterate(0, n -> n + 2); // 偶数
Stream<Double> stream5 = Stream.generate(Math::random);3.3 中间操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// filter:过滤
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
// map:转换
numbers.stream()
.map(n -> n * n)
.forEach(System.out::println);
// sorted:排序
numbers.stream()
.sorted((a, b) -> b - a)
.forEach(System.out::println);
// distinct:去重
List<Integer> duplicates = Arrays.asList(1, 2, 2, 3, 3, 3);
duplicates.stream()
.distinct()
.forEach(System.out::println);
// limit:限制数量
numbers.stream()
.limit(5)
.forEach(System.out::println);3.4 终端操作
// collect:收集
List<String> words = Arrays.asList("apple", "banana", "cherry");
List<String> result = words.stream()
.filter(w -> w.length() > 5)
.collect(Collectors.toList());
// count:计数
long count = words.stream().count();
// reduce:归约
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
// forEach:遍历
words.stream().forEach(System.out::println);
// anyMatch / allMatch / noneMatch
boolean hasLongWord = words.stream().anyMatch(w -> w.length() > 10);
boolean allShort = words.stream().allMatch(w -> w.length() < 10);
// findFirst / findAny
Optional<String> first = words.stream().findFirst();
Optional<String> any = words.stream().findAny();3.5 Collectors 工具类
// 转 List
List<String> list = words.stream().collect(Collectors.toList());
// 转 Set
Set<String> set = words.stream().collect(Collectors.toSet());
// 转 Map
Map<Integer, String> map = words.stream()
.collect(Collectors.toMap(String::length, w -> w));
// 分组
Map<Integer, List<String>> groupByLength = words.stream()
.collect(Collectors.groupingBy(String::length));
// 分区
Map<Boolean, List<String>> partition = words.stream()
.collect(Collectors.partitioningBy(w -> w.length() > 5));
// 统计
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Avg: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());
// 连接字符串
String joined = words.stream().collect(Collectors.joining(", "));3.6 并行流
// 顺序流
long start = System.currentTimeMillis();
numbers.stream().forEach(n -> {
try { Thread.sleep(10); } catch (InterruptedException e) {}
});
System.out.println("Sequential: " + (System.currentTimeMillis() - start) + "ms");
// 并行流
start = System.currentTimeMillis();
numbers.parallelStream().forEach(n -> {
try { Thread.sleep(10); } catch (InterruptedException e) {}
});
System.out.println("Parallel: " + (System.currentTimeMillis() - start) + "ms");4. Optional 类
4.1 Optional 概述
Optional 是一个容器对象,用于优雅地处理空值(NullPointerException)。
4.2 创建 Optional
// 创建非空 Optional
Optional<String> opt1 = Optional.of("Hello");
// 创建允许空的 Optional
Optional<String> opt2 = Optional.ofNullable(null);
// 创建空 Optional
Optional<String> opt3 = Optional.empty();4.3 Optional 常用方法
Optional<String> optional = Optional.of("Hello");
// isPresent:判断是否有值
if (optional.isPresent()) {
System.out.println(optional.get());
}
// ifPresent:如果有值则执行
optional.ifPresent(s -> System.out.println(s));
// orElse:如果为空则返回默认值
String result1 = optional.orElse("Default");
// orElseGet:如果为空则调用供给函数
String result2 = optional.orElseGet(() -> "Default");
// orElseThrow:如果为空则抛出异常
String result3 = optional.orElseThrow(IllegalArgumentException::new);
// map:转换
Optional<Integer> length = optional.map(String::length);
// flatMap:扁平转换
Optional<String> upper = optional.flatMap(s -> Optional.of(s.toUpperCase()));
// filter:过滤
Optional<String> filtered = optional.filter(s -> s.length() > 5);4.4 Optional 最佳实践
// 传统方式
String name = user != null ? user.getName() : "Unknown";
// Optional 方式
String name = Optional.ofNullable(user)
.map(User::getName)
.orElse("Unknown");
// 多层嵌套
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");5. 日期时间 API
5.1 新日期时间 API 概述
Java 8 引入了全新的日期时间 API,位于 java.time 包下。
| 类 | 描述 |
|---|---|
LocalDate | 日期(年-月-日) |
LocalTime | 时间(时:分:秒) |
LocalDateTime | 日期时间 |
ZonedDateTime | 带时区的日期时间 |
Instant | 时间戳 |
Duration | 时间间隔(秒/纳秒) |
Period | 日期间隔(年/月/日) |
DateTimeFormatter | 日期格式化 |
5.2 LocalDate / LocalTime / LocalDateTime
// LocalDate
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2024, 1, 1);
LocalDate parsedDate = LocalDate.parse("2024-01-01");
int year = date.getYear();
Month month = date.getMonth();
int dayOfMonth = date.getDayOfMonth();
DayOfWeek dayOfWeek = date.getDayOfWeek();
// 日期运算
LocalDate tomorrow = today.plusDays(1);
LocalDate lastWeek = today.minusWeeks(1);
// LocalTime
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(14, 30, 0);
LocalTime parsedTime = LocalTime.parse("14:30:00");
// LocalDateTime
LocalDateTime nowDateTime = LocalDateTime.now();
LocalDateTime dateTime = LocalDateTime.of(2024, 1, 1, 14, 30);5.3 Instant 与 Duration / Period
// Instant:时间戳
Instant now = Instant.now();
Instant timestamp = Instant.ofEpochMilli(System.currentTimeMillis());
// Duration:时间间隔
Instant start = Instant.now();
Thread.sleep(1000);
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("Seconds: " + duration.getSeconds());
System.out.println("Millis: " + duration.toMillis());
// Period:日期间隔
LocalDate date1 = LocalDate.of(2024, 1, 1);
LocalDate date2 = LocalDate.of(2024, 12, 31);
Period period = Period.between(date1, date2);
System.out.println("Years: " + period.getYears());
System.out.println("Months: " + period.getMonths());
System.out.println("Days: " + period.getDays());5.4 日期格式化与解析
// 预定义格式
DateTimeFormatter isoDate = DateTimeFormatter.ISO_LOCAL_DATE;
String dateStr = LocalDate.now().format(isoDate);
LocalDate date = LocalDate.parse(dateStr, isoDate);
// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = LocalDateTime.now().format(formatter);
LocalDateTime parsed = LocalDateTime.parse("2024-01-01 14:30:00", formatter);5.5 时区处理
// ZonedDateTime
ZonedDateTime nowBeijing = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime nowNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
// 时区转换
ZonedDateTime beijingTime = ZonedDateTime.of(2024, 1, 1, 14, 30, 0, 0, ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = beijingTime.withZoneSameInstant(ZoneId.of("America/New_York"));6. 接口默认方法与静态方法
6.1 默认方法
interface MyInterface {
void abstractMethod();
default void defaultMethod() {
System.out.println("Default method");
}
}
class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("Abstract method implementation");
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.abstractMethod();
obj.defaultMethod();
}
}6.2 静态方法
interface MathUtil {
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(MathUtil.add(1, 2));
System.out.println(MathUtil.multiply(3, 4));
}
}第二部分:Spring Boot 实战开发
1. Spring Boot 快速入门
1.1 什么是 Spring Boot
Spring Boot 是一个快速开发框架,基于 Spring 框架,简化了 Spring 应用的初始搭建和开发过程。
核心特性:
- 自动配置
- 独立运行(内嵌 Tomcat)
- 起步依赖(Starters)
- 生产级特性(监控、健康检查等)
1.2 创建第一个 Spring Boot 项目
使用 Spring Initializr 创建:
访问 https://start.spring.io/,选择:
- Project: Maven 或 Gradle
- Language: Java
- Spring Boot: 最新稳定版本
- Dependencies: Spring Web
pom.xml 示例:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0.0</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>1.3 主类与启动
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);
}
}1.4 第一个 REST 控制器
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
}1.5 配置文件 application.yml
server:
port: 8080
servlet:
context-path: /demo
spring:
application:
name: demo-service2. 自动配置原理
2.1 @SpringBootApplication 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
}包含三个核心注解:
@SpringBootConfiguration:标记为配置类@EnableAutoConfiguration:启用自动配置@ComponentScan:组件扫描
2.2 自动配置流程
- Spring Boot 启动时加载
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - 根据条件注解(
@ConditionalOnClass、@ConditionalOnMissingBean等)决定是否加载配置 - 自动配置类创建所需的 Bean
2.3 自定义自动配置
创建自定义自动配置类:
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties.getMessage());
}
}配置属性类:
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
private String message = "default message";
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}3. Web 开发
3.1 RESTful API 开发
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.findAll();
return ResponseEntity.ok(users);
}
@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(@Valid @RequestBody User user) {
User createdUser = userService.save(user);
URI location = URI.create("/api/users/" + createdUser.getId());
return ResponseEntity.created(location).body(createdUser);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User user) {
return userService.findById(id)
.map(existingUser -> {
user.setId(id);
User updatedUser = userService.save(user);
return ResponseEntity.ok(updatedUser);
})
.orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
if (userService.existsById(id)) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}3.2 参数验证
public class User {
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
@Email(message = "Invalid email format")
@NotBlank(message = "Email is required")
private String email;
@Min(value = 18, message = "Age must be at least 18")
private Integer age;
// getters and setters
}3.3 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Validation failed", errors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"An unexpected error occurred"
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}3.4 文件上传
@RestController
@RequestMapping("/api/files")
public class FileController {
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
String fileName = file.getOriginalFilename();
Path path = Paths.get("uploads/" + fileName);
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return ResponseEntity.ok("File uploaded successfully: " + fileName);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Failed to upload file: " + e.getMessage());
}
}
@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
Path path = Paths.get("uploads/" + filename);
try {
Resource resource = new UrlResource(path.toUri());
if (resource.exists() && resource.isReadable()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (MalformedURLException e) {
return ResponseEntity.badRequest().build();
}
}
}4. 数据访问
4.1 Spring Data JPA
实体类:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// getters and setters
}Repository 接口:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByNameContainingIgnoreCase(String name);
boolean existsByEmail(String email);
@Query("SELECT u FROM User u WHERE u.createdAt > :since")
List<User> findUsersCreatedAfter(@Param("since") LocalDateTime since);
@Modifying
@Query("UPDATE User u SET u.password = :password WHERE u.id = :id")
int updatePassword(@Param("id") Long id, @Param("password") String password);
}Service 层:
@Service
@Transactional
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<User> findAll() {
return userRepository.findAll();
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
if (userRepository.existsByEmail(user.getEmail())) {
throw new RuntimeException("Email already exists");
}
return userRepository.save(user);
}
public void deleteById(Long id) {
userRepository.deleteById(id);
}
public boolean existsById(Long id) {
return userRepository.existsById(id);
}
}4.2 分页与排序
@GetMapping("/page")
public ResponseEntity<Page<User>> getUsersPage(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "ASC") Sort.Direction direction) {
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
Page<User> usersPage = userRepository.findAll(pageable);
return ResponseEntity.ok(usersPage);
}4.3 配置数据源(application.yml)
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect5. 安全认证
5.1 Spring Security 基础
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>安全配置:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**", "/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}5.2 JWT 认证
JWT 工具类:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(String username, List<String> roles) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(username)
.claim("roles", roles)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}JWT 过滤器:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
@Autowired
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
if (jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
}6. 日志与监控
6.1 日志配置
application.yml:
logging:
level:
root: INFO
com.example.demo: DEBUG
org.springframework.web: INFO
org.hibernate.SQL: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
max-size: 10MB
max-history: 30代码中使用日志:
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User save(User user) {
logger.info("Saving user: {}", user.getEmail());
try {
User savedUser = userRepository.save(user);
logger.debug("User saved successfully with id: {}", savedUser.getId());
return savedUser;
} catch (Exception e) {
logger.error("Error saving user", e);
throw e;
}
}
}6.2 Spring Boot Actuator
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,loggers
endpoint:
health:
show-details: always访问端点:
/actuator/health- 健康检查/actuator/info- 应用信息/actuator/metrics- 指标/actuator/loggers- 日志配置
7. 测试
7.1 单元测试
@SpringBootTest
class UserServiceTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService;
@Test
void testFindAll() {
User user1 = new User();
user1.setId(1L);
user1.setName("Alice");
User user2 = new User();
user2.setId(2L);
user2.setName("Bob");
when(userRepository.findAll()).thenReturn(Arrays.asList(user1, user2));
List<User> users = userService.findAll();
assertEquals(2, users.size());
verify(userRepository, times(1)).findAll();
}
}7.2 Web 层测试
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetAllUsers() throws Exception {
User user = new User();
user.setId(1L);
user.setName("Alice");
when(userService.findAll()).thenReturn(Arrays.asList(user));
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(1)))
.andExpect(jsonPath("$[0].name").value("Alice"));
}
}8. 部署与运维
8.1 打包成 JAR
Maven 命令:
mvn clean package运行 JAR:
java -jar target/demo-1.0.0.jar指定配置文件运行:
java -jar target/demo-1.0.0.jar --spring.profiles.active=prod8.2 Docker 部署
Dockerfile:
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/mydb
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: password
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: mydb
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
volumes:
mysql-data:附录
A. 常用注解速查表
| 注解 | 描述 |
|---|---|
@SpringBootApplication | 主应用类注解 |
@RestController | REST 控制器 |
@RequestMapping | 请求映射 |
@GetMapping | GET 请求映射 |
@PostMapping | POST 请求映射 |
@PutMapping | PUT 请求映射 |
@DeleteMapping | DELETE 请求映射 |
@PathVariable | 路径变量 |
@RequestParam | 请求参数 |
@RequestBody | 请求体 |
@Autowired | 自动装配 |
@Service | 服务层 |
@Repository | 数据访问层 |
@Entity | JPA 实体 |
@Transactional | 事务 |
@Configuration | 配置类 |
@Bean | Bean 定义 |
评论