从 .NET 到 Java 的转型指南:详细学习路线与实践建议

作者:百锦再@新空间日期:9/30/2025

在这里插入图片描述

文章目录

    • 第一部分:转型背景与核心差异分析
      • 1.1 为什么需要从 .NET 转型到 Java
        • 1.2 .NET 与 Java 核心架构差异
          • 1.2.1 运行时环境对比
            * 1.2.2 内存管理机制
        • 1.3 心态调整与学习策略
          • 1.3.1 相似性利用
            * 1.3.2 差异性重视
    • 第二部分:Java 语言基础深入学习
      • 2.1 Java 语法核心概念
          • 2.1.1 基本数据类型与包装类
            * 2.1.2 字符串处理
        • 2.2 面向对象编程深入
          • 2.2.1 类与继承
            * 2.2.2 访问控制与封装
        • 2.3 异常处理机制
        • 2.4 集合框架
    • 第三部分:Java 生态系统与工具链
      • 3.1 构建工具:Maven 与 Gradle
          • 3.1.1 Maven 详细配置
            * 3.1.2 Gradle 构建脚本
        • 3.2 开发工具与环境配置
          • 3.2.1 IDE 选择与配置
            * 3.2.2 版本控制集成
        • 3.3 测试框架
          • 3.3.1 JUnit 测试
            * 3.3.2 Mockito 模拟测试
    • 第四部分:Spring 框架深入学习
      • 4.1 Spring Core 核心概念
          • 4.1.1 依赖注入与控制反转
            * 4.1.2 AOP 面向切面编程
        • 4.2 Spring Boot 自动化配置
          • 4.2.1 Spring Boot 应用启动
            * 4.2.2 应用配置文件
        • 4.3 Spring MVC Web 开发
          • 4.3.1 RESTful API 开发
    • 第五部分:数据库与持久层技术
      • 5.1 JPA 与 Hibernate
          • 5.1.1 实体类映射
            * 5.1.2 Repository 数据访问层
        • 5.2 数据库迁移与版本控制
          • 5.2.1 Flyway 数据库迁移
            * 5.2.2 Flyway 配置
    • 第六部分:高级主题与最佳实践
      • 6.1 性能优化与缓存
          • 6.1.1 Redis 缓存集成
        • 6.2 安全与认证授权
          • 6.2.1 Spring Security 配置
        • 6.3 微服务与分布式系统
          • 6.3.1 Spring Cloud 微服务配置
    • 第七部分:部署与运维
      • 7.1 Docker 容器化部署
          • 7.1.1 Dockerfile 配置
            * 7.1.2 Docker Compose 配置
        • 7.2 监控与日志
          • 7.2.1 Spring Boot Actuator 配置
            * 7.2.2 自定义健康检查
    • 第八部分:学习路线与持续提升
      • 8.1 分阶段学习计划
          • 阶段一:基础入门(1-2个月)
            * 阶段二:Spring 框架(2-3个月)
            * 阶段三:高级特性(2-3个月)
            * 阶段四:生产实践(持续)
        • 8.2 持续学习资源
          • 官方文档
            * 在线课程
            * 实践项目
        • 8.3 社区参与
          • 技术社区
            * 持续提升建议
    • 总结

在这里插入图片描述

第一部分:转型背景与核心差异分析

1.1 为什么需要从 .NET 转型到 Java

在当前的技术环境中,从 .NET 转型到 Java 通常基于以下考虑:

  1. 跨平台需求增加:Java 的"一次编写,到处运行"特性在云原生和容器化环境中具有优势
  2. 生态系统丰富性:Java 拥有庞大的开源生态系统和社区支持
  3. 成本考量:Java 开源技术栈可以降低许可成本
  4. 人才市场因素:Java 开发者在全球范围内更为普及
  5. 企业级应用成熟度:Java 在企业级应用和大规模系统中有着深厚的积累

1.2 .NET 与 Java 核心架构差异

1.2.1 运行时环境对比

.NET CLR vs JVM

1// .NET 中的类型系统示例
2public class Person
3{
4    public string Name { get; set; }
5    public int Age { get; set; }
6    
7    public virtual void Display()
8    {
9        Console.WriteLine($"Name: {Name}, Age: {Age}");
10    }
11}
12
1// Java 中的对应实现
2public class Person {
3    private String name;
4    private int age;
5    
6    public Person() {}
7    
8    public String getName() { return name; }
9    public void setName(String name) { this.name = name; }
10    
11    public int getAge() { return age; }
12    public void setAge(int age) { this.age = age; }
13    
14    public void display() {
15        System.out.println("Name: " + name + ", Age: " + age);
16    }
17}
18

主要差异

  • Java 中没有属性语法,使用 getter/setter 方法
  • Java 方法默认是虚方法(virtual),而 C# 需要显式声明
  • Java 包机制与 .NET 命名空间有相似性但实现不同
1.2.2 内存管理机制

.NET GC vs Java GC

1// .NET 中的资源管理
2public class ResourceHandler : IDisposable
3{
4    private bool disposed = false;
5    
6    public void ProcessData()
7    {
8        if (disposed)
9            throw new ObjectDisposedException(nameof(ResourceHandler));
10        
11        // 处理逻辑
12    }
13    
14    public void Dispose()
15    {
16        Dispose(true);
17        GC.SuppressFinalize(this);
18    }
19    
20    protected virtual void Dispose(bool disposing)
21    {
22        if (!disposed)
23        {
24            if (disposing)
25            {
26                // 释放托管资源
27            }
28            // 释放非托管资源
29            disposed = true;
30        }
31    }
32    
33    ~ResourceHandler()
34    {
35        Dispose(false);
36    }
37}
38
1// Java 中的资源管理
2public class ResourceHandler implements AutoCloseable {
3    private boolean closed = false;
4    
5    public void processData() {
6        if (closed) {
7            throw new IllegalStateException("ResourceHandler is closed");
8        }
9        // 处理逻辑
10    }
11    
12    @Override
13    public void close() {
14        if (!closed) {
15            // 释放资源
16            closed = true;
17        }
18    }
19    
20    // Java 7+ 的 try-with-resources
21    public static void main(String[] args) {
22        try (ResourceHandler handler = new ResourceHandler()) {
23            handler.processData();
24        } catch (Exception e) {
25            e.printStackTrace();
26        }
27    }
28}
29

1.3 心态调整与学习策略

1.3.1 相似性利用
  • 面向对象编程概念相通
  • 设计模式应用基本一致
  • 软件开发原则通用
1.3.2 差异性重视
  • 平台特性差异
  • 生态系统工具链
  • 部署和运维方式

第二部分:Java 语言基础深入学习

2.1 Java 语法核心概念

2.1.1 基本数据类型与包装类
1// Java 基本数据类型与包装类
2public class DataTypesExample {
3    public static void main(String[] args) {
4        // 基本数据类型
5        byte byteValue = 127;
6        short shortValue = 32767;
7        int intValue = 2147483647;
8        long longValue = 9223372036854775807L; // 注意 L 后缀
9        float floatValue = 3.14f; // 注意 f 后缀
10        double doubleValue = 3.141592653589793;
11        char charValue = 'A';
12        boolean booleanValue = true;
13        
14        // 对应的包装类
15        Byte byteObj = byteValue;
16        Short shortObj = shortValue;
17        Integer intObj = intValue;
18        Long longObj = longValue;
19        Float floatObj = floatValue;
20        Double doubleObj = doubleValue;
21        Character charObj = charValue;
22        Boolean booleanObj = booleanValue;
23        
24        // 自动装箱和拆箱
25        Integer autoBoxed = 100; // 自动装箱
26        int autoUnboxed = autoBoxed; // 自动拆箱
27        
28        // 与 .NET 对比:Java 有明确的原始类型和包装类型区分
29    }
30}
31
2.1.2 字符串处理
1public class StringHandling {
2    public static void main(String[] args) {
3        // 字符串创建
4        String str1 = "Hello";
5        String str2 = new String("World");
6        
7        // 字符串不可变性
8        String original = "Java";
9        String modified = original.concat(" Programming");
10        System.out.println(original); // 输出: Java
11        System.out.println(modified); // 输出: Java Programming
12        
13        // StringBuilder 用于可变字符串操作
14        StringBuilder sb = new StringBuilder();
15        sb.append("Hello");
16        sb.append(" ");
17        sb.append("World");
18        String result = sb.toString();
19        System.out.println(result); // 输出: Hello World
20        
21        // StringBuffer 线程安全版本
22        StringBuffer stringBuffer = new StringBuffer();
23        stringBuffer.append("Thread");
24        stringBuffer.append("Safe");
25        
26        // 与 C# 对比:Java 字符串也是不可变的,但 Java 有 StringBuilder/StringBuffer
27    }
28}
29

2.2 面向对象编程深入

2.2.1 类与继承
1// 基类
2public abstract class Animal {
3    protected String name;
4    protected int age;
5    
6    public Animal(String name, int age) {
7        this.name = name;
8        this.age = age;
9    }
10    
11    // 抽象方法
12    public abstract void makeSound();
13    
14    // 具体方法
15    public void sleep() {
16        System.out.println(name + " is sleeping");
17    }
18    
19    // final 方法,不能被子类重写
20    public final void breathe() {
21        System.out.println(name + " is breathing");
22    }
23}
24
25// 接口
26public interface Pet {
27    void play();
28    String getOwner();
29}
30
31// 继承与实现
32public class Dog extends Animal implements Pet {
33    private String owner;
34    
35    public Dog(String name, int age, String owner) {
36        super(name, age); // 调用父类构造函数
37        this.owner = owner;
38    }
39    
40    @Override
41    public void makeSound() {
42        System.out.println("Woof! Woof!");
43    }
44    
45    @Override
46    public void play() {
47        System.out.println(name + " is playing with " + owner);
48    }
49    
50    @Override
51    public String getOwner() {
52        return owner;
53    }
54    
55    // 静态方法
56    public static void describe() {
57        System.out.println("Dogs are loyal animals");
58    }
59}
60
2.2.2 访问控制与封装
1public class AccessModifiersExample {
2    // private - 仅当前类可见
3    private String privateField = "private";
4    
5    // default (package-private) - 同包可见
6    String defaultField = "default";
7    
8    // protected - 同包和子类可见
9    protected String protectedField = "protected";
10    
11    // public - 所有类可见
12    public String publicField = "public";
13    
14    // getter 和 setter 方法
15    public String getPrivateField() {
16        return privateField;
17    }
18    
19    public void setPrivateField(String value) {
20        this.privateField = value;
21    }
22}
23

2.3 异常处理机制

