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.context | jakarta.enterprise.context | CDI 4.0 |
javax.persistence | jakarta.persistence | JPA 3.1 |
javax.validation | jakarta.validation | Bean Validation 3.0 |
javax.ws.rs | jakarta.ws.rs | REST 3.1 |
javax.servlet | jakarta.servlet | Servlet 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. 选型建议
| 维度 | Quarkus | Micronaut | 传统 Jakarta EE |
|---|---|---|---|
| 启动时间 | 亚秒级(原生镜像 < 50ms) | 亚秒级 | 秒级至分钟级 |
| 内存占用 | 极低(容器化 < 60MB) | 低 | 较高 |
| CDI 实现 | ArC(CDI Lite 兼容) | Micronaut IOC(类似语义) | Weld(完整 CDI) |
| JPA 支持 | Hibernate ORM Panache | Micronaut Data JPA | Hibernate / EclipseLink |
| 原生镜像 | 一流支持(Oracle GraalVM) | 支持 | 不支持或实验性 |
| 反应式编程 | Mutiny / Vert.x 集成 | Project Reactor / RxJava | Jakarta Concurrency |
对于需要快速启动、低延迟的容器化微服务场景,Quarkus 与 Micronaut 是首选;若企业已深度依赖 Jakarta EE 完整规范的 ecosystem(如 EJB、JMS、JCA),传统应用服务器仍具不可替代的价值。关于微服务架构设计的更多思考,请参阅 https://plumephp.com/java-microservice-api-design/。
十、小结
| 技术规范 | 主要功能 | 常用场景 |
|---|---|---|
| CDI | 类型安全依赖注入,生命周期管理 | 控制反转、组件解耦 |
| JPA | ORM 映射、JPQL 查询 | 数据持久化、实体建模 |
| Bean Validation | 表达式级校验、嵌套验证 | 表单验证、REST 请求验证 |
本文系统梳理了 Jakarta EE 三大核心规范(CDI、JPA、Bean Validation)的基础用法与高级特性,并扩展探讨了 Jakarta EE 9+ 命名空间迁移、JPA 高级查询与性能策略、CDI 事件驱动架构、自定义验证约束以及 Quarkus / Micronaut 的现代兼容实践。掌握这些内容,开发者不仅能够在传统 Jakarta EE 应用服务器上构建稳健的企业级应用,还能顺利将技能迁移到云原生 Java 框架中,迎接容器化与 Serverless 时代的挑战。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。