📝 Java 注解完全指南
yys-project中延伸学习Java注解,上传一下
1. 什么是Java注解? Java注解(Annotation)是Java 5引入的一种元数据机制,它为代码提供了一种描述性的信息,而不直接影响代码的执行逻辑。可以把注解理解为”给代码贴标签”,这些标签可以被编译器、开发工具或运行时环境读取和处理。
注解的本质
注解本质上是一种特殊的接口
继承自java.lang.annotation.Annotation
接口
在编译后会生成相应的字节码信息
2. Java注解 vs C++的对比
特性
Java注解
C++宏/预处理指令
C++属性(C++11+)
语法
@注解名
#define
, #pragma
[[属性名]]
处理时机
编译时/运行时
预处理时
编译时
类型安全
强类型
弱类型
强类型
反射支持
完全支持
不支持
有限支持
功能范围
元数据、验证、代码生成
文本替换、条件编译
优化提示、属性标记
举例对比: Java注解:
1 2 3 4 @Override public String toString () { return "example" ; }
C++属性:
1 2 3 4 [[override ]] virtual std::string toString () { return "example" ; }
C++宏:
1 2 3 4 #define OVERRIDE virtual OVERRIDE std::string toString () { return "example" ; }
3. 注解的工作原理 3.1 编译时处理 1 2 3 4 5 6 @Override public void method () {}
3.2 三个处理阶段
源码阶段 :注解存在于.java文件中
编译阶段 :注解被编译器处理,可能生成额外代码
运行阶段 :通过反射API读取注解信息
3.3 注解处理器(Annotation Processor) 1 2 3 4 5 6 7 8 9 10 @SupportedAnnotationTypes("com.example.MyAnnotation") public class MyProcessor extends AbstractProcessor { @Override public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { return true ; } }
4. 注解的分类 4.1 按生命周期分类 SOURCE(源码级) 1 2 3 4 @Retention(RetentionPolicy.SOURCE) public @interface SourceAnnotation { String value () ; }
只在源码中存在,编译后丢弃
主要用于编译时检查
例如:@Override
, @SuppressWarnings
CLASS(字节码级) 1 2 3 4 @Retention(RetentionPolicy.CLASS) public @interface ClassAnnotation { String value () ; }
在字节码中保留,但运行时不可访问
默认的保留策略
用于编译时或字节码处理工具
RUNTIME(运行时级) 1 2 3 4 @Retention(RetentionPolicy.RUNTIME) public @interface RuntimeAnnotation { String value () ; }
运行时可通过反射访问
最常用的类型
用于框架和库的运行时处理
4.2 按作用目标分类 1 2 3 4 @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAnnotation { }
常见的ElementType:
TYPE
:类、接口、枚举
METHOD
:方法
FIELD
:字段
PARAMETER
:参数
CONSTRUCTOR
:构造器
PACKAGE
:包
5. 常见内置注解详解 5.1 基础注解 @Override 1 2 3 4 5 6 public class Child extends Parent { @Override public void method () { System.out.println("子类实现" ); } }
功能 :
编译时检查是否正确重写了父类方法
提高代码可读性
防止方法签名错误
@Deprecated 1 2 3 4 5 6 7 8 9 10 public class OldAPI { @Deprecated(since = "1.5", forRemoval = true) public void oldMethod () { } public void newMethod () { } }
功能 :
标记已废弃的API
编译时产生警告
可以指定废弃版本和是否将被移除
@SuppressWarnings 1 2 3 4 5 6 7 8 9 10 11 12 public class Example { @SuppressWarnings("unchecked") public void method () { List list = new ArrayList (); list.add("item" ); } @SuppressWarnings({"unused", "deprecation"}) private void anotherMethod () { } }
常用警告类型 :
unchecked
:未检查转换
unused
:未使用的变量
deprecation
:使用已废弃的API
rawtypes
:使用原始类型
@SafeVarargs 1 2 3 4 5 6 7 8 public class VarargsExample { @SafeVarargs public static <T> void printAll (T... items) { for (T item : items) { System.out.println(item); } } }
5.2 函数式接口注解 @FunctionalInterface 详细解释 @FunctionalInterface
注解用来标记一个接口是函数式接口 ,这是Java 8引入Lambda表达式后的重要概念。
什么是函数式接口? 函数式接口是只有一个抽象方法 的接口,它可以被Lambda表达式实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @FunctionalInterface public interface Calculator { int calculate (int a, int b) ; default void log () { System.out.println("计算完成" ); } static void info () { System.out.println("这是一个计算器接口" ); } }
Lambda表达式实现 传统方式 vs Lambda方式的对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Calculator add = new Calculator () { @Override public int calculate (int a, int b) { return a + b; } }; Calculator add = (a, b) -> a + b;Calculator multiply = (a, b) -> a * b;Calculator subtract = (a, b) -> a - b;int result1 = add.calculate(5 , 3 ); int result2 = multiply.calculate(4 , 6 ); int result3 = subtract.calculate(10 , 4 );
Lambda表达式语法详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Calculator complex = (int a, int b) -> { System.out.println("正在计算: " + a + " + " + b); int result = a + b; return result; }; Calculator simple = (a, b) -> a + b;Function<Integer, Integer> square = x -> x * x; Supplier<String> greeting = () -> "Hello World" ; Calculator add = (a, b) -> a + b;Calculator verbose = (a, b) -> { System.out.println("开始计算" ); return a + b; };
常见的函数式接口 Java提供了许多内置的函数式接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Supplier<String> stringSupplier = () -> "Hello" ; Supplier<Integer> randomNum = () -> new Random ().nextInt(100 ); Consumer<String> printer = str -> System.out.println(str); Consumer<Integer> logger = num -> System.out.println("数字:" + num); Function<String, Integer> stringLength = str -> str.length(); Function<Integer, String> intToString = num -> "数字:" + num; Predicate<String> isEmpty = str -> str.isEmpty(); Predicate<Integer> isEven = num -> num % 2 == 0 ; BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
实际应用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class CalculatorDemo { public static void main (String[] args) { Calculator add = (a, b) -> a + b; Calculator multiply = (a, b) -> a * b; Calculator max = (a, b) -> Math.max(a, b); performCalculation(10 , 5 , add); performCalculation(10 , 5 , multiply); performCalculation(10 , 5 , max); List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0 ) .collect(Collectors.toList()); List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList()); int sum = numbers.stream() .reduce(0 , (a, b) -> a + b); } public static void performCalculation (int a, int b, Calculator calc) { int result = calc.calculate(a, b); System.out.println("结果:" + result); calc.log(); } }
@FunctionalInterface注解的作用
编译时检查 :确保接口只有一个抽象方法
1 2 3 4 5 @FunctionalInterface public interface InvalidInterface { void method1 () ; void method2 () ; }
文档说明 :明确表示这是一个函数式接口
IDE支持 :开发工具可以提供更好的Lambda支持
与传统接口的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface TraditionalInterface { void method1 () ; void method2 () ; void method3 () ; } @FunctionalInterface public interface FunctionalInterface { void singleMethod () ; default void defaultMethod () {} static void staticMethod () {} }
方法引用(Method Reference) Lambda表达式的进一步简化:
1 2 3 4 5 6 7 8 9 10 11 12 Function<String, Integer> lengthLambda = str -> str.length(); Function<String, Integer> lengthMethodRef = String::length; Consumer<String> printLambda = str -> System.out.println(str); Consumer<String> printMethodRef = System.out::println; Supplier<ArrayList<String>> listSupplier = ArrayList::new ;
总结 :@FunctionalInterface
让Java支持函数式编程,使代码更简洁、更具表达力。Lambda表达式本质上是匿名函数的简写,让我们可以将行为作为参数传递,这是现代Java编程的重要特性!
6. 框架中的常用注解 什么是Spring框架? Spring 是Java企业级开发中最流行的应用框架,它提供了:
依赖注入(DI) :自动管理对象间的依赖关系
面向切面编程(AOP) :统一处理横切关注点(如日志、事务)
事务管理 :简化数据库事务处理
Web开发支持 :MVC架构支持
Spring Boot 是Spring的进化版,它:
自动配置 :根据依赖自动配置Spring应用
内嵌服务器 :无需外部Tomcat,直接运行
简化配置 :减少XML配置,基于注解开发
微服务友好 :易于构建独立的微服务应用
6.1 Spring核心注解详解 组件注解系列 @Component - 通用组件注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class EmailService { public void sendEmail (String to, String message) { System.out.println("发送邮件到: " + to); System.out.println("内容: " + message); } } @Component("customEmailService") public class CustomEmailService { }
@Service - 业务逻辑层注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Service public class UserService { @Autowired private UserRepository userRepository; public User createUser (String username, String email) { if (userRepository.existsByUsername(username)) { throw new IllegalArgumentException ("用户名已存在" ); } User user = new User (); user.setUsername(username); user.setEmail(email); user.setCreatedAt(new Date ()); return userRepository.save(user); } public boolean authenticate (String username, String password) { User user = userRepository.findByUsername(username); return user != null && user.getPassword().equals(password); } }
@Repository - 数据访问层注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Repository public class UserRepository { @Autowired private JdbcTemplate jdbcTemplate; public User findById (Long id) { String sql = "SELECT * FROM users WHERE id = ?" ; return jdbcTemplate.queryForObject(sql, new Object []{id}, (rs, rowNum) -> { User user = new User (); user.setId(rs.getLong("id" )); user.setUsername(rs.getString("username" )); user.setEmail(rs.getString("email" )); return user; }); } public User save (User user) { if (user.getId() == null ) { String sql = "INSERT INTO users (username, email, created_at) VALUES (?, ?, ?)" ; KeyHolder keyHolder = new GeneratedKeyHolder (); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1 , user.getUsername()); ps.setString(2 , user.getEmail()); ps.setTimestamp(3 , new Timestamp (user.getCreatedAt().getTime())); return ps; }, keyHolder); user.setId(keyHolder.getKey().longValue()); } else { String sql = "UPDATE users SET username = ?, email = ? WHERE id = ?" ; jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), user.getId()); } return user; } public boolean existsByUsername (String username) { String sql = "SELECT COUNT(*) FROM users WHERE username = ?" ; int count = jdbcTemplate.queryForObject(sql, Integer.class, username); return count > 0 ; } }
@Controller - Web控制器注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Controller @RequestMapping("/web") public class WebController { @Autowired private UserService userService; @GetMapping("/users") public String listUsers (Model model) { List<User> users = userService.getAllUsers(); model.addAttribute("users" , users); return "user-list" ; } @GetMapping("/users/{id}") public String userDetail (@PathVariable Long id, Model model) { User user = userService.findById(id); model.addAttribute("user" , user); return "user-detail" ; } @PostMapping("/users") public String createUser (@ModelAttribute User user) { userService.createUser(user.getUsername(), user.getEmail()); return "redirect:/web/users" ; } }
依赖注入注解 @Autowired - 自动装配注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 @Service public class OrderService { @Autowired private UserService userService; @Autowired private PaymentService paymentService; private final EmailService emailService; private final InventoryService inventoryService; @Autowired public OrderService (EmailService emailService, InventoryService inventoryService) { this .emailService = emailService; this .inventoryService = inventoryService; } private NotificationService notificationService; @Autowired(required = false) public void setNotificationService (NotificationService notificationService) { this .notificationService = notificationService; } public Order createOrder (Long userId, List<OrderItem> items) { User user = userService.findById(userId); for (OrderItem item : items) { if (!inventoryService.isAvailable(item.getProductId(), item.getQuantity())) { throw new RuntimeException ("库存不足" ); } } Order order = new Order (); order.setUser(user); order.setItems(items); order.setStatus("CREATED" ); boolean paymentSuccess = paymentService.processPayment(order.getTotalAmount()); if (paymentSuccess) { order.setStatus("PAID" ); emailService.sendEmail(user.getEmail(), "订单创建成功" ); if (notificationService != null ) { notificationService.sendPushNotification(user.getId(), "您的订单已创建" ); } } return order; } }
@Qualifier - 指定具体Bean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public interface PaymentService { boolean processPayment (BigDecimal amount) ; } @Service("alipayService") public class AlipayService implements PaymentService { public boolean processPayment (BigDecimal amount) { System.out.println("使用支付宝支付: " + amount); return true ; } } @Service("wechatPayService") public class WechatPayService implements PaymentService { public boolean processPayment (BigDecimal amount) { System.out.println("使用微信支付: " + amount); return true ; } } @Service public class OrderService { @Autowired @Qualifier("alipayService") private PaymentService paymentService; public OrderService (@Qualifier("wechatPayService") PaymentService paymentService) { this .paymentService = paymentService; } }
@Value - 属性值注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Service public class ConfigService { @Value("${app.name}") private String appName; @Value("${app.version}") private String appVersion; @Value("${email.smtp.port}") private int smtpPort; @Value("${app.debug:false}") private boolean debugMode; @Value("${app.max-users:1000}") private int maxUsers; @Value("${java.version}") private String javaVersion; @Value("${HOME}") private String homeDirectory; public void printConfig () { System.out.println("应用名称: " + appName); System.out.println("应用版本: " + appVersion); System.out.println("SMTP端口: " + smtpPort); System.out.println("调试模式: " + debugMode); System.out.println("最大用户数: " + maxUsers); System.out.println("Java版本: " + javaVersion); System.out.println("主目录: " + homeDirectory); } }
6.2 Spring Boot注解详解 @SpringBootApplication - 启动类注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @SpringBootApplication public class MyApplication { public static void main (String[] args) { SpringApplication.run(MyApplication.class, args); } } @Configuration @EnableAutoConfiguration @ComponentScan(basePackages = {"com.example.service", "com.example.repository"}) public class CustomApplication { }
REST API注解系列 @RestController - REST控制器注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 @RestController @RequestMapping("/api/v1") public class UserRestController { @Autowired private UserService userService; @GetMapping("/users") public List<User> getAllUsers () { return userService.getAllUsers(); } @GetMapping("/users/{id}") public ResponseEntity<User> getUserById (@PathVariable Long id) { try { User user = userService.findById(id); return ResponseEntity.ok(user); } catch (UserNotFoundException e) { return ResponseEntity.notFound().build(); } } @PostMapping("/users") public ResponseEntity<User> createUser (@RequestBody CreateUserRequest request) { try { User user = userService.createUser(request.getUsername(), request.getEmail()); return ResponseEntity.status(HttpStatus.CREATED).body(user); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().build(); } } @PutMapping("/users/{id}") public ResponseEntity<User> updateUser (@PathVariable Long id, @RequestBody UpdateUserRequest request) { try { User user = userService.updateUser(id, request.getUsername(), request.getEmail()); return ResponseEntity.ok(user); } catch (UserNotFoundException e) { return ResponseEntity.notFound().build(); } } @DeleteMapping("/users/{id}") public ResponseEntity<Void> deleteUser (@PathVariable Long id) { try { userService.deleteUser(id); return ResponseEntity.noContent().build(); } catch (UserNotFoundException e) { return ResponseEntity.notFound().build(); } } @GetMapping("/users") public ResponseEntity<Page<User>> getUsers ( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "id") String sort) { PageRequest pageRequest = PageRequest.of(page, size, Sort.by(sort)); Page<User> users = userService.getUsers(pageRequest); return ResponseEntity.ok(users); } }
HTTP方法映射注解详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @RestController @RequestMapping("/api/products") public class ProductController { @GetMapping public List<Product> getAllProducts () { return productService.findAll(); } @GetMapping("/{id}") public Product getProduct (@PathVariable Long id) { return productService.findById(id); } @PostMapping public ResponseEntity<Product> createProduct (@RequestBody Product product) { Product created = productService.save(product); return ResponseEntity.status(HttpStatus.CREATED).body(created); } @PutMapping("/{id}") public ResponseEntity<Product> updateProduct (@PathVariable Long id, @RequestBody Product product) { product.setId(id); Product updated = productService.save(product); return ResponseEntity.ok(updated); } @PatchMapping("/{id}") public ResponseEntity<Product> patchProduct (@PathVariable Long id, @RequestBody Map<String, Object> updates) { Product updated = productService.partialUpdate(id, updates); return ResponseEntity.ok(updated); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteProduct (@PathVariable Long id) { productService.delete(id); return ResponseEntity.noContent().build(); } }
参数绑定注解详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @RestController @RequestMapping("/api/search") public class SearchController { @GetMapping("/users/{userId}/orders/{orderId}") public Order getUserOrder (@PathVariable Long userId, @PathVariable Long orderId) { return orderService.findByUserIdAndOrderId(userId, orderId); } @GetMapping("/products/{productId}") public Product getProduct (@PathVariable("productId") Long id) { return productService.findById(id); } @GetMapping("/products") public List<Product> searchProducts ( @RequestParam String keyword, // 必需参数 @RequestParam(required = false) String category, // 可选参数 @RequestParam(defaultValue = "0") BigDecimal minPrice, // 有默认值 @RequestParam(defaultValue = "999999") BigDecimal maxPrice) { return productService.search(keyword, category, minPrice, maxPrice); } @GetMapping("/secure-data") public ResponseEntity<String> getSecureData ( @RequestHeader("Authorization") String authToken, @RequestHeader(value = "User-Agent", required = false) String userAgent, @RequestHeader(defaultValue = "application/json") String accept) { if (!isValidToken(authToken)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } return ResponseEntity.ok("安全数据" ); } @PostMapping("/users") public User createUser (@RequestBody CreateUserRequest request) { return userService.createUser(request); } @PostMapping("/users/form") public String createUserForm (@ModelAttribute User user, Model model) { User created = userService.save(user); model.addAttribute("user" , created); return "user-created" ; } }
REST API完整示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 @RestController @RequestMapping("/api/v1/users") @Validated public class UserApiController { @Autowired private UserService userService; @GetMapping public ResponseEntity<ApiResponse<Page<UserDto>>> getUsers ( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "id") String sort, @RequestParam(defaultValue = "asc") String direction, @RequestParam(required = false) String search) { Sort.Direction sortDirection = "desc" .equalsIgnoreCase(direction) ? Sort.Direction.DESC : Sort.Direction.ASC; Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort)); Page<UserDto> users = userService.findUsers(search, pageable); ApiResponse<Page<UserDto>> response = ApiResponse.<Page<UserDto>>builder() .success(true ) .message("用户列表获取成功" ) .data(users) .timestamp(LocalDateTime.now()) .build(); return ResponseEntity.ok(response); } @GetMapping("/{id}") public ResponseEntity<ApiResponse<UserDto>> getUser (@PathVariable Long id) { try { UserDto user = userService.findById(id); return ResponseEntity.ok(ApiResponse.success("用户信息获取成功" , user)); } catch (UserNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ApiResponse.error("用户不存在" , null )); } } @PostMapping public ResponseEntity<ApiResponse<UserDto>> createUser ( @Valid @RequestBody CreateUserRequest request) { try { UserDto user = userService.createUser(request); return ResponseEntity.status(HttpStatus.CREATED) .body(ApiResponse.success("用户创建成功" , user)); } catch (DuplicateUsernameException e) { return ResponseEntity.status(HttpStatus.CONFLICT) .body(ApiResponse.error("用户名已存在" , null )); } } @PutMapping("/{id}") public ResponseEntity<ApiResponse<UserDto>> updateUser ( @PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) { try { UserDto user = userService.updateUser(id, request); return ResponseEntity.ok(ApiResponse.success("用户更新成功" , user)); } catch (UserNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ApiResponse.error("用户不存在" , null )); } } @DeleteMapping("/{id}") public ResponseEntity<ApiResponse<Void>> deleteUser (@PathVariable Long id) { try { userService.deleteUser(id); return ResponseEntity.ok(ApiResponse.success("用户删除成功" , null )); } catch (UserNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ApiResponse.error("用户不存在" , null )); } } @PostMapping("/{id}/avatar") public ResponseEntity<ApiResponse<String>> uploadAvatar ( @PathVariable Long id, @RequestParam("file") MultipartFile file) { try { String avatarUrl = userService.uploadAvatar(id, file); return ResponseEntity.ok(ApiResponse.success("头像上传成功" , avatarUrl)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error("头像上传失败" , null )); } } } @Data @Builder public class ApiResponse <T> { private boolean success; private String message; private T data; private LocalDateTime timestamp; public static <T> ApiResponse<T> success (String message, T data) { return ApiResponse.<T>builder() .success(true ) .message(message) .data(data) .timestamp(LocalDateTime.now()) .build(); } public static <T> ApiResponse<T> error (String message, T data) { return ApiResponse.<T>builder() .success(false ) .message(message) .data(data) .timestamp(LocalDateTime.now()) .build(); } }
6.3 JPA/Hibernate注解详解 **JPA(Java Persistence API)**是Java的数据持久化标准,Hibernate 是JPA的最流行实现。这些注解用于将Java对象映射到数据库表。
核心实体注解 @Entity - 实体类注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "username", nullable = false, unique = true, length = 50) private String username; @Column(name = "email") private String email; @Column(name = "created_at") private LocalDateTime createdAt; public User () {} public User (String username, String email) { this .username = username; this .email = email; this .createdAt = LocalDateTime.now(); } }
@Table - 表映射注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Entity @Table( name = "user_profiles", // 表名 catalog = "myapp_db", // 数据库名(可选) schema = "public", // 模式名(可选) uniqueConstraints = { // 唯一约束 @UniqueConstraint( name = "uk_user_email", columnNames = {"user_id", "email"} ) }, indexes = { // 索引 @Index(name = "idx_user_email", columnList = "email"), @Index(name = "idx_user_created", columnList = "created_at") } ) public class UserProfile { }
@Id 和主键生成策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 @Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq") @SequenceGenerator(name = "product_seq", sequenceName = "product_sequence", allocationSize = 1) private Long id; @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "product_gen") @TableGenerator(name = "product_gen", table = "id_generator", pkColumnName = "gen_name", valueColumnName = "gen_value", pkColumnValue = "product_id", allocationSize = 1) private Long id; @Id private Long userId; @Id private Long productId; } @IdClass(UserProductId.class) @Entity public class UserProduct { @Id private Long userId; @Id private Long productId; private Integer quantity; } public class UserProductId implements Serializable { private Long userId; private Long productId; @Override public boolean equals (Object o) { } @Override public int hashCode () { } }
@Column - 字段映射注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @Entity public class User { @Column(name = "user_name") private String username; @Column( name = "email_address", // 列名 nullable = false, // 不能为null(相当于NOT NULL约束) unique = true, // 唯一约束 length = 255, // 字符串长度(varchar(255)) columnDefinition = "TEXT" // 自定义列定义 ) private String email; @Column( name = "salary", precision = 10, // 总位数 scale = 2 // 小数位数(decimal(10,2)) ) private BigDecimal salary; @Transient private String tempPassword; @Column(name = "birth_date") @Temporal(TemporalType.DATE) private Date birthDate; @Column(name = "created_at") @Temporal(TemporalType.TIMESTAMP) private Date createdAt; @Column(name = "login_time") @Temporal(TemporalType.TIME) private Date loginTime; @Enumerated(EnumType.STRING) private UserStatus status; @Enumerated(EnumType.ORDINAL) private UserRole role; @Lob @Column(name = "profile_photo") private byte [] photo; @Lob @Column(name = "description") private String description; } public enum UserStatus { ACTIVE, INACTIVE, BANNED, SUSPENDED } public enum UserRole { USER, ADMIN, MODERATOR }
关联关系注解 @OneToOne - 一对一关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "profile_id", referencedColumnName = "id") private UserProfile profile; } @Entity public class UserProfile { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; private String bio; private String avatarUrl; @OneToOne(mappedBy = "profile") private User user; } @Service public class UserService { @Autowired private UserRepository userRepository; public User createUserWithProfile (String username, String firstName, String lastName) { UserProfile profile = new UserProfile (); profile.setFirstName(firstName); profile.setLastName(lastName); User user = new User (); user.setUsername(username); user.setProfile(profile); profile.setUser(user); return userRepository.save(user); } }
@OneToMany - 一对多关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Order> orders = new ArrayList <>(); public void addOrder (Order order) { orders.add(order); order.setUser(this ); } public void removeOrder (Order order) { orders.remove(order); order.setUser(null ); } } @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private BigDecimal totalAmount; private LocalDateTime orderTime; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; } @Service public class OrderService { public Order createOrder (Long userId, BigDecimal amount) { User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException ("用户不存在" )); Order order = new Order (); order.setTotalAmount(amount); order.setOrderTime(LocalDateTime.now()); user.addOrder(order); return orderRepository.save(order); } public List<Order> getUserOrders (Long userId) { User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException ("用户不存在" )); return user.getOrders(); } }
@ManyToMany - 多对多关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY) @JoinTable( name = "user_roles", // 中间表名 joinColumns = @JoinColumn(name = "user_id"), // 当前实体的外键列 inverseJoinColumns = @JoinColumn(name = "role_id") // 关联实体的外键列 ) private Set<Role> roles = new HashSet <>(); public void addRole (Role role) { roles.add(role); role.getUsers().add(this ); } public void removeRole (Role role) { roles.remove(role); role.getUsers().remove(this ); } } @Entity public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; @ManyToMany(mappedBy = "roles") private Set<User> users = new HashSet <>(); } @Entity public class User { @Id private Long id; private String username; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private Set<UserCourse> userCourses = new HashSet <>(); } @Entity public class Course { @Id private Long id; private String name; @OneToMany(mappedBy = "course", cascade = CascadeType.ALL) private Set<UserCourse> userCourses = new HashSet <>(); } @Entity @Table(name = "user_courses") public class UserCourse { @EmbeddedId private UserCourseId id; @ManyToOne @MapsId("userId") @JoinColumn(name = "user_id") private User user; @ManyToOne @MapsId("courseId") @JoinColumn(name = "course_id") private Course course; private LocalDateTime enrollmentDate; private BigDecimal grade; private String status; } @Embeddable public class UserCourseId implements Serializable { private Long userId; private Long courseId; @Override public boolean equals (Object o) { } @Override public int hashCode () { } }
级联操作和获取策略 级联操作(Cascade) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Entity public class User { @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List<Order> orders; @OneToOne(cascade = CascadeType.ALL) private UserProfile profile; @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private Set<Role> roles; }
获取策略(Fetch) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Entity public class User { @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List<Order> orders; @OneToOne(fetch = FetchType.EAGER) private UserProfile profile; } @Repository public class UserRepository extends JpaRepository <User, Long> { @Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id") Optional<User> findByIdWithOrders (@Param("id") Long id) ; @EntityGraph(attributePaths = {"orders", "profile"}) @Query("SELECT u FROM User u WHERE u.id = :id") Optional<User> findByIdWithOrdersAndProfile (@Param("id") Long id) ; }
完整的JPA实体示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 @Entity @Table(name = "orders") @EntityListeners(AuditingEntityListener.class) public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "order_number", nullable = false, unique = true, length = 50) private String orderNumber; @Column(name = "total_amount", nullable = false, precision = 10, scale = 2) private BigDecimal totalAmount; @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) private OrderStatus status; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<OrderItem> orderItems = new ArrayList <>(); @Embedded private Address shippingAddress; @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; @LastModifiedDate @Column(name = "updated_at") private LocalDateTime updatedAt; @CreatedBy @Column(name = "created_by", updatable = false) private String createdBy; @LastModifiedBy @Column(name = "updated_by") private String updatedBy; @PrePersist protected void onCreate () { if (orderNumber == null ) { orderNumber = generateOrderNumber(); } if (status == null ) { status = OrderStatus.PENDING; } } @PreUpdate protected void onUpdate () { } private String generateOrderNumber () { return "ORD" + System.currentTimeMillis(); } public void addOrderItem (OrderItem item) { orderItems.add(item); item.setOrder(this ); } public void removeOrderItem (OrderItem item) { orderItems.remove(item); item.setOrder(null ); } } @Embeddable public class Address { @Column(name = "street") private String street; @Column(name = "city") private String city; @Column(name = "state") private String state; @Column(name = "zip_code") private String zipCode; @Column(name = "country") private String country; } public enum OrderStatus { PENDING("待处理" ), CONFIRMED("已确认" ), SHIPPED("已发货" ), DELIVERED("已送达" ), CANCELLED("已取消" ); private final String description; OrderStatus(String description) { this .description = description; } public String getDescription () { return description; } }
6.4 Bean Validation(数据验证)注解详解 Bean Validation 是Java的数据验证标准,用于验证Java对象的字段值是否符合预期。Spring Boot默认集成了Hibernate Validator(Bean Validation的实现)。
基础验证注解 空值检查注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class UserRegistrationDto { @NotNull(message = "用户名不能为空") @NotBlank(message = "用户名不能为空白") @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间") private String username; @NotEmpty(message = "密码不能为空") @Size(min = 6, max = 50, message = "密码长度必须在6-50个字符之间") private String password; @NotBlank(message = "确认密码不能为空") private String confirmPassword; @NotNull(message = "邮箱不能为空") @NotBlank(message = "邮箱不能为空白") @Email(message = "邮箱格式不正确") private String email; } @RestController @RequestMapping("/api/auth") @Validated public class AuthController { @PostMapping("/register") public ResponseEntity<String> register (@Valid @RequestBody UserRegistrationDto dto) { userService.registerUser(dto); return ResponseEntity.ok("注册成功" ); } @GetMapping("/check-username") public ResponseEntity<Boolean> checkUsername ( @RequestParam @NotBlank @Size(min = 3, max = 20) String username) { boolean available = userService.isUsernameAvailable(username); return ResponseEntity.ok(available); } }
数值范围验证注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class ProductDto { @NotBlank(message = "商品名称不能为空") @Size(max = 100, message = "商品名称不能超过100个字符") private String name; @NotNull(message = "价格不能为空") @DecimalMin(value = "0.01", message = "价格必须大于0.01") @DecimalMax(value = "999999.99", message = "价格不能超过999999.99") @Digits(integer = 6, fraction = 2, message = "价格格式不正确,最多6位整数和2位小数") private BigDecimal price; @Min(value = 0, message = "库存数量不能为负数") @Max(value = 10000, message = "库存数量不能超过10000") private Integer stockQuantity; @Positive(message = "重量必须为正数") private Double weight; @PositiveOrZero(message = "折扣不能为负数") @DecimalMax(value = "1.0", message = "折扣不能超过1.0") private BigDecimal discount; }
字符串格式验证注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class ContactDto { @NotBlank(message = "姓名不能为空") @Size(min = 2, max = 50, message = "姓名长度必须在2-50个字符之间") @Pattern(regexp = "^[\\u4e00-\\u9fa5a-zA-Z\\s]+$", message = "姓名只能包含中文、英文字母和空格") private String name; @Email(message = "邮箱格式不正确") @NotBlank(message = "邮箱不能为空") private String email; @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") @NotBlank(message = "手机号不能为空") private String phoneNumber; @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$", message = "身份证号格式不正确") private String idCard; @Pattern(regexp = "^https?://.+", message = "网址格式不正确") private String website; @Pattern(regexp = "^\\d{6}$", message = "邮政编码必须是6位数字") private String zipCode; }
时间日期验证注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class EventDto { @NotBlank(message = "活动名称不能为空") private String name; @NotNull(message = "生日不能为空") @Past(message = "生日必须是过去的日期") private LocalDate birthday; @NotNull(message = "开始时间不能为空") @FutureOrPresent(message = "开始时间不能早于当前时间") private LocalDateTime startTime; @NotNull(message = "结束时间不能为空") @Future(message = "结束时间必须是未来的时间") private LocalDateTime endTime; @PastOrPresent(message = "创建时间不能是未来时间") private LocalDateTime createdAt; }
高级验证功能 自定义验证注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = PasswordValidator.class) @Documented public @interface ValidPassword { String message () default "密码强度不够:至少8位,包含大小写字母、数字和特殊字符" ; Class<?>[] groups() default {}; Class<? extends Payload >[] payload() default {}; } public class PasswordValidator implements ConstraintValidator <ValidPassword, String> { private static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$" ; private Pattern pattern; @Override public void initialize (ValidPassword constraintAnnotation) { pattern = Pattern.compile(PASSWORD_PATTERN); } @Override public boolean isValid (String password, ConstraintValidatorContext context) { if (password == null ) { return false ; } Matcher matcher = pattern.matcher(password); if (!matcher.matches()) { context.disableDefaultConstraintViolation(); List<String> errors = new ArrayList <>(); if (password.length() < 8 ) { errors.add("密码长度至少8位" ); } if (!password.matches(".*[a-z].*" )) { errors.add("必须包含小写字母" ); } if (!password.matches(".*[A-Z].*" )) { errors.add("必须包含大写字母" ); } if (!password.matches(".*\\d.*" )) { errors.add("必须包含数字" ); } if (!password.matches(".*[@$!%*?&].*" )) { errors.add("必须包含特殊字符(@$!%*?&)" ); } String errorMessage = "密码要求:" + String.join("、" , errors); context.buildConstraintViolationWithTemplate(errorMessage) .addConstraintViolation(); return false ; } return true ; } } public class ChangePasswordDto { @NotBlank(message = "当前密码不能为空") private String currentPassword; @ValidPassword @NotBlank(message = "新密码不能为空") private String newPassword; @NotBlank(message = "确认密码不能为空") private String confirmPassword; }
类级别验证(跨字段验证) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = FieldMatchValidator.class) @Documented public @interface FieldMatch { String message () default "字段不匹配" ; String first () ; String second () ; Class<?>[] groups() default {}; Class<? extends Payload >[] payload() default {}; } public class FieldMatchValidator implements ConstraintValidator <FieldMatch, Object> { private String firstFieldName; private String secondFieldName; @Override public void initialize (FieldMatch constraintAnnotation) { firstFieldName = constraintAnnotation.first(); secondFieldName = constraintAnnotation.second(); } @Override public boolean isValid (Object value, ConstraintValidatorContext context) { try { Object firstObj = getFieldValue(value, firstFieldName); Object secondObj = getFieldValue(value, secondFieldName); boolean valid = Objects.equals(firstObj, secondObj); if (!valid) { context.disableDefaultConstraintViolation(); context.buildConstraintViolationWithTemplate( String.format("%s和%s必须相同" , firstFieldName, secondFieldName)) .addPropertyNode(secondFieldName) .addConstraintViolation(); } return valid; } catch (Exception e) { return false ; } } private Object getFieldValue (Object object, String fieldName) throws Exception { Class<?> clazz = object.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); return field.get(object); } } @FieldMatch(first = "password", second = "confirmPassword", message = "密码和确认密码必须相同") @FieldMatch(first = "email", second = "confirmEmail", message = "邮箱和确认邮箱必须相同") public class UserRegistrationDto { @NotBlank(message = "用户名不能为空") @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间") private String username; @ValidPassword @NotBlank(message = "密码不能为空") private String password; @NotBlank(message = "确认密码不能为空") private String confirmPassword; @Email(message = "邮箱格式不正确") @NotBlank(message = "邮箱不能为空") private String email; @NotBlank(message = "确认邮箱不能为空") private String confirmEmail; }
验证组(Validation Groups) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public interface CreateGroup {}public interface UpdateGroup {}public class UserDto { @NotNull(groups = UpdateGroup.class, message = "更新时ID不能为空") @Null(groups = CreateGroup.class, message = "创建时ID必须为空") private Long id; @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名不能为空") @Size(min = 3, max = 20, groups = {CreateGroup.class, UpdateGroup.class}, message = "用户名长度必须在3-20个字符之间") private String username; @NotBlank(groups = CreateGroup.class, message = "创建时密码不能为空") @ValidPassword(groups = CreateGroup.class) private String password; @Email(groups = {CreateGroup.class, UpdateGroup.class}, message = "邮箱格式不正确") @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}, message = "邮箱不能为空") private String email; } @RestController @RequestMapping("/api/users") public class UserController { @PostMapping public ResponseEntity<User> createUser (@Validated(CreateGroup.class) @RequestBody UserDto dto) { User user = userService.createUser(dto); return ResponseEntity.status(HttpStatus.CREATED).body(user); } @PutMapping("/{id}") public ResponseEntity<User> updateUser (@PathVariable Long id, @Validated(UpdateGroup.class) @RequestBody UserDto dto) { dto.setId(id); User user = userService.updateUser(dto); return ResponseEntity.ok(user); } }
全局异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ValidationErrorResponse> handleValidationException ( MethodArgumentNotValidException ex) { ValidationErrorResponse errorResponse = new ValidationErrorResponse (); errorResponse.setMessage("请求参数验证失败" ); errorResponse.setTimestamp(LocalDateTime.now()); Map<String, String> errors = new HashMap <>(); ex.getBindingResult().getFieldErrors().forEach(error -> { errors.put(error.getField(), error.getDefaultMessage()); }); ex.getBindingResult().getGlobalErrors().forEach(error -> { errors.put(error.getObjectName(), error.getDefaultMessage()); }); errorResponse.setErrors(errors); return ResponseEntity.badRequest().body(errorResponse); } @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<ValidationErrorResponse> handleConstraintViolationException ( ConstraintViolationException ex) { ValidationErrorResponse errorResponse = new ValidationErrorResponse (); errorResponse.setMessage("参数验证失败" ); errorResponse.setTimestamp(LocalDateTime.now()); Map<String, String> errors = new HashMap <>(); ex.getConstraintViolations().forEach(violation -> { String propertyPath = violation.getPropertyPath().toString(); String message = violation.getMessage(); errors.put(propertyPath, message); }); errorResponse.setErrors(errors); return ResponseEntity.badRequest().body(errorResponse); } } @Data public class ValidationErrorResponse { private String message; private LocalDateTime timestamp; private Map<String, String> errors; }
6.5 测试注解详解(JUnit 5 + Spring Boot Test) JUnit 5 是Java的标准测试框架,Spring Boot Test 提供了Spring应用的测试支持。
JUnit 5 核心注解 基础测试注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 class CalculatorTest { private Calculator calculator; @BeforeAll static void setUpClass () { System.out.println("初始化测试类资源" ); } @AfterAll static void tearDownClass () { System.out.println("清理测试类资源" ); } @BeforeEach void setUp () { calculator = new Calculator (); System.out.println("为测试方法准备Calculator实例" ); } @AfterEach void tearDown () { calculator = null ; System.out.println("清理Calculator实例" ); } @Test @DisplayName("测试两个正数相加") void testAddPositiveNumbers () { int a = 5 ; int b = 3 ; int expected = 8 ; int actual = calculator.add(a, b); assertEquals(expected, actual, "5 + 3 应该等于 8" ); } @Test @DisplayName("测试除法操作") void testDivision () { assertEquals(2.0 , calculator.divide(10 , 5 ), 0.001 , "10 / 5 应该等于 2.0" ); } @Test @DisplayName("测试除零异常") void testDivisionByZero () { ArithmeticException exception = assertThrows( ArithmeticException.class, () -> calculator.divide(10 , 0 ), "除零应该抛出ArithmeticException" ); assertEquals("/ by zero" , exception.getMessage()); } @Test @Disabled("暂时禁用,等待修复bug #123") void testComplexCalculation () { } }
参数化测试注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 class ParameterizedTestExample { @ParameterizedTest @DisplayName("测试字符串长度验证") @ValueSource(strings = {"", " ", "test", "hello world"}) void testStringLength (String input) { if (input.trim().isEmpty()) { assertTrue(input.trim().length() == 0 ); } else { assertTrue(input.length() > 0 ); } } @ParameterizedTest @DisplayName("测试数字是否为偶数") @ValueSource(ints = {2, 4, 6, 8, 10}) void testEvenNumbers (int number) { assertTrue(number % 2 == 0 , number + " 应该是偶数" ); } @ParameterizedTest @DisplayName("测试加法运算") @CsvSource({ "1, 2, 3", "5, 7, 12", "-2, 3, 1", "0, 0, 0" }) void testAddition (int a, int b, int expected) { Calculator calculator = new Calculator (); assertEquals(expected, calculator.add(a, b)); } @ParameterizedTest @DisplayName("从文件测试用户数据") @CsvFileSource(resources = "/test-users.csv", numLinesToSkip = 1) void testUserValidation (String username, String email, boolean expectedValid) { User user = new User (username, email); assertEquals(expectedValid, UserValidator.isValid(user)); } @ParameterizedTest @DisplayName("测试密码强度") @MethodSource("providePasswordTestCases") void testPasswordStrength (String password, boolean expectedStrong) { assertEquals(expectedStrong, PasswordValidator.isStrong(password)); } static Stream<Arguments> providePasswordTestCases () { return Stream.of( Arguments.of("123456" , false ), Arguments.of("Password123!" , true ), Arguments.of("weakpass" , false ), Arguments.of("Strong@Pass123" , true ) ); } @ParameterizedTest @DisplayName("测试用户状态") @EnumSource(UserStatus.class) void testUserStatus (UserStatus status) { assertNotNull(status); assertNotNull(status.getDescription()); } @ParameterizedTest @DisplayName("测试自定义参数") @ArgumentsSource(CustomArgumentsProvider.class) void testWithCustomProvider (String input, int expectedLength) { assertEquals(expectedLength, input.length()); } } class CustomArgumentsProvider implements ArgumentsProvider { @Override public Stream<? extends Arguments > provideArguments(ExtensionContext context) { return Stream.of( Arguments.of("hello" , 5 ), Arguments.of("world" , 5 ), Arguments.of("test" , 4 ) ); } }
Spring Boot 测试注解 完整应用上下文测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, // 随机端口启动Web服务器 properties = {"spring.profiles.active=test"} // 激活test配置文件 ) @TestPropertySource(locations = "classpath:application-test.properties") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class UserServiceIntegrationTest { @Autowired private UserService userService; @Autowired private UserRepository userRepository; @Autowired private TestRestTemplate restTemplate; @LocalServerPort private int port; @Test @DisplayName("集成测试:创建用户的完整流程") void testCreateUserIntegration () { String username = "testuser" ; String email = "test@example.com" ; User user = userService.createUser(username, email); assertNotNull(user.getId()); assertEquals(username, user.getUsername()); assertEquals(email, user.getEmail()); Optional<User> savedUser = userRepository.findById(user.getId()); assertTrue(savedUser.isPresent()); assertEquals(username, savedUser.get().getUsername()); } @Test @DisplayName("HTTP接口集成测试") void testUserApiEndpoint () { CreateUserRequest request = new CreateUserRequest ("apiuser" , "api@example.com" ); ResponseEntity<User> response = restTemplate.postForEntity( "http://localhost:" + port + "/api/users" , request, User.class ); assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertNotNull(response.getBody()); assertEquals("apiuser" , response.getBody().getUsername()); } }
Web层测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 @WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Autowired private ObjectMapper objectMapper; @Test @DisplayName("测试获取用户API - 成功案例") void testGetUser_Success () throws Exception { Long userId = 1L ; User mockUser = new User ("testuser" , "test@example.com" ); mockUser.setId(userId); when (userService.findById(userId)).thenReturn(mockUser); mockMvc.perform(get("/api/users/{id}" , userId) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id" ).value(userId)) .andExpect(jsonPath("$.username" ).value("testuser" )) .andExpect(jsonPath("$.email" ).value("test@example.com" )) .andDo(print()); verify(userService, times(1 )).findById(userId); } @Test @DisplayName("测试获取用户API - 用户不存在") void testGetUser_NotFound () throws Exception { Long userId = 999L ; when (userService.findById(userId)).thenThrow(new UserNotFoundException ("用户不存在" )); mockMvc.perform(get("/api/users/{id}" , userId) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); verify(userService, times(1 )).findById(userId); } @Test @DisplayName("测试创建用户API - 成功案例") void testCreateUser_Success () throws Exception { CreateUserRequest request = new CreateUserRequest ("newuser" , "new@example.com" ); User createdUser = new User ("newuser" , "new@example.com" ); createdUser.setId(1L ); when (userService.createUser(anyString(), anyString())).thenReturn(createdUser); mockMvc.perform(post("/api/users" ) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id" ).exists()) .andExpect(jsonPath("$.username" ).value("newuser" )) .andExpect(jsonPath("$.email" ).value("new@example.com" )); verify(userService, times(1 )).createUser("newuser" , "new@example.com" ); } @Test @DisplayName("测试创建用户API - 验证失败") void testCreateUser_ValidationError () throws Exception { CreateUserRequest request = new CreateUserRequest ("" , "invalid-email" ); mockMvc.perform(post("/api/users" ) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.errors" ).exists()) .andExpect(jsonPath("$.errors.username" ).exists()) .andExpect(jsonPath("$.errors.email" ).exists()); verify(userService, never()).createUser(anyString(), anyString()); } }
数据层测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 @DataJpaTest class UserRepositoryTest { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository userRepository; @Test @DisplayName("测试根据用户名查找用户") void testFindByUsername () { User user = new User ("testuser" , "test@example.com" ); entityManager.persistAndFlush(user); Optional<User> found = userRepository.findByUsername("testuser" ); assertTrue(found.isPresent()); assertEquals("testuser" , found.get().getUsername()); assertEquals("test@example.com" , found.get().getEmail()); } @Test @DisplayName("测试根据邮箱查找用户") void testFindByEmail () { User user1 = new User ("user1" , "user1@example.com" ); User user2 = new User ("user2" , "user2@example.com" ); entityManager.persist(user1); entityManager.persist(user2); entityManager.flush(); Optional<User> found = userRepository.findByEmail("user1@example.com" ); assertTrue(found.isPresent()); assertEquals("user1" , found.get().getUsername()); } @Test @DisplayName("测试用户名是否存在") void testExistsByUsername () { User user = new User ("existinguser" , "existing@example.com" ); entityManager.persistAndFlush(user); assertTrue(userRepository.existsByUsername("existinguser" )); assertFalse(userRepository.existsByUsername("nonexistentuser" )); } @Test @DisplayName("测试自定义查询方法") void testFindActiveUsers () { User activeUser = new User ("activeuser" , "active@example.com" ); activeUser.setStatus(UserStatus.ACTIVE); User inactiveUser = new User ("inactiveuser" , "inactive@example.com" ); inactiveUser.setStatus(UserStatus.INACTIVE); entityManager.persist(activeUser); entityManager.persist(inactiveUser); entityManager.flush(); List<User> activeUsers = userRepository.findByStatus(UserStatus.ACTIVE); assertEquals(1 , activeUsers.size()); assertEquals("activeuser" , activeUsers.get(0 ).getUsername()); } }
服务层测试(使用Mock) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @Mock private EmailService emailService; @Mock private PasswordEncoder passwordEncoder; @InjectMocks private UserService userService; @Test @DisplayName("测试创建用户 - 成功案例") void testCreateUser_Success () { String username = "newuser" ; String email = "new@example.com" ; String rawPassword = "password123" ; String encodedPassword = "encoded_password" ; User savedUser = new User (username, email); savedUser.setId(1L ); savedUser.setPassword(encodedPassword); when (userRepository.existsByUsername(username)).thenReturn(false ); when (userRepository.existsByEmail(email)).thenReturn(false ); when (passwordEncoder.encode(rawPassword)).thenReturn(encodedPassword); when (userRepository.save(any(User.class))).thenReturn(savedUser); User result = userService.createUser(username, email, rawPassword); assertNotNull(result); assertEquals(1L , result.getId()); assertEquals(username, result.getUsername()); assertEquals(email, result.getEmail()); assertEquals(encodedPassword, result.getPassword()); verify(userRepository, times(1 )).existsByUsername(username); verify(userRepository, times(1 )).existsByEmail(email); verify(passwordEncoder, times(1 )).encode(rawPassword); verify(userRepository, times(1 )).save(any(User.class)); verify(emailService, times(1 )).sendWelcomeEmail(email, username); } @Test @DisplayName("测试创建用户 - 用户名已存在") void testCreateUser_UsernameExists () { String username = "existinguser" ; String email = "new@example.com" ; when (userRepository.existsByUsername(username)).thenReturn(true ); DuplicateUsernameException exception = assertThrows( DuplicateUsernameException.class, () -> userService.createUser(username, email, "password" ) ); assertEquals("用户名已存在: " + username, exception.getMessage()); verify(userRepository, times(1 )).existsByUsername(username); verify(userRepository, never()).existsByEmail(anyString()); verify(userRepository, never()).save(any(User.class)); verify(emailService, never()).sendWelcomeEmail(anyString(), anyString()); } @Test @DisplayName("测试用户登录 - 成功案例") void testAuthenticate_Success () { String username = "testuser" ; String rawPassword = "password123" ; String encodedPassword = "encoded_password" ; User user = new User (username, "test@example.com" ); user.setPassword(encodedPassword); user.setStatus(UserStatus.ACTIVE); when (userRepository.findByUsername(username)).thenReturn(Optional.of(user)); when (passwordEncoder.matches(rawPassword, encodedPassword)).thenReturn(true ); boolean result = userService.authenticate(username, rawPassword); assertTrue(result); verify(userRepository, times(1 )).findByUsername(username); verify(passwordEncoder, times(1 )).matches(rawPassword, encodedPassword); } @Test @DisplayName("测试用户登录 - 密码错误") void testAuthenticate_WrongPassword () { String username = "testuser" ; String rawPassword = "wrongpassword" ; String encodedPassword = "encoded_password" ; User user = new User (username, "test@example.com" ); user.setPassword(encodedPassword); when (userRepository.findByUsername(username)).thenReturn(Optional.of(user)); when (passwordEncoder.matches(rawPassword, encodedPassword)).thenReturn(false ); boolean result = userService.authenticate(username, rawPassword); assertFalse(result); verify(userRepository, times(1 )).findByUsername(username); verify(passwordEncoder, times(1 )).matches(rawPassword, encodedPassword); } }
7. 自定义注解 7.1 创建自定义注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogExecutionTime { String value () default "" ; boolean enabled () default true ; } public class BusinessService { @LogExecutionTime("用户查询") public User findUser (Long id) { return userRepository.findById(id); } @LogExecutionTime(value = "数据处理", enabled = false) public void processData () { } }
7.2 注解处理器实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Component @Aspect public class LogExecutionTimeAspect { private static final Logger logger = LoggerFactory.getLogger(LogExecutionTimeAspect.class); @Around("@annotation(logExecutionTime)") public Object logExecutionTime (ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable { if (!logExecutionTime.enabled()) { return joinPoint.proceed(); } long startTime = System.currentTimeMillis(); String operation = logExecutionTime.value().isEmpty() ? joinPoint.getSignature().getName() : logExecutionTime.value(); try { Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); logger.info("操作 [{}] 执行时间: {}ms" , operation, endTime - startTime); return result; } catch (Exception e) { logger.error("操作 [{}] 执行失败" , operation, e); throw e; } } }
7.3 通过反射处理注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class AnnotationProcessor { public static void processAnnotations (Object obj) { Class<?> clazz = obj.getClass(); if (clazz.isAnnotationPresent(Component.class)) { Component component = clazz.getAnnotation(Component.class); System.out.println("组件名称: " + component.value()); } Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(LogExecutionTime.class)) { LogExecutionTime annotation = method.getAnnotation(LogExecutionTime.class); System.out.println("方法 " + method.getName() + " 需要记录执行时间: " + annotation.value()); } } Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowired.class)) { System.out.println("字段 " + field.getName() + " 需要自动注入" ); } } } }
8. 注解的最佳实践 8.1 设计原则
单一职责 :每个注解只做一件事
命名清晰 :注解名称应该清楚表达其用途
参数合理 :提供合理的默认值,减少使用复杂度
文档完善 :为注解和参数提供详细文档
8.2 使用建议 1 2 3 4 5 6 7 8 9 10 @AllInOne(log = true, validate = true, cache = true, retry = 3) public void method () {}@Log @Validate @Cacheable @Retry(times = 3) public void method () {}
8.3 性能考虑 1 2 3 4 5 6 7 8 9 10 11 12 public class AnnotationCache { private static final Map<Class<?>, List<Method>> methodCache = new ConcurrentHashMap <>(); public static List<Method> getAnnotatedMethods (Class<?> clazz, Class<? extends Annotation> annotation) { return methodCache.computeIfAbsent(clazz, k -> { return Arrays.stream(k.getDeclaredMethods()) .filter(m -> m.isAnnotationPresent(annotation)) .collect(Collectors.toList()); }); } }
9. 常见错误和解决方案 9.1 注解丢失问题 1 2 3 4 5 6 7 8 9 @Target(ElementType.METHOD) public @interface MyAnnotation {}@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation {}
9.2 继承问题 1 2 3 4 5 @Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service {}
9.3 重复注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Repeatable(Schedules.class) public @interface Schedule { String time () ; } public @interface Schedules { Schedule[] value(); } @Schedule(time = "morning") @Schedule(time = "evening") public void task () {}
10. 学习建议 10.1 循序渐进
理解基础 :先掌握内置注解的使用
框架应用 :学习Spring等框架中注解的使用
自定义注解 :尝试创建自己的注解
深入原理 :了解注解处理器和反射机制
10.2 实践项目
创建一个简单的Web项目,使用Spring Boot注解
实现一个日志记录的自定义注解
编写一个数据验证的注解系统
10.3 进阶学习
学习APT(Annotation Processing Tool)
了解编译时代码生成
研究主流框架的注解实现原理
总结 Java注解是一个强大的元编程工具,它让代码更加简洁、可读性更强。相比C++的宏系统,Java注解提供了更安全、更强大的元数据机制。掌握注解的使用不仅能让你更好地使用现有框架,还能帮你设计出更优雅的API。
注解是对代码的描述,而不是代码本身。正确理解和使用注解,将大大提升Java编程水平。
感谢阅读!如果这篇文章对你有帮助,欢迎点赞和分享。