Java EE/Jakarta EE 三大规范详解:CDI、JPA 与 Bean Validation

Java EE/Jakarta EE 三大规范详解:CDI、JPA 与 Bean Validation 一、CDI:Contexts and Dependency Injection(上下文与依赖注入) 1.

Java EE/Jakarta EE 三大规范详解:CDI、JPA 与 Bean Validation


一、CDI:Contexts and Dependency Injection(上下文与依赖注入)

1. CDI 简介

CDI(Contexts and Dependency Injection)是 Jakarta EE 平台提供的依赖注入与生命周期管理核心规范,最初由 JSR-299 定义,后经 Jakarta EE 社区持续演进。CDI 的核心价值在于通过类型安全的依赖注入机制,将对象创建、作用域管理与业务逻辑彻底解耦,使开发者专注于领域建模而非繁琐的对象装配。

相比传统工厂模式或手动 new 实例化,CDI 提供了声明式编程范式:只需在字段、构造器或 setter 上标注 @Inject,容器便会自动分析类型图,注入匹配的实现实例。这不仅降低了组件间的耦合度,还为 AOP 拦截、事件总线、装饰器模式等企业级扩展提供了统一入口。

CDI 的设计深受 Spring Framework 影响,同时又与 Servlet、JPA、JAX-RS 等 Jakarta EE 规范深度集成,形成了完整的全栈企业级编程模型。对于希望深入理解 Jakarta EE 生态的开发者,建议先阅读 https://plumephp.com/java-spring-boot-fundamentals/ 了解依赖注入的基础思想,再研究本文的 CDI 实战细节。

CDI 是 Jakarta EE 提供的依赖注入和生命周期管理标准,提供:

  • 类型安全的依赖注入机制
  • 生命周期与作用域控制
  • 拦截器、事件系统、装饰器等扩展功能

2. 常用注解说明

注解功能说明
@Inject注入依赖对象
@Named定义 Bean 名称,供 EL 表达式使用
@ApplicationScoped应用作用域,整个应用生命周期
@RequestScoped每个 HTTP 请求周期一个实例
@SessionScoped每个 HTTP 会话一个实例
@Dependent(默认)与注入点生命周期一致
@Produces生产一个注入对象
@Qualifier自定义限定符,用于区分同类 Bean

作用域的语义与实现细节:

@ApplicationScoped 的 Bean 在应用启动后首次被注入时创建,以单例模式存在于整个 JVM 生命周期中,适用于无状态服务、配置读取器或共享缓存对象。@RequestScoped 的生命周期绑定到当前 HTTP 请求线程,请求结束后自动销毁,常用于与用户会话相关的临时数据载体。@SessionScoped 则跨越多个请求,在 HTTP 会话有效期内保持状态,适合购物车、用户偏好等场景。@Dependent 每次注入都会创建新实例,是默认值,适用于那些需要独立生命周期的工具类或短期对象。

限定符(Qualifier)机制:当容器中存在多个同一类型的 Bean 实现时,仅靠类型无法区分注入目标。此时可通过自定义注解标记 @Qualifier,在注入点使用对应注解精准指定所需实现。这比 Spring 的 @Primary 或 @Profile 提供了更细粒度的类型安全控制。

3. CDI 示例

Bean 类定义

@ApplicationScoped
public class GreetingService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}

使用 @Inject 注入 Bean

@RequestScoped
public class UserController {

    @Inject
    private GreetingService greetingService;

    public void handleRequest() {
        System.out.println(greetingService.greet("Alice"));
    }
}

在上述示例中,UserController 无需关心 GreetingService 的创建过程,容器会在处理 HTTP 请求时自动完成注入。构造器注入同样受支持:在构造器上标注 @Inject 可确保 Bean 在实例化时所有依赖均已就绪,避免了字段注入可能导致的空指针风险。

使用 @Produces

