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

第一部分:转型背景与核心差异分析
1.1 为什么需要从 .NET 转型到 Java
在当前的技术环境中,从 .NET 转型到 Java 通常基于以下考虑:
- 跨平台需求增加:Java 的"一次编写,到处运行"特性在云原生和容器化环境中具有优势
- 生态系统丰富性:Java 拥有庞大的开源生态系统和社区支持
- 成本考量:Java 开源技术栈可以降低许可成本
- 人才市场因素:Java 开发者在全球范围内更为普及
- 企业级应用成熟度: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 /app/target/*.jar app.jar 33 34# 健康检查 35HEALTHCHECK \ 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个月)
- Java 语言基础
- 语法、面向对象、集合框架
- 异常处理、IO 操作、多线程
- 开发工具掌握
- IntelliJ IDEA 使用
- Maven/Gradle 构建工具
- Git 版本控制
阶段二:Spring 框架(2-3个月)
- Spring Core
- IOC、DI、AOP 概念
- Bean 生命周期管理
- Spring Boot
- 自动配置原理
- Starter 使用
- 应用配置管理
- 数据访问
- JPA/Hibernate
- 事务管理
- 数据库迁移
阶段三:高级特性(2-3个月)
- 微服务架构
- Spring Cloud 组件
- 服务注册发现
- 配置中心
- 性能优化
- 缓存策略
- 数据库优化
- JVM 调优
- 安全防护
- Spring Security
- OAuth2/JWT
- 安全最佳实践
阶段四:生产实践(持续)
- 容器化部署
- Docker/Kubernetes
- CI/CD 流水线
- 监控运维
- 应用监控
- 日志分析
- 性能调优
- 架构设计
- 系统架构模式
- 领域驱动设计
- 代码重构
8.2 持续学习资源
官方文档
在线课程
- Spring 官方教程
- Baeldung Java/Spring 教程
- Java Brains YouTube 频道
实践项目
- 个人博客系统
- 电商平台
- 微服务示例项目
- 开源项目贡献
8.3 社区参与
技术社区
- Stack Overflow
- GitHub
- 掘金、CSDN
- 公司内部技术分享
持续提升建议
- 代码审查:参与团队代码审查,学习优秀代码风格
- 技术分享:定期进行技术分享,巩固知识
- 开源贡献:参与开源项目,了解最佳实践
- 技术博客:撰写技术博客,加深理解
- 参加技术会议:了解行业最新动态
总结
从 .NET 转型到 Java 是一个系统性的过程,需要从语言基础开始,逐步深入到框架使用、系统架构和运维部署。关键成功因素包括:
- 扎实的基础:深入理解 Java 语言特性和 JVM 原理
- 框架熟练度:掌握 Spring 生态系统的核心组件
- 工程化能力:熟悉构建工具、测试框架和 CI/CD
- 架构思维:理解微服务、分布式系统设计原则
- 持续学习:跟上技术发展趋势,不断更新知识体系
通过系统的学习和实践,.NET 开发者可以成功转型为 Java 开发者,并在新的技术栈中发挥更大的价值。记住,转型不仅是技术栈的切换,更是思维方式和开发理念的更新。保持开放的心态和持续学习的态度,是成功转型的关键。
《从 .NET 到 Java 的转型指南:详细学习路线与实践建议》 是转载文章,点击查看原文。