1public class ExceptionHandling {
2    
3    // 检查型异常
4    public void readFile(String filename) throws IOException {
5        BufferedReader reader = null;
6        try {
7            reader = new BufferedReader(new FileReader(filename));
8            String line;
9            while ((line = reader.readLine()) != null) {
10                System.out.println(line);
11            }
12        } catch (FileNotFoundException e) {
13            System.err.println("File not found: " + filename);
14            throw e; // 重新抛出异常
15        } catch (IOException e) {
16            System.err.println("Error reading file: " + e.getMessage());
17            throw e;
18        } finally {
19            // 确保资源被释放
20            if (reader != null) {
21                try {
22                    reader.close();
23                } catch (IOException e) {
24                    System.err.println("Error closing reader: " + e.getMessage());
25                }
26            }
27        }
28    }
29    
30    // 使用 try-with-resources (Java 7+)
31    public void readFileModern(String filename) throws IOException {
32        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
33            String line;
34            while ((line = reader.readLine()) != null) {
35                System.out.println(line);
36            }
37        } // 自动调用 reader.close()
38    }
39    
40    // 非检查型异常
41    public void divideNumbers(int a, int b) {
42        if (b == 0) {
43            throw new IllegalArgumentException("Divisor cannot be zero");
44        }
45        int result = a / b;
46        System.out.println("Result: " + result);
47    }
48    
49    // 自定义异常
50    public static class CustomException extends Exception {
51        public CustomException(String message) {
52            super(message);
53        }
54        
55        public CustomException(String message, Throwable cause) {
56            super(message, cause);
57        }
58    }
59}
60

2.4 集合框架

1import java.util.*;
2import java.util.stream.Collectors;
3
4public class CollectionExamples {
5    
6    public void listExamples() {
7        // ArrayList - 类似 C# List<T>
8        List<String> arrayList = new ArrayList<>();
9        arrayList.add("Apple");
10        arrayList.add("Banana");
11        arrayList.add("Orange");
12        
13        // LinkedList
14        List<String> linkedList = new LinkedList<>();
15        linkedList.add("First");
16        linkedList.add("Second");
17        
18        // 遍历方式
19        for (String fruit : arrayList) {
20            System.out.println(fruit);
21        }
22        
23        // 使用迭代器
24        Iterator<String> iterator = arrayList.iterator();
25        while (iterator.hasNext()) {
26            System.out.println(iterator.next());
27        }
28        
29        // Java 8+ Stream API
30        arrayList.stream()
31                .filter(f -> f.startsWith("A"))
32                .forEach(System.out::println);
33    }
34    
35    public void setExamples() {
36        // HashSet - 无序,不允许重复
37        Set<String> hashSet = new HashSet<>();
38        hashSet.add("Apple");
39        hashSet.add("Banana");
40        hashSet.add("Apple"); // 不会被添加
41        
42        // TreeSet - 有序
43        Set<String> treeSet = new TreeSet<>();
44        treeSet.add("Orange");
45        treeSet.add("Apple");
46        treeSet.add("Banana");
47        // 输出顺序: Apple, Banana, Orange
48    }
49    
50    public void mapExamples() {
51        // HashMap - 类似 C# Dictionary<TKey, TValue>
52        Map<String, Integer> hashMap = new HashMap<>();
53        hashMap.put("John", 25);
54        hashMap.put("Jane", 30);
55        hashMap.put("Bob", 35);
56        
57        // 获取值
58        Integer age = hashMap.get("John");
59        
60        // 遍历
61        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
62            System.out.println(entry.getKey() + ": " + entry.getValue());
63        }
64        
65        // Java 8+ 方式
66        hashMap.forEach((key, value) -> System.out.println(key + ": " + value));
67        
68        // TreeMap - 按键排序
69        Map<String, Integer> treeMap = new TreeMap<>();
70        treeMap.put("Orange", 5);
71        treeMap.put("Apple", 3);
72        treeMap.put("Banana", 7);
73        // 按键顺序: Apple, Banana, Orange
74    }
75    
76    public void streamExamples() {
77        List<String> names = Arrays.asList("John", "Jane", "Bob", "Alice", "Charlie");
78        
79        // Stream 操作
80        List<String> result = names.stream()
81                .filter(name -> name.length() > 4)
82                .map(String::toUpperCase)
83                .sorted()
84                .collect(Collectors.toList());
85        
86        System.out.println(result); // [ALICE, CHARLIE]
87        
88        // 分组
89        Map<Integer, List<String>> groupedByLength = names.stream()
90                .collect(Collectors.groupingBy(String::length));
91        
92        System.out.println(groupedByLength);
93    }
94}
95

第三部分:Java 生态系统与工具链

3.1 构建工具:Maven 与 Gradle

3.1.1 Maven 详细配置
1<!-- pom.xml 示例 -->
2<project xmlns="http://maven.apache.org/POM/4.0.0"
3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
5         http://maven.apache.org/xsd/maven-4.0.0.xsd">
6    <modelVersion>4.0.0</modelVersion>
7    
8    <!-- 项目坐标 -->
9    <groupId>com.example</groupId>
10    <artifactId>my-java-app</artifactId>
11    <version>1.0.0</version>
12    <packaging>jar</packaging>
13    
14    <properties>
15        <maven.compiler.source>11</maven.compiler.source>
16        <maven.compiler.target>11</maven.compiler.target>
17        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18        <spring.version>5.3.9</spring.version>
19    </properties>
20    
21    <dependencies>
22        <!-- Spring Framework -->
23        <dependency>
24            <groupId>org.springframework</groupId>
25            <artifactId>spring-context</artifactId>
26            <version>${spring.version}</version>
27        </dependency>
28        
29        <!-- 测试依赖 -->
30        <dependency>
31            <groupId>junit</groupId>
32            <artifactId>junit</artifactId>
33            <version>4.13.2</version>
34            <scope>test</scope>
35        </dependency>
36        
37        <!-- 日志 -->
38        <dependency>
39            <groupId>org.slf4j</groupId>
40            <artifactId>slf4j-api</artifactId>
41            <version>1.7.32</version>
42        </dependency>
43    </dependencies>
44    
45    <build>
46        <plugins>
47            <!-- 编译器插件 -->
48            <plugin>
49                <groupId>org.apache.maven.plugins</groupId>
50                <artifactId>maven-compiler-plugin</artifactId>
51                <version>3.8.1</version>
52                <configuration>
53                    <source>11</source>
54                    <target>11</target>
55                </configuration>
56            </plugin>
57            
58            <!-- 打包插件 -->
59            <plugin>
60                <groupId>org.apache.maven.plugins</groupId>
61                <artifactId>maven-jar-plugin</artifactId>
62                <version>3.2.0</version>
63                <configuration>
64                    <archive>
65                        <manifest>
66                            <mainClass>com.example.MainApplication</mainClass>
67                        </manifest>
68                    </archive>
69                </configuration>
70            </plugin>
71        </plugins>
72    </build>
73</project>
74
3.1.2 Gradle 构建脚本
1// build.gradle 示例
2plugins {
3    id 'java'
4    id 'application'
5}
6
7group = 'com.example'
8version = '1.0.0'
9sourceCompatibility = '11'
10
11repositories {
12    mavenCentral()
13}
14
15dependencies {
16    implementation 'org.springframework:spring-context:5.3.9'
17    implementation 'org.slf4j:slf4j-api:1.7.32'
18    
19    testImplementation 'junit:junit:4.13.2'
20}
21
22application {
23    mainClassName = 'com.example.MainApplication'
24}
25
26// 自定义任务
27task createStartScripts(type: CreateStartScripts) {
28    mainClass = 'com.example.MainApplication'
29    applicationName = 'myapp'
30    outputDir = new File(project.buildDir, 'scripts')
31    classpath = jar.outputs.files + project.configurations.runtimeClasspath
32}
33
34// 测试配置
35test {
36    useJUnit()
37    testLogging {
38        events "passed", "skipped", "failed"
39        exceptionFormat "full"
40    }
41}
42

3.2 开发工具与环境配置

3.2.1 IDE 选择与配置

IntelliJ IDEA 推荐配置

  • 安装 Lombok 插件
  • 配置 Code Style 符合团队规范
  • 启用 Annotation Processors
  • 配置 Debug 和 Hot Swap

Eclipse 配置

  • 安装 Spring Tools Suite
  • 配置 Build Path
  • 设置 Formatter 和 Clean Up
3.2.2 版本控制集成
1// Git 版本控制最佳实践示例
2public class VersionControlExample {
3    /**
4     * 功能:用户注册
5     * 作者:张三
6     * 日期:2023-10-01
7     * 版本:1.0
8     */
9    public void registerUser(String username, String password) {
10        // 参数验证
11        if (username == null || username.trim().isEmpty()) {
12            throw new IllegalArgumentException("用户名不能为空");
13        }
14        
15        if (password == null || password.length() < 6) {
16            throw new IllegalArgumentException("密码长度至少6位");
17        }
18        
19        // 业务逻辑
20        // ...
21    }
22    
23    /**
24     * 功能:用户登录
25     * 修改:李四 - 2023-10-02 - 添加记住登录状态功能
26     */
27    public boolean loginUser(String username, String password, boolean rememberMe) {
28        // 登录逻辑
29        return true;
30    }
31}
32

3.3 测试框架