public class Resources {
    @Produces
    @RequestScoped
    public EntityManager produceEntityManager() {
        return Persistence.createEntityManagerFactory("myPU").createEntityManager();
    }
}

@Produces 方法为容器提供自定义实例化逻辑,常用于创建第三方库对象(如 EntityManager、DataSource)或需要复杂初始化参数的资源。通过在方法上叠加作用域注解,可以精确控制所生产对象的生命周期。

二、JPA:Java Persistence API(持久化规范)

1. JPA 简介

JPA(Java Persistence API)是 Jakarta EE 标准的对象关系映射(ORM)规范,定义了实体类与关系型数据库表之间的映射规则、实体生命周期管理接口以及面向对象的查询语言 JPQL。作为 Jakarta EE 数据持久层的基石,JPA 屏蔽了不同数据库的底层差异,使开发者能够以面向对象的方式操作关系型数据。

JPA 的核心实现包括 Hibernate、EclipseLink 与 OpenJPA,其中 Hibernate 凭借出色的性能优化与生态完善度成为业界事实标准。关于 ORM 框架的选型与性能对比,可进一步阅读 https://plumephp.com/java-orm-hibernate-mybatis/ 中的深入分析。

JPA 的设计哲学深受领域驱动设计(DDD)影响,通过 @Entity、@Embeddable 与 @Embedded 等注解,支持聚合根、值对象等复杂领域模型的直接映射。同时,JPA 的级联操作(CascadeType)与抓取策略(FetchType.LAZY / EAGER)为关联实体的高效加载提供了灵活控制。

JPA 是 Java EE 标准 ORM 框架,提供实体映射、对象关系管理、查询语言(JPQL)等。

2. 常用注解说明

注解用途说明
@Entity标识实体类
@Table指定数据库表名
@Id主键
@GeneratedValue主键生成策略
@Column字段映射属性
@ManyToOne / @OneToMany关联关系
@Transient不持久化的字段

主键生成策略详解:GenerationType.AUTO 由持久化提供者自动选择最适合的策略;IDENTITY 依赖数据库自增列(如 MySQL AUTO_INCREMENT、PostgreSQL SERIAL),插入后立即获取主键值;SEQUENCE 使用数据库序列(如 Oracle、PostgreSQL),可通过 @SequenceGenerator 预先分配主键区间,减少数据库往返;TABLE 通过代理表维护主键计数器,虽兼容性最好但性能最差,生产环境应尽量避免。

关联关系要点:@ManyToOne 表示多对一关联,通常在多的一方维护外键,且建议配置 FetchType.LAZY 避免 N+1 查询;@OneToMany 表示一对多关联,可通过 mappedBy 指定由对方实体维护关联关系,实现在有向图中避免双向关联的数据冗余存储。

3. 实体类示例

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private int age;

    // Getters & Setters
}

4. 使用 EntityManager

@ApplicationScoped
public class UserRepository {

    @Inject
    private EntityManager em;

    public User findById(Long id) {
        return em.find(User.class, id);
    }

    public void save(User user) {
        em.getTransaction().begin();
        em.persist(user);
        em.getTransaction().commit();
    }
}

EntityManager 是 JPA 的核心操作接口,负责实体对象的 CRUD 与查询执行。在 Jakarta EE 容器中(如 WildFly、Payara),EntityManager 通常由容器注入(@PersistenceContext),事务边界也由容器透明管理(JTA)。Jakarta EE 9+ 已将 javax.persistence 包名迁移至 jakarta.persistence,使用旧包名会导致编译错误。

三、Bean Validation(JSR 380 / Jakarta Bean Validation)

1. Bean Validation 简介

Bean Validation 是 Jakarta EE 标准的声明式数据校验框架,通过注解方式在 Java Bean 的属性上定义约束规则,由验证器引擎在运行时自动执行校验。该规范经历了从 JSR-303(Bean Validation 1.0)到 JSR-380(Bean Validation 2.0),再到 Jakarta Bean Validation 3.0 的演进,现已成为 Jakarta EE 10 的重要组成部分。

