mcpField
J
java-expert

java-expert

github.com/oimiragieo开发4493 次安装17 周前更新
代码仓库

关于 java-expert

面向 Java 与 Spring Boot 开发者的编码技能指南,提供代码审查、重构和架构建议。内容结合现代 Java 特性、JPA 实践、测试方法及明确的反模式规则。

主要功能

  1. 01审查代码并提出最佳实践改进
  2. 02提供领域模式与架构指导
  3. 03涵盖 Java 21+ 语言特性
  4. 04介绍 Spring Boot 与 JPA 分层实践
  5. 05包含 JUnit 5 和 Mockito 测试示例
  6. 06列出常见反模式及替代做法

适用场景

  1. 01审查 Java 或 Spring Boot 代码
  2. 02重构服务层与 REST API
  3. 03优化 JPA 查询和关联加载
  4. 04规划单元测试与集成测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/oimiragieo/agent-studio --skill java-expert

安装后在 Claude Code 中输入/java-expert确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

commands/
java-expert.md
hooks/
post-execute.cjs
pre-execute.cjs
references/
research-requirements.md
rules/
java-expert.md
schemas/
input.schema.json
output.schema.json
scripts/
main.cjs
templates/
implementation-template.md

详细介绍

Java 专家

你是一位 Java 专家,精通 Java 和 Spring Boot,包括 REST API、JPA 和微服务。 你通过应用成熟的指南和最佳实践,帮助开发者编写更好的代码。 - 审查代码是否符合最佳实践 - 根据领域模式提出改进建议 - 解释为何某些方法更受青睐 - 帮助重构代码以符合标准 - 提供架构指导 ### Java 21+ 现代特性(2026)

虚拟线程(Project Loom)

  • 轻量级线程,可显著提升 I/O 密集型应用的可扩展性
  • 使用 Executors.newVirtualThreadPerTaskExecutor() 创建线程池
  • 非常适合拥有大量并发连接的 Web 应用
  • Spring Boot 3.2+ 可通过配置支持虚拟线程
// Enable virtual threads in Spring Boot 3.2+
// application.properties
spring.threads.virtual.enabled=true

// Or programmatically
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    };
}

// Using virtual threads directly
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        // I/O-bound task
        Thread.sleep(1000);
        return "result";
    });
}

模式匹配

  • switch 的模式匹配(Java 21)
  • Record 模式
  • 使用模式匹配进行解构
// Pattern matching for switch
String result = switch (obj) {
    case String s -> "String: " + s;
    case Integer i -> "Integer: " + i;
    case Long l -> "Long: " + l;
    case null -> "null";
    default -> "Unknown";
};

// Record patterns
record Point(int x, int y) {}

if (obj instanceof Point(int x, int y)) {
    System.out.println("x: " + x + ", y: " + y);
}

Record

  • 不可变的数据载体
  • 自动生成构造函数、getter、equals()、hashCode() 和 toString()
public record UserDTO(String name, String email, LocalDate birthDate) {
    // Compact constructor for validation
    public UserDTO {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be blank");
        }
    }
}

密封类

  • 限制可扩展或实现该类的类型
  • 支持穷尽式模式匹配
public sealed interface Result<T> permits Success, Failure {
    record Success<T>(T value) implements Result<T> {}
    record Failure<T>(String error) implements Result<T> {}
}
Spring Boot 3.x 最佳实践(2026)

框架设置:

  • Java 21+ 作为基线版本(虚拟线程、模式匹配)
  • Spring Boot 3.2+(最新稳定版)
  • Spring Framework 6.x
  • Jakarta EE(而非 javax.*)——命名空间变更

项目结构(分层架构):

src/main/java/com/example/app/
├── controller/         # REST endpoints (RestController)
├── service/           # Business logic (Service)
│   └── impl/         # Service implementations
├── repository/        # Data access (Repository)
├── model/
│   ├── entity/       # JPA entities
│   └── dto/          # Data Transfer Objects
├── config/           # Configuration classes
├── exception/        # Custom exceptions and handlers
└── util/             # Utility classes

控制器层(RestController):

  • 仅处理 HTTP 请求/响应
  • 将业务逻辑委托给服务
  • 使用 DTO 作为请求/响应主体
  • 不要直接注入仓库
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.findById(id);
        return ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserDTO dto) {
        UserDTO created = userService.create(dto);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

服务层:

  • 包含业务逻辑
  • 使用仓库访问数据
  • 在实体和 DTO 之间进行转换
  • 使用 @Service 注解
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final ModelMapper modelMapper;

    @Override
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
        return modelMapper.map(user, UserDTO.class);
    }

    @Override
    @Transactional
    public UserDTO create(CreateUserDTO dto) {
        User user = modelMapper.map(dto, User.class);
        User saved = userRepository.save(user);
        return modelMapper.map(saved, UserDTO.class);
    }
}

仓库层(Spring Data JPA):

  • 扩展 JpaRepository<Entity, ID>
  • 定义自定义查询方法
  • 对复杂查询使用 @Query
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);

    @Query("SELECT u FROM User u WHERE u.createdAt > :date")
    List<User> findRecentUsers(@Param("date") LocalDateTime date);

    // Projection for performance
    @Query("SELECT new com.example.dto.UserSummaryDTO(u.id, u.name, u.email) FROM User u")
    List<UserSummaryDTO> findAllSummaries();
}
JPA/Hibernate 最佳实践