3.3.1 JUnit 测试
1import org.junit.*;
2import static org.junit.Assert.*;
3
4public class CalculatorTest {
5    
6    private Calculator calculator;
7    
8    @BeforeClass
9    public static void setUpClass() {
10        // 在所有测试方法之前执行一次
11        System.out.println("测试类初始化");
12    }
13    
14    @AfterClass
15    public static void tearDownClass() {
16        // 在所有测试方法之后执行一次
17        System.out.println("测试类清理");
18    }
19    
20    @Before
21    public void setUp() {
22        // 在每个测试方法之前执行
23        calculator = new Calculator();
24    }
25    
26    @After
27    public void tearDown() {
28        // 在每个测试方法之后执行
29        calculator = null;
30    }
31    
32    @Test
33    public void testAdd() {
34        // 准备
35        int a = 5;
36        int b = 3;
37        int expected = 8;
38        
39        // 执行
40        int actual = calculator.add(a, b);
41        
42        // 验证
43        assertEquals("加法计算错误", expected, actual);
44    }
45    
46    @Test
47    public void testDivide() {
48        // 测试正常除法
49        assertEquals(2, calculator.divide(6, 3));
50    }
51    
52    @Test(expected = IllegalArgumentException.class)
53    public void testDivideByZero() {
54        // 测试除零异常
55        calculator.divide(5, 0);
56    }
57    
58    @Test
59    @Ignore("尚未实现")
60    public void testAdvancedFeature() {
61        // 被忽略的测试
62    }
63    
64    // 参数化测试
65    @RunWith(Parameterized.class)
66    public static class ParameterizedTest {
67        private int input;
68        private int expected;
69        private Calculator calculator = new Calculator();
70        
71        public ParameterizedTest(int input, int expected) {
72            this.input = input;
73            this.expected = expected;
74        }
75        
76        @Parameterized.Parameters
77        public static Collection<Object[]> data() {
78            return Arrays.asList(new Object[][] {
79                {0, 0}, {1, 1}, {2, 4}, {3, 9}
80            });
81        }
82        
83        @Test
84        public void testSquare() {
85            assertEquals(expected, calculator.square(input));
86        }
87    }
88}
89
90// 被测试的类
91class Calculator {
92    public int add(int a, int b) {
93        return a + b;
94    }
95    
96    public int divide(int a, int b) {
97        if (b == 0) {
98            throw new IllegalArgumentException("除数不能为零");
99        }
100        return a / b;
101    }
102    
103    public int square(int x) {
104        return x * x;
105    }
106}
107
3.3.2 Mockito 模拟测试
1import org.junit.Test;
2import org.junit.runner.RunWith;
3import org.mockito.*;
4import org.mockito.junit.MockitoJUnitRunner;
5import static org.mockito.Mockito.*;
6
7@RunWith(MockitoJUnitRunner.class)
8public class UserServiceTest {
9    
10    @Mock
11    private UserRepository userRepository;
12    
13    @Mock
14    private EmailService emailService;
15    
16    @InjectMocks
17    private UserService userService;
18    
19    @Test
20    public void testRegisterUser() {
21        // 准备模拟数据
22        User user = new User("[email protected]", "John Doe");
23        when(userRepository.save(any(User.class))).thenReturn(user);
24        doNothing().when(emailService).sendWelcomeEmail(anyString());
25        
26        // 执行测试
27        User registeredUser = userService.registerUser("[email protected]", "John Doe");
28        
29        // 验证行为
30        verify(userRepository, times(1)).save(any(User.class));
31        verify(emailService, times(1)).sendWelcomeEmail("[email protected]");
32        
33        // 验证结果
34        assertNotNull(registeredUser);
35        assertEquals("[email protected]", registeredUser.getEmail());
36    }
37    
38    @Test
39    public void testRegisterUserWithExistingEmail() {
40        // 模拟已存在的用户
41        when(userRepository.findByEmail("[email protected]"))
42            .thenReturn(new User("[email protected]", "Existing User"));
43        
44        // 验证异常
45        try {
46            userService.registerUser("[email protected]", "New User");
47            fail("应该抛出异常");
48        } catch (IllegalArgumentException e) {
49            assertEquals("邮箱已存在", e.getMessage());
50        }
51        
52        // 验证保存方法没有被调用
53        verify(userRepository, never()).save(any(User.class));
54    }
55    
56    @Captor
57    private ArgumentCaptor<User> userCaptor;
58    
59    @Test
60    public void testUserRegistrationDetails() {
61        // 执行测试
62        userService.registerUser("[email protected]", "Test User");
63        
64        // 捕获参数并验证
65        verify(userRepository).save(userCaptor.capture());
66        User capturedUser = userCaptor.getValue();
67        
68        assertEquals("[email protected]", capturedUser.getEmail());
69        assertEquals("Test User", capturedUser.getName());
70        assertNotNull(capturedUser.getRegistrationDate());
71    }
72}
73
74// 相关类定义
75class User {
76    private String email;
77    private String name;
78    private Date registrationDate;
79    
80    public User(String email, String name) {
81        this.email = email;
82        this.name = name;
83        this.registrationDate = new Date();
84    }
85    
86    // getters and setters
87}
88
89interface UserRepository {
90    User save(User user);
91    User findByEmail(String email);
92}
93
94interface EmailService {
95    void sendWelcomeEmail(String email);
96}
97
98class UserService {
99    private UserRepository userRepository;
100    private EmailService emailService;
101    
102    public User registerUser(String email, String name) {
103        // 检查邮箱是否已存在
104        User existing = userRepository.findByEmail(email);
105        if (existing != null) {
106            throw new IllegalArgumentException("邮箱已存在");
107        }
108        
109        // 创建新用户
110        User newUser = new User(email, name);
111        User savedUser = userRepository.save(newUser);
112        
113        // 发送欢迎邮件
114        emailService.sendWelcomeEmail(email);
115        
116        return savedUser;
117    }
118}
119

第四部分:Spring 框架深入学习

4.1 Spring Core 核心概念

4.1.1 依赖注入与控制反转
1// 配置类方式
2@Configuration
3@ComponentScan("com.example")
4@PropertySource("classpath:application.properties")
5public class AppConfig {
6    
7    @Bean
8    @Profile("dev")
9    public DataSource devDataSource() {
10        return new EmbeddedDatabaseBuilder()
11                .setType(EmbeddedDatabaseType.H2)
12                .addScript("classpath:schema.sql")
13                .addScript("classpath:data.sql")
14                .build();
15    }
16    
17    @Bean
18    @Profile("prod")
19    public DataSource prodDataSource() {
20        HikariDataSource dataSource = new HikariDataSource();
21        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
22        dataSource.setUsername("user");
23        dataSource.setPassword("password");
24        return dataSource;
25    }
26    
27    @Bean
28    public PlatformTransactionManager transactionManager(DataSource dataSource) {
29        return new DataSourceTransactionManager(dataSource);
30    }
31}
32
33// 服务类
34@Service
35@Transactional
36public class UserService {
37    
38    private final UserRepository userRepository;
39    private final EmailService emailService;
40    
41    // 构造器注入(推荐)
42    @Autowired
43    public UserService(UserRepository userRepository, EmailService emailService) {
44        this.userRepository = userRepository;
45        this.emailService = emailService;
46    }
47    
48    public User registerUser(UserRegistrationDto dto) {
49        // 业务逻辑
50        User user = new User();
51        user.setEmail(dto.getEmail());
52        user.setName(dto.getName());
53        user.setPassword(encodePassword(dto.getPassword()));
54        
55        User savedUser = userRepository.save(user);
56        emailService.sendWelcomeEmail(savedUser.getEmail());
57        
58        return savedUser;
59    }
60    
61    private String encodePassword(String password) {
62        // 密码加密逻辑
63        return password; // 实际应该使用 BCrypt 等
64    }
65}
66
67// 数据访问层
68@Repository
69public class JpaUserRepository implements UserRepository {
70    
71    @PersistenceContext
72    private EntityManager entityManager;
73    
74    @Override
75    public User save(User user) {
76        if (user.getId() == null) {
77            entityManager.persist(user);
78            return user;
79        } else {
80            return entityManager.merge(user);
81        }
82    }
83    
84    @Override
85    public Optional<User> findByEmail(String email) {
86        TypedQuery<User> query = entityManager.createQuery(
87            "SELECT u FROM User u WHERE u.email = :email", User.class);
88        query.setParameter("email", email);
89        
90        try {
91            return Optional.of(query.getSingleResult());
92        } catch (NoResultException e) {
93            return Optional.empty();
94        }
95    }
96}
97
98// 邮件服务
99@Service
100public class EmailService {
101    
102    private final JavaMailSender mailSender;
103    private final TemplateEngine templateEngine;
104    
105    @Autowired
106    public EmailService(JavaMailSender mailSender, TemplateEngine templateEngine) {
107        this.mailSender = mailSender;
108        this.templateEngine = templateEngine;
109    }
110    
111    @Async
112    public void sendWelcomeEmail(String email) {
113        try {
114            MimeMessage message = mailSender.createMimeMessage();
115            MimeMessageHelper helper = new MimeMessageHelper(message, true);
116            
117            helper.setTo(email);
118            helper.setSubject("欢迎注册");
119            
120            Context context = new Context();
121            context.setVariable("email", email);
122            String htmlContent = templateEngine.process("welcome-email", context);
123            
124            helper.setText(htmlContent, true);
125            mailSender.send(message);
126            
127        } catch (Exception e) {
128            throw new RuntimeException("发送邮件失败", e);
129        }
130    }
131}
132
4.1.2 AOP 面向切面编程
1// 自定义注解
2@Target(ElementType.METHOD)
3@Retention(RetentionPolicy.RUNTIME)
4public @interface LogExecutionTime {
5}
6
7// 切面类
8@Aspect
9@Component
10public class LoggingAspect {
11    
12    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
13    
14    @Around("@annotation(LogExecutionTime)")
15    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
16        long start = System.currentTimeMillis();
17        
18        Object proceed = joinPoint.proceed();
19        
20        long executionTime = System.currentTimeMillis() - start;
21        
22        logger.info("{} executed in {} ms", 
23                   joinPoint.getSignature(), executionTime);
24        
25        return proceed;
26    }
27    
28    // 异常处理切面
29    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", 
30                   throwing = "ex")
31    public void logServiceException(Exception ex) {
32        logger.error("Service layer exception: {}", ex.getMessage(), ex);
33    }
34    
35    // 方法调用前后通知
36    @Before("execution(* com.example.service.UserService.*(..))")
37    public void logMethodCall(JoinPoint joinPoint) {
38        logger.debug("调用方法: {} with args: {}", 
39                    joinPoint.getSignature().getName(),
40                    Arrays.toString(joinPoint.getArgs()));
41    }
42}
43
44// 使用切面的服务
45@Service
46public class OrderService {
47    
48    @LogExecutionTime
49    public Order createOrder(OrderRequest request) {
50        // 业务逻辑
51        try {
52            Thread.sleep(100); // 模拟处理时间
53            return new Order();
54        } catch (InterruptedException e) {
55            Thread.currentThread().interrupt();
56            throw new RuntimeException(e);
57        }
58    }
59}
60

4.2 Spring Boot 自动化配置

4.2.1 Spring Boot 应用启动
1// 主应用类
2@SpringBootApplication
3@EnableAsync
4@EnableScheduling
5@EnableCaching
6@EnableTransactionManagement
7public class Application {
8    
9    private static final Logger logger = LoggerFactory.getLogger(Application.class);
10    
11    public static void main(String[] args) {
12        SpringApplication app = new SpringApplication(Application.class);
13        
14        // 自定义启动配置
15        app.setBannerMode(Banner.Mode.CONSOLE);
16        app.setLogStartupInfo(true);
17        
18        ConfigurableApplicationContext context = app.run(args);
19        
20        // 应用启动后执行
21        checkBeans(context);
22    }
23    
24    private static void checkBeans(ConfigurableApplicationContext context) {
25        logger.info("检查Spring Bean配置...");
26        
27        String[] beanNames = context.getBeanDefinitionNames();
28        Arrays.sort(beanNames);
29        
30        for (String beanName : beanNames) {
31            if (beanName.contains("service") || beanName.contains("controller")) {
32                logger.debug("加载的Bean: {}", beanName);
33            }
34        }
35    }
36}
37
38// 配置类
39@Configuration
40@EnableConfigurationProperties({AppProperties.class, SecurityProperties.class})
41public class AppConfig {
42    
43    @Bean
44    @ConditionalOnMissingBean
45    public ObjectMapper objectMapper() {
46        ObjectMapper mapper = new ObjectMapper();
47        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
48        mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
49        mapper.registerModule(new JavaTimeModule());
50        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
51        return mapper;
52    }
53    
54    @Bean
55    @ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
56    public CacheManager cacheManager() {
57        return new ConcurrentMapCacheManager("users", "orders");
58    }
59}
60
61// 配置属性类
62@ConfigurationProperties(prefix = "app")
63@Component
64@Validated
65public class AppProperties {
66    
67    @NotBlank
68    private String name;
69    
70    @Min(1024)
71    @Max(65535)
72    private int port = 8080;
73    
74    private Security security = new Security();
75    
76    // getters and setters
77    
78    public static class Security {
79        private boolean enabled = true;
80        private String secretKey;
81        
82        // getters and setters
83    }
84}
85
4.2.2 应用配置文件
1# application.yml
2spring:
3  application:
4    name: my-spring-app
5  profiles:
6    active: @activatedProperties@
7  datasource:
8    url: jdbc:mysql://localhost:3306/mydb
9    username: ${DB_USERNAME:root}
10    password: ${DB_PASSWORD:password}
11    hikari:
12      maximum-pool-size: 20
13      minimum-idle: 5
14  jpa:
15    hibernate:
16      ddl-auto: validate
17    show-sql: true
18    properties:
19      hibernate:
20        dialect: org.hibernate.dialect.MySQL8Dialect
21        format_sql: true
22  redis:
23    host: localhost
24    port: 6379
25    timeout: 2000ms
26
27server:
28  port: 8080
29  servlet:
30    context-path: /api
31  compression:
32    enabled: true
33
34app:
35  name: "My Application"
36  port: 8080
37  security:
38    enabled: true
39    secret-key: ${APP_SECRET:default-secret-key}
40  cache:
41    enabled: true
42
43logging:
44  level:
45    com.example: DEBUG
46    org.springframework.security: INFO
47  pattern:
48    console: "%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n"
49  file:
50    name: logs/application.log
51    max-size: 10MB
52