相对于在业务代码中编写大量 if-null-then-throw 的手动校验逻辑,Bean Validation 将校验规则与业务逻辑彻底分离,提升了代码的可读性与可维护性。Hibernate Validator 作为 Bean Validation 的参考实现,额外提供了 @URL、@CreditCardNumber、@Range 等实用约束注解,并支持表达式语言(EL)在错误消息中嵌入校验参数值。

关于校验规范的更多实战案例,推荐参考 https://plumephp.com/java-validation-hibernate/ 一文。

用于对 Java Bean 属性进行声明式验证,常与 JPA、REST API 结合使用。

2. 常用注解说明

注解验证内容
@NotNull不为 null
@Size字符串/集合长度限制
@Min/@Max数值最小/最大值
@Email合法的 Email 格式
@Pattern匹配正则表达式
@Valid嵌套验证 Bean

3. 实体类使用 Bean Validation

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    @Size(min = 2, max = 20)
    private String name;

    @Min(18)
    private int age;

    @Email
    private String email;
}

4. 在资源类中触发验证

@Path("/users")
@RequestScoped
public class UserResource {

    @Inject
    private UserRepository userRepo;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createUser(@Valid User user) {
        userRepo.save(user);
        return Response.status(201).build();
    }
}

在 JAX-RS 资源方法中,对参数标注 @Valid 后,JAX-RS 实现(如 RESTEasy、Jersey)会自动在方法调用前执行 Bean Validation;若校验失败,抛出 ConstraintViolationException,由异常映射器转化为 HTTP 400 响应。在 Jakarta EE 9+ 中,javax.validation 已更名为 jakarta.validation,兼容性问题需要特别注意。

5. 全局异常处理(ConstraintViolationException)

@Provider
public class ConstraintViolationMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(ConstraintViolationException ex) {
        List<String> errors = ex.getConstraintViolations().stream()
            .map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())
            .collect(Collectors.toList());

        return Response.status(Response.Status.BAD_REQUEST)
                       .entity(errors)
                       .build();
    }
}

上述异常映射器将校验失败信息收集为结构化列表返回给客户端,便于前端表单进行字段级错误提示。生产环境中可进一步封装为统一的错误响应体(含错误码、时间戳、请求轨迹 ID),并与日志系统联动记录完整校验上下文。

四、三者整合示例:用户注册服务

@Path("/register")
@RequestScoped
public class RegistrationResource {

    @Inject
    private UserRepository repo;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response register(@Valid User user) {
        repo.save(user);
        return Response.status(201).entity("User registered!").build();
    }
}

完整依赖:

<dependencies>
    <!-- CDI -->
    <dependency>
        <groupId>jakarta.enterprise</groupId>
        <artifactId>jakarta.enterprise.cdi-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>

    <!-- JPA -->
    <dependency>
        <groupId>jakarta.persistence</groupId>
        <artifactId>jakarta.persistence-api</artifactId>
        <version>3.1.0</version>
    </dependency>

    <!-- Bean Validation -->
    <dependency>
        <groupId>jakarta.validation</groupId>
        <artifactId>jakarta.validation-api</artifactId>
        <version>3.0.2</version>
    </dependency>

    <!-- Hibernate Validator 实现 -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>8.0.1.Final</version>
    </dependency>

    <!-- H2 数据库(开发用) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>2.2.224</version>
    </dependency>
</dependencies>

上述整合示例展示了 CDI + JPA + Bean Validation 三者协同工作的经典模式:CDI 负责依赖装配,JPA 负责数据持久化,Bean Validation 负责数据质量守门。在实际企业应用中,这三层之上通常还会叠加 Jakarta REST(JAX-RS)、Jakarta Security(认证授权)与 Jakarta Messaging(异步通知),构成完整的企业级应用架构。

五、Jakarta EE 9+ 命名空间迁移与版本演进

1. 从 javax 到 jakarta 的历史背景

