Modern Java Engineering
Chapitre 14
14 - Jakarta EE
14 - Jakarta EE
Cours 14 : Jakarta EE
1. Servlets
1.1 Servlet Lifecycle
Loading → Initialization (init) → Service (doGet/doPost) → Destruction (destroy)
1.2 Servlet simple
@WebServlet("/api/orders/*")
public class OrderServlet extends HttpServlet {
@Inject
private OrderService orderService;
@Override
public void init() throws ServletException {
// Initialisation du servlet
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json");
String orderId = req.getPathInfo().substring(1);
Order order = orderService.findById(orderId);
resp.getWriter().write(convertToJson(order));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Order order = parseBody(req, Order.class);
Order created = orderService.create(order);
resp.setStatus(HttpServletResponse.SC_CREATED);
resp.getWriter().write(convertToJson(created));
}
}
1.3 Filters
@WebFilter("/api/*")
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) request;
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long duration = System.currentTimeMillis() - start;
log.info("{} {} - {}ms", httpReq.getMethod(),
httpReq.getRequestURI(), duration);
}
}
@WebFilter("/api/admin/*")
public class SecurityFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) request;
String token = httpReq.getHeader("Authorization");
if (token == null || !validateToken(token)) {
((HttpServletResponse) response).setStatus(401);
return;
}
chain.doFilter(request, response);
}
}
1.4 Listeners
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
// Initialisation au démarrage de l'application
log.info("Application démarrée");
}
@Override
public void contextDestroyed(ServletContextEvent event) {
// Nettoyage à l'arrêt
log.info("Application arrêtée");
}
}
@WebListener
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
log.info("Session créée: {}", event.getSession().getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
log.info("Session détruite: {}", event.getSession().getId());
}
}
2. JAX-RS (REST)
2.1 Endpoint REST
@Path("/api/orders")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class OrderResource {
@Inject
private OrderService orderService;
@GET
public Response getOrders(@QueryParam("page") @DefaultValue("1") int page,
@QueryParam("size") @DefaultValue("20") int size) {
Page<Order> orders = orderService.findAll(page, size);
return Response.ok(orders).build();
}
@GET
@Path("/{id}")
public Response getOrder(@PathParam("id") String id) {
return orderService.findById(id)
.map(order -> Response.ok(order).build())
.orElse(Response.status(404).build());
}
@POST
public Response createOrder(Order order, @Context UriInfo uriInfo) {
Order created = orderService.create(order);
URI uri = uriInfo.getAbsolutePathBuilder()
.path(created.getId().toString()).build();
return Response.created(uri).entity(created).build();
}
@PUT
@Path("/{id}")
public Response updateOrder(@PathParam("id") String id, Order order) {
orderService.update(id, order);
return Response.ok().build();
}
@DELETE
@Path("/{id}")
public Response deleteOrder(@PathParam("id") String id) {
orderService.delete(id);
return Response.noContent().build();
}
}
2.2 Exception Mapper
@Provider
public class OrderNotFoundExceptionMapper
implements ExceptionMapper<OrderNotFoundException> {
@Override
public Response toResponse(OrderNotFoundException exception) {
return Response.status(404)
.entity(new ErrorResponse("ORDER_NOT_FOUND", exception.getMessage()))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
@Provider
public class ValidationExceptionMapper
implements ExceptionMapper<ValidationException> {
@Override
public Response toResponse(ValidationException exception) {
return Response.status(422)
.entity(new ErrorResponse("VALIDATION_ERROR",
exception.getMessage()))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
3. CDI (Contexts and Dependency Injection)
3.1 Beans
@ApplicationScoped
public class OrderService {
@Inject
private OrderRepository orderRepository;
@Inject
private Event<OrderCreatedEvent> orderCreatedEvent;
public Order createOrder(Order order) {
orderRepository.save(order);
orderCreatedEvent.fire(new OrderCreatedEvent(order.getId()));
return order;
}
}
@RequestScoped
public class OrderContext {
private String currentOrderId;
// ...
}
3.2 Producers
@ApplicationScoped
public class Resources {
@Produces
@PersistenceContext(unitName = "orderPU")
private EntityManager em;
@Produces
@OrderDatabase
public DataSource createDataSource() {
// Configuration programmatique
}
@Produces
@ApplicationScoped
public ObjectMapper objectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
}
3.3 Interceptors
@Interceptor
@Logged
public class LoggingInterceptor {
@AroundInvoke
public Object logMethodEntry(InvocationContext context) throws Exception {
String method = context.getMethod().getName();
String params = Arrays.toString(context.getParameters());
log.info("Méthode {} appelée avec paramètres: {}", method, params);
long start = System.currentTimeMillis();
try {
return context.proceed();
} finally {
log.info("Méthode {} terminée en {}ms", method,
System.currentTimeMillis() - start);
}
}
}
@Interceptor
@Transactional
public class TransactionInterceptor {
@Inject
private UserTransaction transaction;
@AroundInvoke
public Object manageTransaction(InvocationContext context) throws Exception {
transaction.begin();
try {
Object result = context.proceed();
transaction.commit();
return result;
} catch (Exception e) {
transaction.rollback();
throw e;
}
}
}
4. JPA (Java Persistence API)
4.1 Entities
@Entity
@Table(name = "orders")
@NamedQueries({
@NamedQuery(name = "Order.findByCustomer",
query = "SELECT o FROM Order o WHERE o.customerId = :customerId"),
@NamedQuery(name = "Order.findByStatus",
query = "SELECT o FROM Order o WHERE o.status = :status")
})
public class OrderEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(name = "customer_id", nullable = false)
private String customerId;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@Column(name = "total_amount", precision = 10, scale = 2)
private BigDecimal totalAmount;
@Column(name = "currency", length = 3)
private String currency;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL,
orphanRemoval = true, fetch = FetchType.LAZY)
private List<OrderLineEntity> lines = new ArrayList<>();
@Version
private int version; // Optimistic locking
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
public void prePersist() {
createdAt = LocalDateTime.now();
}
}
4.2 Relationships
@Entity
@Table(name = "order_lines")
public class OrderLineEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private OrderEntity order;
@Column(name = "product_id", nullable = false)
private String productId;
@Column(name = "quantity", nullable = false)
private int quantity;
@Column(name = "unit_price", precision = 10, scale = 2)
private BigDecimal unitPrice;
@Column(name = "line_total", precision = 10, scale = 2)
private BigDecimal lineTotal;
}
@Entity
@Table(name = "products")
public class ProductEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(unique = true, nullable = false)
private String sku;
@Column(nullable = false)
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private CategoryEntity category;
@ElementCollection
@CollectionTable(name = "product_tags",
joinColumns = @JoinColumn(name = "product_id"))
@Column(name = "tag")
private Set<String> tags = new HashSet<>();
}
4.3 Repository with JPA
@Stateless
public class OrderRepository {
@PersistenceContext(unitName = "orderPU")
private EntityManager em;
public OrderEntity findById(UUID id) {
return em.find(OrderEntity.class, id);
}
public List<OrderEntity> findByCustomer(String customerId) {
return em.createNamedQuery("Order.findByCustomer", OrderEntity.class)
.setParameter("customerId", customerId)
.getResultList();
}
public OrderEntity save(OrderEntity order) {
if (order.getId() == null) {
em.persist(order);
return order;
} else {
return em.merge(order);
}
}
public void delete(UUID id) {
OrderEntity order = em.getReference(OrderEntity.class, id);
em.remove(order);
}
}
5. EJB (Enterprise JavaBeans)
5.1 Stateless Session Bean
@Stateless
public class OrderService {
@PersistenceContext
private EntityManager em;
@Resource
private SessionContext context;
public Order createOrder(Order order) {
em.persist(order);
return order;
}
public List<Order> findOrdersByCustomer(String customerId) {
return em.createQuery("SELECT o FROM Order o WHERE o.customerId = :cid", Order.class)
.setParameter("cid", customerId)
.getResultList();
}
}
5.2 Singleton Session Bean
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class OrderCounter {
private AtomicLong counter = new AtomicLong();
@Lock(LockType.WRITE)
public long increment() {
return counter.incrementAndGet();
}
@Lock(LockType.READ)
public long getCount() {
return counter.get();
}
@PostConstruct
public void init() {
counter.set(0);
}
}
5.3 Message-Driven Bean
@MessageDriven(
activationConfig = {
@ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "jakarta.jms.Queue"
),
@ActivationConfigProperty(
propertyName = "destinationLookup",
propertyValue = "jms/orderQueue"
)
}
)
public class OrderProcessingMDB implements MessageListener {
@Inject
private OrderService orderService;
@Override
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
Order order = (Order) ((ObjectMessage) message).getObject();
orderService.processOrder(order);
}
} catch (JMSException e) {
log.error("Erreur traitement message", e);
}
}
}
6. Bean Validation
6.1 Validation constraints
public class OrderRequest {
@NotNull(message = "Customer ID is required")
private String customerId;
@NotEmpty(message = "Order must have at least one item")
@Valid
private List<OrderItemRequest> items;
}
public class OrderItemRequest {
@NotNull
private String productId;
@Min(1)
@Max(999)
private int quantity;
@DecimalMin("0.01")
@DecimalMax("999999.99")
@NotNull
private BigDecimal unitPrice;
}
// Custom validator
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidOrderValidator.class)
public @interface ValidOrder {
String message() default "Invalid order";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class ValidOrderValidator
implements ConstraintValidator<ValidOrder, OrderRequest> {
@Override
public boolean isValid(OrderRequest order, ConstraintValidatorContext context) {
if (order.getItems() == null || order.getItems().isEmpty()) {
return false;
}
return order.getItems().stream()
.allMatch(item -> item.getQuantity() > 0);
}
}
7. Batch (JSR 352)
@Named
public class OrderBatchlet extends AbstractBatchlet {
@Inject
private OrderService orderService;
@Override
public String process() throws Exception {
List<Order> pendingOrders = orderService.findPendingOrders();
for (Order order : pendingOrders) {
orderService.processOrder(order);
}
return "COMPLETED";
}
}
// job.xml
<job id="orderProcessingJob" xmlns="https://jakarta.ee/xml/ns/jakartaee">
<step id="processOrders">
<batchlet ref="orderBatchlet"/>
</step>
</job>
8. JMS (Java Message Service)
@Stateless
public class OrderMessageProducer {
@Resource(lookup = "jms/orderQueue")
private Queue orderQueue;
@Inject
private JMSContext jmsContext;
public void sendOrder(Order order) {
ObjectMessage message = jmsContext.createObjectMessage(order);
jmsContext.createProducer()
.setDeliveryMode(DeliveryMode.PERSISTENT)
.setPriority(5)
.setTimeToLive(86400000) // 24 hours
.send(orderQueue, message);
}
}
Points clés
- Servlets : Lifecycle init → service → destroy
- JAX-RS : @Path, @GET, @POST, exception mappers
- CDI : @Inject, @Produces, @Interceptor
- JPA : Entities, relationships (OneToMany, ManyToOne)
- EJB : @Stateless, @Singleton, MDB
- Bean Validation : @NotNull, @Min, custom validators
- Batch : JSR 352 pour le traitement par lots
- JMS : Producteurs et consommateurs asynchrones