4.3 Spring MVC Web 开发

4.3.1 RESTful API 开发
1// 统一响应格式
2public class ApiResponse<T> {
3    private boolean success;
4    private String message;
5    private T data;
6    private String timestamp;
7    
8    public ApiResponse(boolean success, String message, T data) {
9        this.success = success;
10        this.message = message;
11        this.data = data;
12        this.timestamp = Instant.now().toString();
13    }
14    
15    public static <T> ApiResponse<T> success(T data) {
16        return new ApiResponse<>(true, "成功", data);
17    }
18    
19    public static <T> ApiResponse<T> success(String message, T data) {
20        return new ApiResponse<>(true, message, data);
21    }
22    
23    public static <T> ApiResponse<T> error(String message) {
24        return new ApiResponse<>(false, message, null);
25    }
26    
27    // getters and setters
28}
29
30// 统一异常处理
31@ControllerAdvice
32@RestController
33public class GlobalExceptionHandler {
34    
35    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
36    
37    @ExceptionHandler(EntityNotFoundException.class)
38    @ResponseStatus(HttpStatus.NOT_FOUND)
39    public ApiResponse<Void> handleEntityNotFound(EntityNotFoundException ex) {
40        logger.warn("实体未找到: {}", ex.getMessage());
41        return ApiResponse.error(ex.getMessage());
42    }
43    
44    @ExceptionHandler(ValidationException.class)
45    @ResponseStatus(HttpStatus.BAD_REQUEST)
46    public ApiResponse<Void> handleValidationException(ValidationException ex) {
47        logger.warn("验证失败: {}", ex.getMessage());
48        return ApiResponse.error(ex.getMessage());
49    }
50    
51    @ExceptionHandler(AccessDeniedException.class)
52    @ResponseStatus(HttpStatus.FORBIDDEN)
53    public ApiResponse<Void> handleAccessDenied(AccessDeniedException ex) {
54        logger.warn("访问被拒绝: {}", ex.getMessage());
55        return ApiResponse.error("没有访问权限");
56    }
57    
58    @ExceptionHandler(Exception.class)
59    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
60    public ApiResponse<Void> handleGenericException(Exception ex) {
61        logger.error("服务器内部错误", ex);
62        return ApiResponse.error("服务器内部错误,请稍后重试");
63    }
64    
65    // 处理方法参数验证错误
66    @ExceptionHandler(MethodArgumentNotValidException.class)
67    @ResponseStatus(HttpStatus.BAD_REQUEST)
68    public ApiResponse<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) {
69        Map<String, String> errors = new HashMap<>();
70        ex.getBindingResult().getFieldErrors()
71                .forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
72        
73        return new ApiResponse<>(false, "参数验证失败", errors);
74    }
75}
76
77// REST 控制器
78@RestController
79@RequestMapping("/api/v1/users")
80@Validated
81@Slf4j
82public class UserController {
83    
84    private final UserService userService;
85    
86    public UserController(UserService userService) {
87        this.userService = userService;
88    }
89    
90    @GetMapping
91    @PreAuthorize("hasRole('ADMIN')")
92    public ApiResponse<Page<UserDto>> getUsers(
93            @RequestParam(defaultValue = "0") int page,
94            @RequestParam(defaultValue = "20") int size,
95            @RequestParam(required = false) String search) {
96        
97        Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
98        Page<UserDto> users = userService.getUsers(pageable, search);
99        
100        return ApiResponse.success(users);
101    }
102    
103    @GetMapping("/{id}")
104    public ApiResponse<UserDto> getUser(@PathVariable Long id) {
105        UserDto user = userService.getUserById(id);
106        return ApiResponse.success(user);
107    }
108    
109    @PostMapping
110    @ResponseStatus(HttpStatus.CREATED)
111    public ApiResponse<UserDto> createUser(@Valid @RequestBody CreateUserRequest request) {
112        UserDto createdUser = userService.createUser(request);
113        return ApiResponse.success("用户创建成功", createdUser);
114    }
115    
116    @PutMapping("/{id}")
117    public ApiResponse<UserDto> updateUser(
118            @PathVariable Long id, 
119            @Valid @RequestBody UpdateUserRequest request) {
120        
121        UserDto updatedUser = userService.updateUser(id, request);
122        return ApiResponse.success("用户更新成功", updatedUser);
123    }
124    
125    @DeleteMapping("/{id}")
126    @ResponseStatus(HttpStatus.NO_CONTENT)
127    public void deleteUser(@PathVariable Long id) {
128        userService.deleteUser(id);
129    }
130    
131    @GetMapping("/{id}/orders")
132    public ApiResponse<List<OrderDto>> getUserOrders(@PathVariable Long id) {
133        List<OrderDto> orders = userService.getUserOrders(id);
134        return ApiResponse.success(orders);
135    }
136}
137
138// DTO 类
139public class CreateUserRequest {
140    
141    @NotBlank(message = "邮箱不能为空")
142    @Email(message = "邮箱格式不正确")
143    private String email;
144    
145    @NotBlank(message = "姓名不能为空")
146    @Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间")
147    private String name;
148    
149    @NotBlank(message = "密码不能为空")
150    @Size(min = 6, message = "密码长度至少6位")
151    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$", 
152             message = "密码必须包含大小写字母和数字")
153    private String password;
154    
155    // getters and setters
156}
157
158// 服务层实现
159@Service
160@Transactional(readOnly = true)
161@Slf4j
162public class UserService {
163    
164    private final UserRepository userRepository;
165    private final UserMapper userMapper;
166    private final PasswordEncoder passwordEncoder;
167    
168    public UserService(UserRepository userRepository, UserMapper userMapper, 
169                      PasswordEncoder passwordEncoder) {
170        this.userRepository = userRepository;
171        this.userMapper = userMapper;
172        this.passwordEncoder = passwordEncoder;
173    }
174    
175    public Page<UserDto> getUsers(Pageable pageable, String search) {
176        Specification<User> spec = buildSearchSpecification(search);
177        Page<User> users = userRepository.findAll(spec, pageable);
178        return users.map(userMapper::toDto);
179    }
180    
181    public UserDto getUserById(Long id) {
182        User user = userRepository.findById(id)
183                .orElseThrow(() -> new EntityNotFoundException("用户不存在: " + id));
184        return userMapper.toDto(user);
185    }
186    
187    @Transactional
188    public UserDto createUser(CreateUserRequest request) {
189        // 检查邮箱是否已存在
190        if (userRepository.existsByEmail(request.getEmail())) {
191            throw new ValidationException("邮箱已存在: " + request.getEmail());
192        }
193        
194        User user = new User();
195        user.setEmail(request.getEmail());
196        user.setName(request.getName());
197        user.setPassword(passwordEncoder.encode(request.getPassword()));
198        user.setStatus(UserStatus.ACTIVE);
199        user.setCreatedAt(LocalDateTime.now());
200        
201        User savedUser = userRepository.save(user);
202        log.info("创建用户成功: {}", savedUser.getEmail());
203        
204        return userMapper.toDto(savedUser);
205    }
206    
207    @Transactional
208    public UserDto updateUser(Long id, UpdateUserRequest request) {
209        User user = userRepository.findById(id)
210                .orElseThrow(() -> new EntityNotFoundException("用户不存在: " + id));
211        
212        user.setName(request.getName());
213        user.setUpdatedAt(LocalDateTime.now());
214        
215        User updatedUser = userRepository.save(user);
216        return userMapper.toDto(updatedUser);
217    }
218    
219    @Transactional
220    public void deleteUser(Long id) {
221        User user = userRepository.findById(id)
222                .orElseThrow(() -> new EntityNotFoundException("用户不存在: " + id));
223        
224        user.setStatus(UserStatus.DELETED);
225        userRepository.save(user);
226        log.info("删除用户: {}", id);
227    }
228    
229    private Specification<User> buildSearchSpecification(String search) {
230        return (root, query, criteriaBuilder) -> {
231            if (StringUtils.isEmpty(search)) {
232                return criteriaBuilder.conjunction();
233            }
234            
235            String likePattern = "%" + search.toLowerCase() + "%";
236            return criteriaBuilder.or(
237                criteriaBuilder.like(criteriaBuilder.lower(root.get("name")), likePattern),
238                criteriaBuilder.like(criteriaBuilder.lower(root.get("email")), likePattern)
239            );
240        };
241    }
242}
243

第五部分:数据库与持久层技术

5.1 JPA 与 Hibernate