2017 年,Oracle 将 Java EE 捐赠给 Eclipse 基金会,项目名称随之更改为 Jakarta EE。由于商标授权限制,原有 javax.* 命名空间无法继续使用。Eclipse 基金会经社区投票,决定从 Jakarta EE 9 开始将所有 API 包名由 javax 批量迁移至 jakarta。这一变更虽然表面上看仅是字符串替换,实则波及整个 Java 企业生态:所有依赖 Jakarta EE 的框架、库与应用均需同步升级。

核心规范包名对照:

原 Java EE 包名新 Jakarta EE 包名规范版本
javax.enterprise.contextjakarta.enterprise.contextCDI 4.0
javax.persistencejakarta.persistenceJPA 3.1
javax.validationjakarta.validationBean Validation 3.0
javax.ws.rsjakarta.ws.rsREST 3.1
javax.servletjakarta.servletServlet 6.0

2. 版本演进路线

Jakarta EE 8 是最后一个保留 javax 前缀的版本,与 Java EE 8 功能完全等价。Jakarta EE 9 完成了命名空间迁移,但未引入新功能,被称为"纯净迁移"版本。Jakarta EE 9.1 增加了对 Java SE 11 的支持。Jakarta EE 10 正式发布于 2022 年,在 CDI 4.0 中引入了 CDI-Lite 子集,专为微服务与云原生场景优化。Jakarta EE 11 预计将进一步收紧 API,提升模块化支持,并与虚拟线程(Project Loom)进行深度适配。

3. 迁移实战:代码兼容性处理

// Jakarta EE 9+ 正确写法
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;

@ApplicationScoped
public class ModernService {

    @Inject
    private LegacyRepository repo;

    public void create(@NotNull String name) {
        repo.save(name);
    }
}

在大型遗留系统中,可采用 Apache Tomcat Migration Tool for Jakarta EE(jakartaee-migration)工具对 WAR/JAR 包进行字节码级自动重命名。Maven 项目中,建议使用 maven-shade-plugin 配合 jakarta 重定位规则,或分阶段引入兼容层框架(如 Spring Framework 6.x 已全面迁移至 jakarta 命名空间)。

六、JPA 高级查询:JPQL、Criteria API、原生 SQL 与性能策略

1. JPQL 面向对象查询

JPQL(Java Persistence Query Language)是 JPA 的面向对象查询语言,语法与 SQL 相似但操作的是实体类与属性而非数据库表与列。JPQL 查询在编译期仅做语法检查,真正的语义验证与 SQL 转换发生在持久化提供者执行阶段。

// 命名查询定义
@Entity
@NamedQueries({
    @NamedQuery(
        name = "User.findByNameLike",
        query = "SELECT u FROM User u WHERE u.name LIKE :pattern"
    )
})
public class User { /* ... */ }

// 使用命名查询
public List<User> searchUsers(String keyword) {
    TypedQuery<User> query = em.createNamedQuery("User.findByNameLike", User.class);
    query.setParameter("pattern", "%" + keyword + "%");
    return query.getResultList();
}

命名查询在实体类加载时即被解析缓存,避免了每次请求重复解析 JPQL 的开销,适合高频调用的固定业务查询。

2. Criteria API 类型安全查询

Criteria API 是 JPA 2.0 引入的强类型查询构建 API,彻底消除了 JPQL 字符串拼接带来的拼写错误与注入风险。

public List<User> findUsersByAgeRange(int minAge, int maxAge) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<User> cq = cb.createQuery(User.class);
    Root<User> root = cq.from(User.class);

    Predicate ageBetween = cb.between(root.get("age"), minAge, maxAge);
    cq.where(ageBetween);
    cq.orderBy(cb.desc(root.get("id")));

    return em.createQuery(cq).getResultList();
}

Criteria API 虽然在代码量上略多于 JPQL,但具备 IDE 自动补全、编译期类型检查与动态条件组合三大优势,特别适合查询条件随用户输入动态变化的复杂搜索场景(如电商平台的组合筛选)。