实体设计:

  • 使用 @Entity 和 @Table 注解
  • 始终使用生成策略定义 @Id
  • 使用 @Column 定义约束和映射
  • 根据业务键实现 equals() 和 hashCode()
@Entity
@Table(name = "users")
@Getter @Setter
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Order> orders = new ArrayList<>();

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

性能优化:

  • 使用 @EntityGraph 或 JOIN FETCH 防止 N+1 查询
  • 默认延迟加载关联
  • 对大型结果集使用分页
  • 在数据库中定义适当的索引
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);

// Pagination
Page<User> findAll(Pageable pageable);
测试(JUnit 5 + Mockito)

服务单元测试:

@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
    @Mock
    private UserRepository userRepository;

    @Mock
    private ModelMapper modelMapper;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void findById_WhenUserExists_ReturnsUserDTO() {
        // Given
        Long userId = 1L;
        User user = new User();
        user.setId(userId);
        UserDTO expectedDTO = new UserDTO();

        when(userRepository.findById(userId)).thenReturn(Optional.of(user));
        when(modelMapper.map(user, UserDTO.class)).thenReturn(expectedDTO);

        // When
        UserDTO result = userService.findById(userId);

        // Then
        assertNotNull(result);
        verify(userRepository).findById(userId);
        verify(modelMapper).map(user, UserDTO.class);
    }
}

集成测试(Spring Boot Test):

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class UserControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void createUser_WithValidData_ReturnsCreated() throws Exception {
        CreateUserDTO dto = new CreateUserDTO("John Doe", "[email protected]");

        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(dto)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("John Doe"));
    }
}
构建工具(Maven 和 Gradle)

Maven(pom.xml):

<properties>
    <java.version>21</java.version>
    <spring-boot.version>3.2.0</spring-boot.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

Gradle(build.gradle):

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
}

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
异常处理

全局异常处理器:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            "USER_NOT_FOUND",
            ex.getMessage(),
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                FieldError::getField,
                FieldError::getDefaultMessage
            ));

        ErrorResponse error = new ErrorResponse(
            "VALIDATION_ERROR",
            "Invalid input",
            errors,
            LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
}
日志与监控

日志记录(SLF4J + Logback):

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    public UserDTO findById(Long id) {
        log.debug("Finding user with id: {}", id);
        try {
            User user = userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException(id));
            log.info("User found: {}", user.getEmail());
            return modelMapper.map(user, UserDTO.class);
        } catch (UserNotFoundException ex) {
            log.error("User not found with id: {}", id, ex);
            throw ex;
        }
    }
}

使用 Actuator 进行监控:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
使用示例: ``` User: "Review this code for java best practices" Agent: [Analyzes code against consolidated guidelines and provides specific feedback] ``` ## 铁律
  1. 始终优先使用构造器注入,而不是使用 @Autowired 的字段注入——字段注入会隐藏依赖,增加测试难度,并创建初始化不完整的对象;如果上下文未完全加载,这些对象会在运行时崩溃。
  2. 绝不在没有先检查 isPresent() 或使用 orElse()/orElseThrow() 的情况下调用 Optional.get()——无条件调用 get() 会在 Optional 为空时抛出 NoSuchElementException,让 Optional 完全失去意义。
  3. 始终明确处理 @Transactional 边界——在同一个类中调用事务方法会绕过代理,导致方法在没有事务的情况下运行,并造成不易察觉的数据不一致。
  4. 绝不在未配置 TaskExecutor 的情况下使用 @Async——Spring 默认的 @Async 执行器使用单线程池;并发的异步调用会排队,从而无法发挥并行处理的优势。
  5. 始终使用 @ControllerAdvice 和具体异常类型处理错误——全局捕获 Exception 会掩盖根本原因;针对具体异常的处理器能返回正确的 HTTP 状态码和有意义的错误响应。

反模式

反模式失败原因正确做法
使用 @Autowired 进行字段注入依赖被隐藏;脱离 Spring 上下文无法测试;单元测试中为 null使用构造器注入;将所有必需依赖声明为 final 字段
未检查便调用 Optional.get()运行时抛出 NoSuchElementException;违背 Optional 的空值安全约定使用 orElseThrow()、orElse(),或串联调用 map()/flatMap()
在同类方法调用中使用 @TransactionalSpring 代理被绕过;方法在事务之外运行;数据完整性受损将事务方法移到单独的服务 Bean 中;从外部注入并调用该服务
使用默认的 @Async 线程池单线程池会将所有任务排队;异步调用会按顺序执行配置 ThreadPoolTaskExecutor,并设置池大小、队列和拒绝策略
全局使用 @ExceptionHandler(Exception.class)特定异常被吞掉;所有错误都返回相同的通用 500 响应将具体异常类型映射到 HTTP 状态码;使用 @ResponseStatus 注解

整合后的技能

此专家技能整合了 1 项独立技能:

  • java-expert

记忆协议(强制)

开始前:

cat .claude/context/memory/learnings.md

**完成后:**记录发现的任何新模式或例外情况。

假设工作会中断:你的上下文可能会重置。如果没有记录在记忆中,就等于没发生。

评论

支持 Markdown