5.1.1 实体类映射
1// 基类实体
2@MappedSuperclass
3@EntityListeners(AuditingEntityListener.class)
4public abstract class BaseEntity {
5    
6    @Id
7    @GeneratedValue(strategy = GenerationType.IDENTITY)
8    private Long id;
9    
10    @CreatedDate
11    @Column(name = "created_at", updatable = false)
12    private LocalDateTime createdAt;
13    
14    @LastModifiedDate
15    @Column(name = "updated_at")
16    private LocalDateTime updatedAt;
17    
18    @Version
19    private Long version;
20    
21    // getters and setters
22}
23
24// 用户实体
25@Entity
26@Table(name = "users", 
27       indexes = {
28           @Index(name = "idx_user_email", columnList = "email", unique = true),
29           @Index(name = "idx_user_status", columnList = "status")
30       })
31@NamedQueries({
32    @NamedQuery(
33        name = "User.findByStatus",
34        query = "SELECT u FROM User u WHERE u.status = :status"
35    )
36})
37public class User extends BaseEntity {
38    
39    @Column(name = "email", nullable = false, length = 100)
40    private String email;
41    
42    @Column(name = "name", nullable = false, length = 50)
43    private String name;
44    
45    @Column(name = "password_hash", nullable = false, length = 100)
46    private String password;
47    
48    @Enumerated(EnumType.STRING)
49    @Column(name = "status", length = 20)
50    private UserStatus status = UserStatus.ACTIVE;
51    
52    @Column(name = "last_login_at")
53    private LocalDateTime lastLoginAt;
54    
55    // 一对一关系
56    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
57    private UserProfile profile;
58    
59    // 一对多关系
60    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
61    @OrderBy("createdAt DESC")
62    private List<Order> orders = new ArrayList<>();
63    
64    // 多对多关系
65    @ManyToMany(fetch = FetchType.LAZY)
66    @JoinTable(
67        name = "user_roles",
68        joinColumns = @JoinColumn(name = "user_id"),
69        inverseJoinColumns = @JoinColumn(name = "role_id")
70    )
71    private Set<Role> roles = new HashSet<>();
72    
73    // 构造函数
74    public User() {}
75    
76    public User(String email, String name) {
77        this.email = email;
78        this.name = name;
79    }
80    
81    // 业务方法
82    public void addOrder(Order order) {
83        orders.add(order);
84        order.setUser(this);
85    }
86    
87    public void removeOrder(Order order) {
88        orders.remove(order);
89        order.setUser(null);
90    }
91    
92    public void addRole(Role role) {
93        roles.add(role);
94        role.getUsers().add(this);
95    }
96    
97    // getters and setters
98}
99
100// 枚举定义
101public enum UserStatus {
102    ACTIVE, INACTIVE, SUSPENDED, DELETED
103}
104
105// 订单实体
106@Entity
107@Table(name = "orders")
108public class Order extends BaseEntity {
109    
110    @Column(name = "order_number", unique = true, length = 20)
111    private String orderNumber;
112    
113    @Column(name = "total_amount", precision = 10, scale = 2)
114    private BigDecimal totalAmount;
115    
116    @Enumerated(EnumType.STRING)
117    @Column(name = "status", length = 20)
118    private OrderStatus status = OrderStatus.PENDING;
119    
120    // 多对一关系
121    @ManyToOne(fetch = FetchType.LAZY)
122    @JoinColumn(name = "user_id")
123    private User user;
124    
125    // 一对多关系
126    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
127    private List<OrderItem> items = new ArrayList<>();
128    
129    // 嵌入式对象
130    @Embedded
131    private Address shippingAddress;
132    
133    // 业务方法
134    public void addItem(OrderItem item) {
135        items.add(item);
136        item.setOrder(this);
137    }
138    
139    public BigDecimal calculateTotal() {
140        return items.stream()
141                .map(OrderItem::getSubtotal)
142                .reduce(BigDecimal.ZERO, BigDecimal::add);
143    }
144    
145    // getters and setters
146}
147
148// 嵌入式地址类
149@Embeddable
150public class Address {
151    
152    @Column(name = "street", length = 100)
153    private String street;
154    
155    @Column(name = "city", length = 50)
156    private String city;
157    
158    @Column(name = "state", length = 50)
159    private String state;
160    
161    @Column(name = "zip_code", length = 20)
162    private String zipCode;
163    
164    @Column(name = "country", length = 50)
165    private String country = "China";
166    
167    // getters and setters
168}
169
5.1.2 Repository 数据访问层
1// 基础 Repository 接口
2@NoRepositoryBean
3public interface BaseRepository<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
4    
5    default Optional<T> findByIdOptional(ID id) {
6        return findById(id);
7    }
8    
9    List<T> findByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
10    
11    @Query("SELECT COUNT(e) FROM #{#entityName} e")
12    long countAll();
13    
14    @Modifying
15    @Query("UPDATE #{#entityName} e SET e.status = :status WHERE e.id = :id")
16    int updateStatus(@Param("id") ID id, @Param("status") String status);
17}
18
19// 用户 Repository
20@Repository
21public interface UserRepository extends BaseRepository<User, Long> {
22    
23    // 派生查询方法
24    Optional<User> findByEmail(String email);
25    
26    boolean existsByEmail(String email);
27    
28    List<User> findByStatusOrderByCreatedAtDesc(UserStatus status);
29    
30    List<User> findByNameContainingIgnoreCase(String name);
31    
32    // @Query 注解查询
33    @Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
34    List<User> findByEmailDomain(@Param("domain") String domain);
35    
36    @Query("SELECT u.name, COUNT(o) FROM User u LEFT JOIN u.orders o GROUP BY u.id")
37    List<Object[]> findUserOrderCount();
38    
39    // 原生 SQL 查询
40    @Query(value = "SELECT * FROM users WHERE DATE(created_at) = :date", nativeQuery = true)
41    List<User> findByCreatedDate(@Param("date") LocalDate date);
42    
43    // 分页查询
44    Page<User> findByStatus(UserStatus status, Pageable pageable);
45    
46    // 使用 Specification 进行复杂查询
47    default Page<User> findActiveUsersWithOrders(Pageable pageable) {
48        return findAll((root, query, criteriaBuilder) -> {
49            List<Predicate> predicates = new ArrayList<>();
50            predicates.add(criteriaBuilder.equal(root.get("status"), UserStatus.ACTIVE));
51            predicates.add(criteriaBuilder.isNotEmpty(root.get("orders")));
52            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
53        }, pageable);
54    }
55}
56
57// 自定义 Repository 实现
58public interface UserRepositoryCustom {
59    List<User> findComplexUsers(UserSearchCriteria criteria);
60    Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable);
61}
62
63// 自定义 Repository 实现类
64@Repository
65@Transactional(readOnly = true)
66public class UserRepositoryImpl implements UserRepositoryCustom {
67    
68    @PersistenceContext
69    private EntityManager entityManager;
70    
71    @Override
72    public List<User> findComplexUsers(UserSearchCriteria criteria) {
73        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
74        CriteriaQuery<User> query = cb.createQuery(User.class);
75        Root<User> root = query.from(User.class);
76        
77        List<Predicate> predicates = buildPredicates(criteria, cb, root);
78        query.where(predicates.toArray(new Predicate[0]));
79        
80        TypedQuery<User> typedQuery = entityManager.createQuery(query);
81        return typedQuery.getResultList();
82    }
83    
84    @Override
85    public Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable) {
86        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
87        CriteriaQuery<User> query = cb.createQuery(User.class);
88        Root<User> root = query.from(User.class);
89        
90        List<Predicate> predicates = buildPredicates(criteria, cb, root);
91        query.where(predicates.toArray(new Predicate[0]));
92        
93        // 排序
94        List<Order> orders = new ArrayList<>();
95        for (Sort.Order sortOrder : pageable.getSort()) {
96            if (sortOrder.isAscending()) {
97                orders.add(cb.asc(root.get(sortOrder.getProperty())));
98            } else {
99                orders.add(cb.desc(root.get(sortOrder.getProperty())));
100            }
101        }
102        query.orderBy(orders);
103        
104        TypedQuery<User> typedQuery = entityManager.createQuery(query);
105        typedQuery.setFirstResult((int) pageable.getOffset());
106        typedQuery.setMaxResults(pageable.getPageSize());
107        
108        List<User> result = typedQuery.getResultList();
109        
110        // 获取总数
111        Long total = getTotalCount(criteria);
112        
113        return new PageImpl<>(result, pageable, total);
114    }
115    
116    private List<Predicate> buildPredicates(UserSearchCriteria criteria, 
117                                           CriteriaBuilder cb, Root<User> root) {
118        List<Predicate> predicates = new ArrayList<>();
119        
120        if (StringUtils.hasText(criteria.getKeyword())) {
121            String likePattern = "%" + criteria.getKeyword().toLowerCase() + "%";
122            predicates.add(cb.or(
123                cb.like(cb.lower(root.get("name")), likePattern),
124                cb.like(cb.lower(root.get("email")), likePattern)
125            ));
126        }
127        
128        if (criteria.getStatus() != null) {
129            predicates.add(cb.equal(root.get("status"), criteria.getStatus()));
130        }
131        
132        if (criteria.getStartDate() != null) {
133            predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), 
134                                                 criteria.getStartDate()));
135        }
136        
137        if (criteria.getEndDate() != null) {
138            predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), 
139                                              criteria.getEndDate()));
140        }
141        
142        return predicates;
143    }
144    
145    private Long getTotalCount(UserSearchCriteria criteria) {
146        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
147        CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
148        Root<User> root = countQuery.from(User.class);
149        
150        List<Predicate> predicates = buildPredicates(criteria, cb, root);
151        countQuery.select(cb.count(root)).where(predicates.toArray(new Predicate[0]));
152        
153        return entityManager.createQuery(countQuery).getSingleResult();
154    }
155}
156
157// 搜索条件类
158@Data
159public class UserSearchCriteria {
160    private String keyword;
161    private UserStatus status;
162    private LocalDateTime startDate;
163    private LocalDateTime endDate;
164    private Boolean hasOrders;
165}
166

5.2 数据库迁移与版本控制

5.2.1 Flyway 数据库迁移
1-- V1__Create_users_table.sql
2CREATE TABLE users (
3    id BIGINT AUTO_INCREMENT PRIMARY KEY,
4    email VARCHAR(100) NOT NULL UNIQUE,
5    name VARCHAR(50) NOT NULL,
6    password_hash VARCHAR(100) NOT NULL,
7    status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
8    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
9    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
10    version BIGINT DEFAULT 0
11);
12
13CREATE INDEX idx_user_email ON users(email);
14CREATE INDEX idx_user_status ON users(status);
15
16-- V2__Create_orders_table.sql
17CREATE TABLE orders (
18    id BIGINT AUTO_INCREMENT PRIMARY KEY,
19    order_number VARCHAR(20) UNIQUE NOT NULL,
20    user_id BIGINT NOT NULL,
21    total_amount DECIMAL(10,2),
22    status VARCHAR(20) DEFAULT 'PENDING',
23    shipping_street VARCHAR(100),
24    shipping_city VARCHAR(50),
25    shipping_state VARCHAR(50),
26    shipping_zip_code VARCHAR(20),
27    shipping_country VARCHAR(50) DEFAULT 'China',
28    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
29    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
30    version BIGINT DEFAULT 0,
31    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
32);
33
34CREATE INDEX idx_order_user_id ON orders(user_id);
35CREATE INDEX idx_order_number ON orders(order_number);
36
37-- V3__Create_order_items_table.sql
38CREATE TABLE order_items (
39    id BIGINT AUTO_INCREMENT PRIMARY KEY,
40    order_id BIGINT NOT NULL,
41    product_name VARCHAR(100) NOT NULL,
42    quantity INT NOT NULL,
43    unit_price DECIMAL(10,2) NOT NULL,
44    subtotal DECIMAL(10,2) NOT NULL,
45    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46    FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
47);
48
49-- V4__Add_last_login_to_users.sql
50ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP NULL;
51
52-- V5__Create_user_roles.sql
53CREATE TABLE roles (
54    id BIGINT AUTO_INCREMENT PRIMARY KEY,
55    name VARCHAR(50) NOT NULL UNIQUE,
56    description VARCHAR(200),
57    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
58);
59
60CREATE TABLE user_roles (
61    user_id BIGINT NOT NULL,
62    role_id BIGINT NOT NULL,
63    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
64    PRIMARY KEY (user_id, role_id),
65    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
66    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
67);
68
69-- V6__Insert_initial_data.sql
70INSERT INTO roles (name, description) VALUES 
71('ADMIN', '系统管理员'),
72('USER', '普通用户'),
73('MANAGER', '经理');
74
75-- 回滚脚本 R__Drop_user_roles.sql
76DROP TABLE IF EXISTS user_roles;
77DROP TABLE IF EXISTS roles;
78
5.2.2 Flyway 配置
1@Configuration
2public class FlywayConfig {
3    
4    @Bean
5    public FlywayMigrationStrategy flywayMigrationStrategy() {
6        return flyway -> {
7            // 在迁移前执行的操作
8            log.info("开始数据库迁移...");
9            flyway.migrate();
10        };
11    }
12    
13    @Bean
14    @ConfigurationProperties(prefix = "spring.flyway")
15    public FlywayProperties flywayProperties() {
16        return new FlywayProperties();
17    }
18}
19
1# application.yml 中的 Flyway 配置
2spring:
3  flyway:
4    enabled: true
5    locations: classpath:db/migration
6    baseline-on-migrate: true
7    baseline-version: 1
8    validate-on-migrate: true
9    out-of-order: false
10    clean-disabled: true
11    table: flyway_schema_history
12    placeholders:
13      table-prefix: ""
14