3. 原生 SQL 与结果映射

当 JPQL 无法表达复杂的统计分析、窗口函数或数据库专属语法时,可使用原生 SQL 查询配合结果集映射。

public List<Object[]> getUserStats() {
    String sql = """
        SELECT u.department, COUNT(*), AVG(u.age)
        FROM users u
        GROUP BY u.department
        HAVING COUNT(*) > 5
        ORDER BY COUNT(*) DESC
        """;
    Query query = em.createNativeQuery(sql);
    return query.getResultList();
}

4. 分页与二级缓存策略

public List<User> findPage(int page, int size) {
    TypedQuery<User> query = em.createQuery("SELECT u FROM User u", User.class);
    query.setFirstResult(page * size);  // 起始偏移量
    query.setMaxResults(size);          // 每页数量

    // 启用查询缓存(需配合 ehcache 或 Caffeine 等缓存实现)
    query.setHint("org.hibernate.cacheable", true);
    return query.getResultList();
}

JPA 的二级缓存由 Cache 接口统一管理,适合读多写少的实体数据。启用二级缓存需要在 persistence.xml 中配置缓存区域工厂,并在需要缓存的实体上标注 @Cacheable 或 @org.hibernate.annotations.Cache。

对于更深入的 JPA 性能优化与缓存实践,可参考 https://plumephp.com/java-jvm-performance-tuning/ 中的系统调优方法论。

七、CDI 事件驱动架构:观察者模式、异步事件与事务性事件

1. 同步事件基础

CDI 的事件机制实现了观察者模式(Observer Pattern):事件生产者通过 Event<T> 接口触发事件,消费者通过 @Observes 标注监听特定类型的事件。该机制天然解耦了事件源与处理器,使系统各模块可以独立演进。

// 定义领域事件
public class OrderCreatedEvent {
    private final Long orderId;
    private final BigDecimal amount;

    public OrderCreatedEvent(Long orderId, BigDecimal amount) {
        this.orderId = orderId;
        this.amount = amount;
    }
    // getters ...
}

// 事件生产者
@ApplicationScoped
public class OrderService {

    @Inject
    private Event<OrderCreatedEvent> event;

    public void createOrder(Long orderId, BigDecimal amount) {
        // 执行业务逻辑 ...
        event.fire(new OrderCreatedEvent(orderId, amount));
    }
}

// 事件消费者
@ApplicationScoped
public class NotificationHandler {

    public void onOrderCreated(@Observes OrderCreatedEvent event) {
        System.out.println("订单已创建: " + event.getOrderId()
            + ", 金额: " + event.getAmount());
    }
}

2. 限定符实现发布-订阅路由

当同一事件类型触发多个不同业务动作时,可通过自定义限定符实现事件路由。

// 自定义限定符
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface AsyncProcessing { }

// 生产者带限定符触发
@Inject
@AsyncProcessing
private Event<OrderCreatedEvent> asyncEvent;

// 消费者仅响应该限定符事件
public void asyncHandle(@Observes @AsyncProcessing OrderCreatedEvent event) {
    // 异步邮件发送逻辑
}

3. 异步事件与事务性事件

Jakarta EE 9+(CDI 4.0)与 Weld 等实现支持异步事件发送与事务性事件观察:

// 异步触发事件(fireAsync 返回 CompletionStage)
public void createOrderAsync(Long orderId, BigDecimal amount) {
    event.fireAsync(new OrderCreatedEvent(orderId, amount))
         .thenRun(() -> System.out.println("异步事件处理完成"));
}

// 异步消费者
public void asyncObserver(@ObservesAsync OrderCreatedEvent event) {
    // 在独立线程中执行
}

// 事务性事件:仅在事务提交成功后触发
public void onTxSuccess(@Observes(during = TransactionPhase.AFTER_SUCCESS)
                        OrderCreatedEvent event) {
    // 确保订单已持久化后再发邮件,避免事务回滚导致不一致
}

