Java 8 实战开发与 Spring Boot 学习笔记
侧边栏壁纸
  • 累计撰写 12 篇文章
  • 累计收到 1 条评论

Java 8 实战开发与 Spring Boot 学习笔记

ASN__
2026-03-21 / 0 评论 / 6 阅读 / 正在检测是否收录...

目录


第一部分: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 操作流程:

  1. 创建 Stream
  2. 中间操作(返回 Stream)
  3. 终端操作(产生结果)

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-service

2. 自动配置原理

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 自动配置流程

  1. Spring Boot 启动时加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  2. 根据条件注解(@ConditionalOnClass@ConditionalOnMissingBean 等)决定是否加载配置
  3. 自动配置类创建所需的 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.MySQL8Dialect

5. 安全认证

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=prod

8.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主应用类注解
@RestControllerREST 控制器
@RequestMapping请求映射
@GetMappingGET 请求映射
@PostMappingPOST 请求映射
@PutMappingPUT 请求映射
@DeleteMappingDELETE 请求映射
@PathVariable路径变量
@RequestParam请求参数
@RequestBody请求体
@Autowired自动装配
@Service服务层
@Repository数据访问层
@EntityJPA 实体
@Transactional事务
@Configuration配置类
@BeanBean 定义

B. 参考资源

0

评论

博主关闭了所有页面的评论