第六部分:高级主题与最佳实践

6.1 性能优化与缓存

6.1.1 Redis 缓存集成
1// 缓存配置
2@Configuration
3@EnableCaching
4public class CacheConfig {
5    
6    @Bean
7    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
8        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
9                .entryTtl(Duration.ofMinutes(30))
10                .disableCachingNullValues()
11                .serializeKeysWith(RedisSerializationContext.SerializationPair
12                        .fromSerializer(new StringRedisSerializer()))
13                .serializeValuesWith(RedisSerializationContext.SerializationPair
14                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
15        
16        return RedisCacheManager.builder(connectionFactory)
17                .cacheDefaults(config)
18                .withInitialCacheConfigurations(getCacheConfigurations())
19                .transactionAware()
20                .build();
21    }
22    
23    private Map<String, RedisCacheConfiguration> getCacheConfigurations() {
24        Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
25        
26        cacheConfigs.put("users", RedisCacheConfiguration.defaultCacheConfig()
27                .entryTtl(Duration.ofHours(1))
28                .serializeValuesWith(RedisSerializationContext.SerializationPair
29                        .fromSerializer(new GenericJackson2JsonRedisSerializer())));
30        
31        cacheConfigs.put("products", RedisCacheConfiguration.defaultCacheConfig()
32                .entryTtl(Duration.ofMinutes(15)));
33        
34        return cacheConfigs;
35    }
36    
37    @Bean
38    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
39        RedisTemplate<String, Object> template = new RedisTemplate<>();
40        template.setConnectionFactory(connectionFactory);
41        
42        // 使用 Jackson 序列化
43        Jackson2JsonRedisSerializer<Object> serializer = 
44            new Jackson2JsonRedisSerializer<>(Object.class);
45        
46        ObjectMapper mapper = new ObjectMapper();
47        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
48        mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), 
49                                   ObjectMapper.DefaultTyping.NON_FINAL);
50        serializer.setObjectMapper(mapper);
51        
52        template.setKeySerializer(new StringRedisSerializer());
53        template.setValueSerializer(serializer);
54        template.setHashKeySerializer(new StringRedisSerializer());
55        template.setHashValueSerializer(serializer);
56        template.afterPropertiesSet();
57        
58        return template;
59    }
60}
61
62// 缓存服务
63@Service
64@Slf4j
65public class CacheService {
66    
67    private final RedisTemplate<String, Object> redisTemplate;
68    private final ObjectMapper objectMapper;
69    
70    public CacheService(RedisTemplate<String, Object> redisTemplate, 
71                       ObjectMapper objectMapper) {
72        this.redisTemplate = redisTemplate;
73        this.objectMapper = objectMapper;
74    }
75    
76    public <T> void set(String key, T value, Duration ttl) {
77        try {
78            redisTemplate.opsForValue().set(key, value, ttl);
79        } catch (Exception e) {
80            log.error("Redis 设置缓存失败 key: {}", key, e);
81        }
82    }
83    
84    @SuppressWarnings("unchecked")
85    public <T> T get(String key, Class<T> type) {
86        try {
87            Object value = redisTemplate.opsForValue().get(key);
88            if (value == null) {
89                return null;
90            }
91            
92            if (type.isInstance(value)) {
93                return (T) value;
94            }
95            
96            // 类型转换
97            return objectMapper.convertValue(value, type);
98        } catch (Exception e) {
99            log.error("Redis 获取缓存失败 key: {}", key, e);
100            return null;
101        }
102    }
103    
104    public boolean delete(String key) {
105        try {
106            return Boolean.TRUE.equals(redisTemplate.delete(key));
107        } catch (Exception e) {
108            log.error("Redis 删除缓存失败 key: {}", key, e);
109            return false;
110        }
111    }
112    
113    public boolean exists(String key) {
114        try {
115            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
116        } catch (Exception e) {
117            log.error("Redis 检查键存在失败 key: {}", key, e);
118            return false;
119        }
120    }
121    
122    public void setHash(String key, String hashKey, Object value) {
123        try {
124            redisTemplate.opsForHash().put(key, hashKey, value);
125        } catch (Exception e) {
126            log.error("Redis 设置哈希缓存失败 key: {}, hashKey: {}", key, hashKey, e);
127        }
128    }
129    
130    @SuppressWarnings("unchecked")
131    public <T> T getHash(String key, String hashKey, Class<T> type) {
132        try {
133            Object value = redisTemplate.opsForHash().get(key, hashKey);
134            if (value == null) {
135                return null;
136            }
137            
138            if (type.isInstance(value)) {
139                return (T) value;
140            }
141            
142            return objectMapper.convertValue(value, type);
143        } catch (Exception e) {
144            log.error("Redis 获取哈希缓存失败 key: {}, hashKey: {}", key, hashKey, e);
145            return null;
146        }
147    }
148}
149
150// 使用缓存的服务
151@Service
152@Slf4j
153public class CachedUserService {
154    
155    private final UserRepository userRepository;
156    private final CacheService cacheService;
157    private final UserMapper userMapper;
158    
159    private static final String USER_CACHE_PREFIX = "user:";
160    private static final String USER_LIST_CACHE_KEY = "users:list";
161    private static final Duration USER_CACHE_TTL = Duration.ofHours(1);
162    
163    public CachedUserService(UserRepository userRepository, CacheService cacheService,
164                           UserMapper userMapper) {
165        this.userRepository = userRepository;
166        this.cacheService = cacheService;
167        this.userMapper = userMapper;
168    }
169    
170    @Cacheable(value = "users", key = "#id", unless = "#result == null")
171    public UserDto getUserById(Long id) {
172        log.debug("从数据库获取用户: {}", id);
173        User user = userRepository.findById(id)
174                .orElseThrow(() -> new EntityNotFoundException("用户不存在: " + id));
175        return userMapper.toDto(user);
176    }
177    
178    @Cacheable(value = "users", key = "#email", unless = "#result == null")
179    public UserDto getUserByEmail(String email) {
180        log.debug("从数据库获取用户: {}", email);
181        User user = userRepository.findByEmail(email)
182                .orElseThrow(() -> new EntityNotFoundException("用户不存在: " + email));
183        return userMapper.toDto(user);
184    }
185    
186    @Caching(
187        put = {
188            @CachePut(value = "users", key = "#result.id"),
189            @CachePut(value = "users", key = "#result.email")
190        },
191        evict = {
192            @CacheEvict(value = "users", key = "'list'"),
193            @CacheEvict(value = "users", key = "'search:' + #result.status")
194        }
195    )
196    public UserDto updateUser(UserDto userDto) {
197        User user = userRepository.findById(userDto.getId())
198                .orElseThrow(() -> new EntityNotFoundException("用户不存在: " + userDto.getId()));
199        
200        userMapper.updateUserFromDto(userDto, user);
201        User updatedUser = userRepository.save(user);
202        
203        log.info("更新用户: {}", userDto.getId());
204        return userMapper.toDto(updatedUser);
205    }
206    
207    @CacheEvict(value = "users", allEntries = true)
208    public void evictAllUserCaches() {
209        log.info("清除所有用户缓存");
210    }
211    
212    // 手动缓存管理
213    public List<UserDto> getActiveUsers() {
214        String cacheKey = "users:active";
215        
216        // 尝试从缓存获取
217        List<UserDto> cachedUsers = cacheService.get(cacheKey, List.class);
218        if (cachedUsers != null) {
219            log.debug("从缓存获取活跃用户列表");
220            return cachedUsers;
221        }
222        
223        // 从数据库获取
224        log.debug("从数据库获取活跃用户列表");
225        List<User> users = userRepository.findByStatusOrderByCreatedAtDesc(UserStatus.ACTIVE);
226        List<UserDto> userDtos = users.stream()
227                .map(userMapper::toDto)
228                .collect(Collectors.toList());
229        
230        // 设置缓存
231        cacheService.set(cacheKey, userDtos, Duration.ofMinutes(30));
232        
233        return userDtos;
234    }
235}
236

6.2 安全与认证授权