异步事件特别适用于 IO 密集型操作(如发送邮件、调用外部 API),避免阻塞主业务线程。事务性事件则通过 CDI 与 JTA 事务管理器的深度集成,解决了业务数据持久化与后续动作之间的原子性难题。

CDI 事件模型与并发编程密切相关,对事件处理中的线程安全与并发控制感兴趣的朋友,可以参考 https://plumephp.com/java-concurrent-programming/。

八、Bean Validation 自定义约束与跨字段验证

1. 自定义单字段约束

当标准注解无法满足业务校验需求时,可通过组合 @Constraint、验证器实现与默认消息模板创建自定义约束。

// 定义约束注解
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
    String message() default "手机号格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 验证器实现
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {

    private static final Pattern PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isEmpty()) {
            return true; // 使用 @NotNull 处理空值
        }
        return PATTERN.matcher(value).matches();
    }
}

2. 跨字段验证实现

跨字段验证(如确认密码与密码一致、开始日期小于结束日期)需要使用类级别约束。

// 类级别约束注解
@Constraint(validatedBy = DateRangeValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidDateRange {
    String message() default "结束日期必须晚于开始日期";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

// 需要验证的实体
@Entity
@ValidDateRange
public class EventPlan {

    @Id
    @GeneratedValue
    private Long id;

    private LocalDate startDate;
    private LocalDate endDate;

    // getters & setters
}

// 类级别验证器
public class DateRangeValidator implements ConstraintValidator<ValidDateRange, EventPlan> {

    @Override
    public boolean isValid(EventPlan plan, ConstraintValidatorContext context) {
        if (plan.getStartDate() == null || plan.getEndDate() == null) {
            return true;
        }
        boolean valid = plan.getEndDate().isAfter(plan.getStartDate());

        if (!valid) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
                   .addPropertyNode("endDate")
                   .addConstraintViolation();
        }
        return valid;
    }
}

3. 分组校验与顺序校验

在复杂的业务流程中(如草稿保存 vs 正式提交),不同场景需要执行不同的校验规则。Bean Validation 通过分组接口实现这一需求。

public interface OnCreate { }
public interface OnUpdate { }

@Entity
public class Product {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull(groups = OnCreate.class)
    @Size(min = 2, max = 50, groups = {OnCreate.class, OnUpdate.class})
    private String name;

    @NotNull(groups = OnCreate.class)
    @Min(value = 0, groups = {OnCreate.class, OnUpdate.class})
    private BigDecimal price;
}

// 仅在创建时校验 id 存在性
public Response create(@Validated(OnCreate.class) Product product) { ... }
public Response update(@Validated(OnUpdate.class) Product product) { ... }

分组校验在 RESTful API 的多态场景中极为实用,一份实体类即可覆盖不同生命周期的校验需求,避免了维护多个 DTO 类的冗余。

九、Micronaut 与 Quarkus 对 Jakarta EE 规范的现代兼容实践

1. 云原生 Java 的崛起

传统应用服务器(WildFly、WebLogic、WebSphere)以重量级运行时与长启动周期著称,在云原生与 Serverless 时代面临性能瓶颈。Micronaut 与 Quarkus 作为新一代云原生 Java 框架,在设计之初便将快速启动、低内存占用与 GraalVM 原生镜像编译作为核心目标,同时对 Jakarta EE 关键规范(CDI、JPA、Bean Validation、JAX-RS)提供了兼容实现。

关于 GraalVM 原生编译的深入细节,可阅读 https://plumephp.com/java-spring-native-graalvm/。

2. Quarkus 中的 Jakarta EE 实践

Quarkus 官方扩展了对 Jakarta EE 9+ 规范的全面支持,大量采用 jakarta.* 命名空间。其 ArC(Another CDI Container)实现了 CDI Lite 规范,通过构建期字节码生成替代运行期反射,显著提升了依赖注入性能。

// Quarkus 控制器示例(Jakarta REST + CDI + Bean Validation)
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@Path("/api/users")
public class UserResource {

    @Inject
    UserService userService;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(@Valid UserDto dto) {
        User user = userService.create(dto);
        return Response.status(201).entity(user).build();
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User getById(@PathParam("id") Long id) {
        return userService.findById(id);
    }
}

// 实体与验证 DTO
import jakarta.validation.constraints.*;

public class UserDto {

    @NotBlank(message = "用户名不能为空")
    @Size(max = 30)
    private String username;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Min(1)
    @Max(150)
    private int age;
}

Quarkus Dev Mode 支持热重载,修改代码后无需重新编译即可看到效果,极大提升了开发效率。同时,Quarkus 将 Hibernate ORM 与 Hibernate Validator 打包为同一扩展,只需引入单一依赖即可同时获得 JPA 与 Bean Validation 能力。

3. Micronaut 中的 Jakarta EE 兼容

Micronaut 采用 AOP 编译期代理而非 JDK 动态代理或 CGLIB,消除了反射带来的性能损耗。Micronaut Data 作为其数据访问层,支持 JPA 注解实体的 Repository 模式;Micronaut Validation 则集成了 Hibernate Validator,提供与 Jakarta Bean Validation 一致的校验体验。

// Micronaut Repository(兼容 JPA 实体)
import io.micronaut.data.annotation.*;
import io.micronaut.data.repository.CrudRepository;
import jakarta.validation.constraints.NotNull;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {

    // Micronaut Data 自动生成查询实现
    List<User> findByNameContaining(String name);

    @Query("SELECT u FROM User u WHERE u.age > :minAge")
    List<User> findAdults(@NotNull int minAge);
}

// Micronaut 控制器中的 Bean Validation
import io.micronaut.http.annotation.*;
import jakarta.validation.Valid;

@Controller("/users")
public class UserController {

    @Post
    public HttpResponse<User> create(@Body @Valid UserDto dto) {
        // 校验失败时自动返回 400 Bad Request
        return HttpResponse.ok();
    }
}

4. 选型建议

维度QuarkusMicronaut传统 Jakarta EE
启动时间亚秒级(原生镜像 < 50ms)亚秒级秒级至分钟级
内存占用极低(容器化 < 60MB)低较高
CDI 实现ArC(CDI Lite 兼容)Micronaut IOC(类似语义)Weld(完整 CDI)
JPA 支持Hibernate ORM PanacheMicronaut Data JPAHibernate / EclipseLink
原生镜像一流支持(Oracle GraalVM)支持不支持或实验性
反应式编程Mutiny / Vert.x 集成Project Reactor / RxJavaJakarta Concurrency

对于需要快速启动、低延迟的容器化微服务场景,Quarkus 与 Micronaut 是首选;若企业已深度依赖 Jakarta EE 完整规范的 ecosystem(如 EJB、JMS、JCA),传统应用服务器仍具不可替代的价值。关于微服务架构设计的更多思考,请参阅 https://plumephp.com/java-microservice-api-design/。

十、小结

技术规范主要功能常用场景
CDI类型安全依赖注入,生命周期管理控制反转、组件解耦
JPAORM 映射、JPQL 查询数据持久化、实体建模
Bean Validation表达式级校验、嵌套验证表单验证、REST 请求验证

本文系统梳理了 Jakarta EE 三大核心规范(CDI、JPA、Bean Validation)的基础用法与高级特性,并扩展探讨了 Jakarta EE 9+ 命名空间迁移、JPA 高级查询与性能策略、CDI 事件驱动架构、自定义验证约束以及 Quarkus / Micronaut 的现代兼容实践。掌握这些内容,开发者不仅能够在传统 Jakarta EE 应用服务器上构建稳健的企业级应用,还能顺利将技能迁移到云原生 Java 框架中,迎接容器化与 Serverless 时代的挑战。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计