6.2.1 Spring Security 配置
1// 安全配置
2@Configuration
3@EnableWebSecurity
4@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
5public class SecurityConfig {
6    
7    private final UserDetailsService userDetailsService;
8    private final JwtTokenProvider jwtTokenProvider;
9    
10    public SecurityConfig(UserDetailsService userDetailsService, 
11                         JwtTokenProvider jwtTokenProvider) {
12        this.userDetailsService = userDetailsService;
13        this.jwtTokenProvider = jwtTokenProvider;
14    }
15    
16    @Bean
17    public PasswordEncoder passwordEncoder() {
18        return new BCryptPasswordEncoder();
19    }
20    
21    @Bean
22    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
23        http
24            .cors().and()
25            .csrf().disable()
26            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
27            .and()
28            .authorizeRequests()
29            .antMatchers("/api/auth/**").permitAll()
30            .antMatchers("/api/public/**").permitAll()
31            .antMatchers("/api/admin/**").hasRole("ADMIN")
32            .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
33            .antMatchers("/api/**").authenticated()
34            .anyRequest().permitAll()
35            .and()
36            .apply(new JwtConfigurer(jwtTokenProvider));
37        
38        return http.build();
39    }
40    
41    @Bean
42    public CorsFilter corsFilter() {
43        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
44        CorsConfiguration config = new CorsConfiguration();
45        
46        config.setAllowCredentials(true);
47        config.addAllowedOriginPattern("*");
48        config.addAllowedHeader("*");
49        config.addAllowedMethod("*");
50        config.addExposedHeader("Authorization");
51        
52        source.registerCorsConfiguration("/**", config);
53        return new CorsFilter(source);
54    }
55}
56
57// JWT 配置
58public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
59    
60    private final JwtTokenProvider jwtTokenProvider;
61    
62    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
63        this.jwtTokenProvider = jwtTokenProvider;
64    }
65    
66    @Override
67    public void configure(HttpSecurity http) {
68        JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
69        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
70    }
71}
72
73// JWT Token 过滤器
74public class JwtTokenFilter extends OncePerRequestFilter {
75    
76    private final JwtTokenProvider jwtTokenProvider;
77    
78    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
79        this.jwtTokenProvider = jwtTokenProvider;
80    }
81    
82    @Override
83    protected void doFilterInternal(HttpServletRequest request, 
84                                  HttpServletResponse response, 
85                                  FilterChain filterChain) throws ServletException, IOException {
86        
87        String token = resolveToken(request);
88        
89        if (token != null && jwtTokenProvider.validateToken(token)) {
90            Authentication auth = jwtTokenProvider.getAuthentication(token);
91            SecurityContextHolder.getContext().setAuthentication(auth);
92        }
93        
94        filterChain.doFilter(request, response);
95    }
96    
97    private String resolveToken(HttpServletRequest request) {
98        String bearerToken = request.getHeader("Authorization");
99        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
100            return bearerToken.substring(7);
101        }
102        return null;
103    }
104}
105
106// JWT Token 提供者
107@Component
108public class JwtTokenProvider {
109    
110    @Value("${app.jwt.secret:defaultSecret}")
111    private String jwtSecret;
112    
113    @Value("${app.jwt.expiration:86400000}")
114    private long jwtExpirationMs;
115    
116    private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
117    
118    public String generateToken(Authentication authentication) {
119        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
120        
121        Date now = new Date();
122        Date expiryDate = new Date(now.getTime() + jwtExpirationMs);
123        
124        return Jwts.builder()
125                .setSubject(userPrincipal.getUsername())
126                .setIssuedAt(now)
127                .setExpiration(expiryDate)
128                .signWith(SignatureAlgorithm.HS512, jwtSecret)
129                .compact();
130    }
131    
132    public String getUsernameFromToken(String token) {
133        Claims claims = Jwts.parser()
134                .setSigningKey(jwtSecret)
135                .parseClaimsJws(token)
136                .getBody();
137        
138        return claims.getSubject();
139    }
140    
141    public boolean validateToken(String token) {
142        try {
143            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
144            return true;
145        } catch (SignatureException ex) {
146            logger.error("Invalid JWT signature");
147        } catch (MalformedJwtException ex) {
148            logger.error("Invalid JWT token");
149        } catch (ExpiredJwtException ex) {
150            logger.error("Expired JWT token");
151        } catch (UnsupportedJwtException ex) {
152            logger.error("Unsupported JWT token");
153        } catch (IllegalArgumentException ex) {
154            logger.error("JWT claims string is empty");
155        }
156        return false;
157    }
158    
159    public Authentication getAuthentication(String token) {
160        String username = getUsernameFromToken(token);
161        
162        UserDetails userDetails = loadUserByUsername(username);
163        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
164    }
165    
166    private UserDetails loadUserByUsername(String username) {
167        // 实现用户加载逻辑
168        return null; // 实际应该从数据库加载
169    }
170}
171
172// 用户详情服务
173@Service
174@Transactional(readOnly = true)
175public class CustomUserDetailsService implements UserDetailsService {
176    
177    private final UserRepository userRepository;
178    
179    public CustomUserDetailsService(UserRepository userRepository) {
180        this.userRepository = userRepository;
181    }
182    
183    @Override
184    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
185        User user = userRepository.findByEmail(email)
186                .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + email));
187        
188        return UserPrincipal.create(user);
189    }
190    
191    public UserDetails loadUserById(Long id) {
192        User user = userRepository.findById(id)
193                .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + id));
194        
195        return UserPrincipal.create(user);
196    }
197}
198
199// 用户主体类
200public class UserPrincipal implements UserDetails {
201    
202    private Long id;
203    private String email;
204    private String password;
205    private Collection<? extends GrantedAuthority> authorities;
206    
207    public UserPrincipal(Long id, String email, String password, 
208                        Collection<? extends GrantedAuthority> authorities) {
209        this.id = id;
210        this.email = email;
211        this.password = password;
212        this.authorities = authorities;
213    }
214    
215    public static UserPrincipal create(User user) {
216        List<GrantedAuthority> authorities = user.getRoles().stream()
217                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
218                .collect(Collectors.toList());
219        
220        return new UserPrincipal(
221            user.getId(),
222            user.getEmail(),
223            user.getPassword(),
224            authorities
225        );
226    }
227    
228    // UserDetails 接口方法实现
229    @Override
230    public Collection<? extends GrantedAuthority> getAuthorities() {
231        return authorities;
232    }
233    
234    @Override
235    public String getPassword() {
236        return password;
237    }
238    
239    @Override
240    public String getUsername() {
241        return email;
242    }
243    
244    @Override
245    public boolean isAccountNonExpired() {
246        return true;
247    }
248    
249    @Override
250    public boolean isAccountNonLocked() {
251        return true;
252    }
253    
254    @Override
255    public boolean isCredentialsNonExpired() {
256        return true;
257    }
258    
259    @Override
260    public boolean isEnabled() {
261        return true;
262    }
263    
264    // getters
265    public Long getId() {
266        return id;
267    }
268}
269

6.3 微服务与分布式系统

6.3.1 Spring Cloud 微服务配置
1// 服务注册与发现
2@SpringBootApplication
3@EnableEurekaClient
4@EnableDiscoveryClient
5public class UserServiceApplication {
6    
7    public static void main(String[] args) {
8        SpringApplication.run(UserServiceApplication.class, args);
9    }
10}
11
12// 配置类
13@Configuration
14public class FeignConfig {
15    
16    @Bean
17    public Logger.Level feignLoggerLevel() {
18        return Logger.Level.FULL;
19    }
20    
21    @Bean
22    public ErrorDecoder errorDecoder() {
23        return new CustomErrorDecoder();
24    }
25}
26
27// Feign 客户端
28@FeignClient(name = "order-service", path = "/api/orders")
29public interface OrderServiceClient {
30    
31    @GetMapping("/user/{userId}")
32    List<OrderDto> getUserOrders(@PathVariable("userId") Long userId);
33    
34    @PostMapping
35    OrderDto createOrder(@RequestBody CreateOrderRequest request);
36    
37    @PutMapping("/{orderId}/status")
38    OrderDto updateOrderStatus(@PathVariable("orderId") Long orderId, 
39                              @RequestBody UpdateOrderStatusRequest request);
40}
41
42// 服务调用示例
43@Service
44@Slf4j
45public class UserOrderService {
46    
47    private final OrderServiceClient orderServiceClient;
48    private final CircuitBreakerFactory circuitBreakerFactory;
49    
50    public UserOrderService(OrderServiceClient orderServiceClient,
51                           CircuitBreakerFactory circuitBreakerFactory) {
52        this.orderServiceClient = orderServiceClient;
53        this.circuitBreakerFactory = circuitBreakerFactory;
54    }
55    
56    public List<OrderDto> getUserOrdersWithFallback(Long userId) {
57        CircuitBreaker circuitBreaker = circuitBreakerFactory.create("order-service");
58        
59        return circuitBreaker.run(
60            () -> orderServiceClient.getUserOrders(userId),
61            throwable -> {
62                log.warn("订单服务调用失败,使用降级策略", throwable);
63                return Collections.emptyList();
64            }
65        );
66    }
67    
68    @Retry(name = "order-service", fallbackMethod = "createOrderFallback")
69    public OrderDto createOrderWithRetry(CreateOrderRequest request) {
70        return orderServiceClient.createOrder(request);
71    }
72    
73    public OrderDto createOrderFallback(CreateOrderRequest request, Exception ex) {
74        log.error("创建订单重试失败", ex);
75        // 返回默认值或抛出业务异常
76        throw new BusinessException("订单服务暂时不可用");
77    }
78}
79
80// 配置中心配置
81@Configuration
82@RefreshScope
83public class AppConfig {
84    
85    @Value("${app.feature.enabled:false}")
86    private boolean featureEnabled;
87    
88    @Value("${app.cache.ttl:300}")
89    private long cacheTtl;
90    
91    @Bean
92    @RefreshScope
93    public CacheManager cacheManager() {
94        // 动态配置的缓存管理器
95        return new ConcurrentMapCacheManager();
96    }
97    
98    // getters
99}
100

第七部分:部署与运维

7.1 Docker 容器化部署

7.1.1 Dockerfile 配置
1# 多阶段构建 Dockerfile
2FROM openjdk:11-jdk-slim as builder
3
4# 安装 Maven
5RUN apt-get update && apt-get install -y maven
6
7WORKDIR /app
8COPY pom.xml .
9COPY src ./src
10
11# 构建应用
12RUN mvn clean package -DskipTests
13
14# 运行时镜像
15FROM openjdk:11-jre-slim
16
17# 安装必要的工具
18RUN apt-get update && apt-get install -y --no-install-recommends \
19    curl \
20    && rm -rf /var/lib/apt/lists/*
21
22# 创建应用用户
23RUN groupadd -r appuser && useradd -r -g appuser appuser
24
25# 创建应用目录
26RUN mkdir -p /app/logs && chown -R appuser:appuser /app
27
28USER appuser
29WORKDIR /app
30
31# 复制构建结果
32COPY --from=builder /app/target/*.jar app.jar
33
34# 健康检查
35HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
36    CMD curl -f http://localhost:8080/actuator/health || exit 1
37
38# 环境变量
39ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom"
40ENV SPRING_PROFILES_ACTIVE="docker"
41
42# 暴露端口
43EXPOSE 8080
44
45# 启动应用
46ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
47
7.1.2 Docker Compose 配置
1version: '3.8'
2
3services:
4  app:
5    build: .
6    ports:
7      - "8080:8080"
8    environment:
9      - SPRING_PROFILES_ACTIVE=docker
10      - DB_HOST=mysql
11      - REDIS_HOST=redis
12    depends_on:
13      - mysql
14      - redis
15    networks:
16      - app-network
17    deploy:
18      resources:
19        limits:
20          memory: 1G
21        reservations:
22          memory: 512M
23
24  mysql:
25    image: mysql:8.0
26    environment:
27      MYSQL_ROOT_PASSWORD: rootpassword
28      MYSQL_DATABASE: myapp
29      MYSQL_USER: appuser
30      MYSQL_PASSWORD: apppassword
31    ports:
32      - "3306:3306"
33    volumes:
34      - mysql-data:/var/lib/mysql
35      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
36    networks:
37      - app-network
38    command: 
39      - --default-authentication-plugin=mysql_native_password
40      - --character-set-server=utf8mb4
41      - --collation-server=utf8mb4_unicode_ci
42
43  redis:
44    image: redis:6.2-alpine
45    ports:
46      - "6379:6379"
47    volumes:
48      - redis-data:/data
49    networks:
50      - app-network
51    command: redis-server --appendonly yes
52
53  nginx:
54    image: nginx:alpine
55    ports:
56      - "80:80"
57      - "443:443"
58    volumes:
59      - ./nginx/conf.d:/etc/nginx/conf.d
60      - ./nginx/ssl:/etc/nginx/ssl
61    depends_on:
62      - app
63    networks:
64      - app-network
65
66volumes:
67  mysql-data:
68  redis-data:
69
70networks:
71  app-network:
72    driver: bridge
73

7.2 监控与日志

7.2.1 Spring Boot Actuator 配置
1# 监控配置
2management:
3  endpoints:
4    web:
5      exposure:
6        include: health,info,metrics,loggers,prometheus
7      base-path: /actuator
8  endpoint:
9    health:
10      show-details: always
11      show-components: always
12    metrics:
13      enabled: true
14    loggers:
15      enabled: true
16  metrics:
17    export:
18      prometheus:
19        enabled: true
20    distribution:
21      percentiles-histogram:
22        http.server.requests: true
23  info:
24    env:
25      enabled: true
26
7.2.2 自定义健康检查
1@Component
2public class DatabaseHealthIndicator implements HealthIndicator {
3    
4    private final DataSource dataSource;
5    
6    public DatabaseHealthIndicator(DataSource dataSource) {
7        this.dataSource = dataSource;
8    }
9    
10    @Override
11    public Health health() {
12        try (Connection connection = dataSource.getConnection()) {
13            // 执行简单的数据库检查
14            try (Statement statement = connection.createStatement()) {
15                statement.execute("SELECT 1");
16            }
17            
18            return Health.up()
19                    .withDetail("database", "MySQL")
20                    .withDetail("validationQuery", "SELECT 1")
21                    .build();
22                    
23        } catch (Exception e) {
24            return Health.down(e)
25                    .withDetail("database", "MySQL")
26                    .withDetail("error", e.getMessage())
27                    .build();
28        }
29    }
30}
31
32@Component
33public class RedisHealthIndicator implements HealthIndicator {
34    
35    private final RedisTemplate<String, Object> redisTemplate;
36    
37    public RedisHealthIndicator(RedisTemplate<String, Object> redisTemplate) {
38        this.redisTemplate = redisTemplate;
39    }
40    
41    @Override
42    public Health health() {
43        try {
44            // 执行简单的 Redis 操作
45            String testKey = "health:check";
46            redisTemplate.opsForValue().set(testKey, "test", Duration.ofSeconds(10));
47            String value = (String) redisTemplate.opsForValue().get(testKey);
48            
49            if ("test".equals(value)) {
50                return Health.up()
51                        .withDetail("redis", "connected")
52                        .build();
53            } else {
54                return Health.down()
55                        .withDetail("redis", "unexpected response")
56                        .build();
57            }
58        } catch (Exception e) {
59            return Health.down(e)
60                    .withDetail("redis", "connection failed")
61                    .withDetail("error", e.getMessage())
62                    .build();
63        }
64    }
65}
66

第八部分:学习路线与持续提升

8.1 分阶段学习计划

阶段一:基础入门(1-2个月)
  1. Java 语言基础
    • 语法、面向对象、集合框架
    • 异常处理、IO 操作、多线程
  2. 开发工具掌握
    • IntelliJ IDEA 使用
    • Maven/Gradle 构建工具
    • Git 版本控制
阶段二:Spring 框架(2-3个月)
  1. Spring Core
    • IOC、DI、AOP 概念
    • Bean 生命周期管理
  2. Spring Boot
    • 自动配置原理
    • Starter 使用
    • 应用配置管理
  3. 数据访问
    • JPA/Hibernate
    • 事务管理
    • 数据库迁移
阶段三:高级特性(2-3个月)
  1. 微服务架构
    • Spring Cloud 组件
    • 服务注册发现
    • 配置中心
  2. 性能优化
    • 缓存策略
    • 数据库优化
    • JVM 调优
  3. 安全防护
    • Spring Security
    • OAuth2/JWT
    • 安全最佳实践
阶段四:生产实践(持续)
  1. 容器化部署
    • Docker/Kubernetes
    • CI/CD 流水线
  2. 监控运维
    • 应用监控
    • 日志分析
    • 性能调优
  3. 架构设计
    • 系统架构模式
    • 领域驱动设计
    • 代码重构

8.2 持续学习资源

官方文档
在线课程
  • Spring 官方教程
  • Baeldung Java/Spring 教程
  • Java Brains YouTube 频道
实践项目
  • 个人博客系统
  • 电商平台
  • 微服务示例项目
  • 开源项目贡献

8.3 社区参与

技术社区
  • Stack Overflow
  • GitHub
  • 掘金、CSDN
  • 公司内部技术分享
持续提升建议
  1. 代码审查:参与团队代码审查,学习优秀代码风格
  2. 技术分享:定期进行技术分享,巩固知识
  3. 开源贡献:参与开源项目,了解最佳实践
  4. 技术博客:撰写技术博客,加深理解
  5. 参加技术会议:了解行业最新动态

总结

从 .NET 转型到 Java 是一个系统性的过程,需要从语言基础开始,逐步深入到框架使用、系统架构和运维部署。关键成功因素包括:

  1. 扎实的基础:深入理解 Java 语言特性和 JVM 原理
  2. 框架熟练度:掌握 Spring 生态系统的核心组件
  3. 工程化能力:熟悉构建工具、测试框架和 CI/CD
  4. 架构思维:理解微服务、分布式系统设计原则
  5. 持续学习:跟上技术发展趋势,不断更新知识体系

通过系统的学习和实践,.NET 开发者可以成功转型为 Java 开发者,并在新的技术栈中发挥更大的价值。记住,转型不仅是技术栈的切换,更是思维方式和开发理念的更新。保持开放的心态和持续学习的态度,是成功转型的关键。


从 .NET 到 Java 的转型指南:详细学习路线与实践建议》 是转载文章,点击查看原文


相关推荐


【关于虚拟机执行ip addr 命令不显示ip地址问题】
Mr.Ja9/30/2025

本文主要记录了作者在使用虚拟机时,遇到执行ip addr命令不显示 IP 地址的问题及解决过程。起初,作者发现虚拟机仅显示回环地址与网卡物理信息,无 IPv4 地址,先后通过检查 Linux 系统内网卡配置文件、重启网络服务、切换虚拟机网络模式,以及搜索技术博客、咨询 AI 等常规方式排查,均未解决问题。最终,作者偶然意识到虚拟机 NAT 模式依赖物理机的 VMware 相关服务,经检查发现 Windows 物理机中 “VMware NAT Service” 服务未启动,启动该服务后,在虚拟机中执行ifup


LinuxC++——etcd分布式键值存储系统API(libetcd-cpp-api3)下载与二次封装
深思慎考10/2/2025

etcd-cpp-apiv3 是一个 C++ 语言编写的 etcd 客户端库,用于与 etcd 分布式键值存储系统进行交互。特性说明项目简介基于 C++ 的 etcd v3 API 客户端库核心功能分布式键值存储、配置管理、服务发现、分布式锁主要依赖官方仓库github下载地址通信协议通过 gRPC 与 etcd 服务器通信 (HTTP2 + protobuf)


BFF层设计:GraphQL网关在微前端联调中的实践
qq_314166012025/10/2

在现代前端架构中,微前端(Micro-Frontend)和 GraphQL 已成为热门技术趋势。随着业务模块的复杂化,前后端协作变得越来越关键。在本文中,我们将深入探讨 BFF(Backend For Frontend)层在微前端联调中的实践经验,并分享如何通过 GraphQL 网关提升前后端协作效率 ??。 一、什么是 BFF 层 BFF(Backend For Frontend)是一种为前端量身定制的后端服务层。它的核心目标是将不同前端应用(Web、移动端等)对数据的需求进行统一


--group-start/--group-end 能不能解决 OpenSSL 1.0 vs 1.1 的优先级问题?
dlz08362025/10/2

--group-start / --group-end 其实是 ld 链接器的一个功能,专门用来解决 循环依赖的静态库 问题。 例如: -Wl,--start-group -lfoo -lbar -lbaz -Wl,--end-group 会让链接器在这几个库之间反复扫描,直到符号解析完为止。 🔎 那么能不能用它来“优先选 1.0 而不是 1.1”? 答案是:不行,原因如下: --start-group/--end-group 只是解决静态库依赖次序的问题,不会影响动态


ElementUI-Table 表格实现行拖拽
宣晨光2025/10/3

1、引入依赖 npm install sortablejs --save 2、table表格设置 1、添加属性 ref="multipleTable"  row-key="id" @row-drag-end="handleDragEnd"  2、添加列 <el-table-column width="50" align="center">             <template >               <i class="el-icon-rank drag-han


《Shell脚本门诊部:我治好了日志清理、自动备份和监控报警的“慢性病”》
鋯莂從媊2025/10/4

核心思路: 把Shell脚本比作“药方”,专门治疗运维工作中的那些重复、繁琐的“慢性病”。每个案例都是一个完整的、可复用的脚本。 内容要点与实操步骤: 案例一:日志清理脚本(“磁盘空间肥胖症”) 1.病症: /app/logs 目录动不动就100%,需要自动清理7天前的日志。 2.药方: #!/bin/bash # 日志清理专家 - 专治磁盘空间肥胖症 LOG_DIR="/app/logs" FIND_RESULT=$(find $LOG_DIR -name "*.log" -typ


Qiankun 子应用生命周期及使用场景解析
excel2025/10/5

在前端微前端架构中,Qiankun 是常用的微前端框架。它允许主应用动态加载多个子应用,而子应用必须遵循特定生命周期,保证能够被正确加载、挂载和卸载。本文将详细解析 子应用的三个核心生命周期函数:bootstrap、mount、unmount,并说明它们的使用场景,同时给出具体示例。 1. 子应用生命周期概览 子应用生命周期分为三个阶段: Bootstrap(初始化阶段) Mount(挂载阶段) Unmount(卸载阶段) 每个阶段有明确的调用时机和职责。 2. bootstrap:初始


Node.js(十二)插件开发
燎原人生2025/10/6

一、Node.js 原生插件(Native Addon)开发入门教程 1. 准备环境 安装 Node.js(建议最新版) 安装 node-gyp 工具(用于编译 C++ 代码) npm install -g node-gyp 安装编译环境 Windows: 需要安装 Windows Build ToolsmacOS: 需要安装 XcodeLinux: 需要安装 g++、make 等 2. 创建项目目录 mkdir my-native-addon cd my-native-


Spec-Kit:AI驱动的软件开发全流程管理工具套件
是魔丸啊2025/10/8

Spec-Kit 是一套专为AI辅助开发场景设计的命令行工具套件,提供了从需求规格到代码实现的标准化工作流程。通过8个核心命令,它确保软件开发的每个环节都保持高质量和一致性。 核心理念 端到端管理:覆盖需求规格化、架构设计、任务分解、代码实现全流程 AI优化设计:专为与Claude等AI助手协作而优化 质量保证:内置多层次验证机制 知识沉淀:项目宪法、设计决策完整记录 这篇文章主要是想记录下spec-kit的核心command具体在干嘛。所以翻译了下所有的command(按照实际使用顺序)


前端梳理体系从常问问题去完善-框架篇(react生态)
大前端helloworld2025/10/9

前言 国庆去趟了杭州,但是人太多了,走路都觉得空气很闷,天气也很热,玩了两天就回宿舍躺了,感觉人太多,看不到风景,而且消费也很高,性价比不是很值得,就呆在公寓,看了两本书,有一本是名著,《呼啸山庄》虽然是写的是爱情,但爱情背后是人性。爱情啊,这个课题本来就是让人很难读懂得,关于爱,也看了一篇文章。关于爱上人渣得,爱上人渣,或是那些求而不得甚至是受制于禁忌的爱,本质上也是在追求这种刺激,或者说正是因为这样的对象能给自己麻木的感官更大的刺激,从而误以为这就是「爱」的本质,就像是人们虽然知道「吊桥效应

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0