JSR-299 (CDI) 核心目標是整合 JSF 與 EJB,但其**依賴注入容器**能力遠超於此。本教程在**純 Servlet 環境**示範 CDI 整合 JPA,避開 JSF 深層封裝與 EJB 重型容器,保留:
適合具備 Servlet、JPA、Maven、Spring IoC 基礎的開發者。
<repositories>
<repository>
<id>jboss-public-repository-group</id>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<!-- Weld Servlet 整合 -->
<dependency>
<groupId>org.jboss.weld.servlet</groupId>
<artifactId>weld-servlet-core</artifactId>
<version>1.1.8.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-core</artifactId>
<version>1.1.8.Final</version>
</dependency>
<!-- Hibernate EntityManager -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.4.Final</version>
</dependency>
<!-- Servlet API (provided) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
初次建構:mvn dependency:resolve 下載依賴。
在 web.xml 註冊 Weld Listener:
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
並在 META-INF/beans.xml(可空)啟用 Bean 發現模式。
@Entity
public class Employee {
@Id @GeneratedValue Long id;
String name; String department; BigDecimal salary;
// getters/setters
}
@ApplicationScoped
public class EmployeeRepository {
@PersistenceContext EntityManager em;
public Employee findById(Long id) { return em.find(Employee.class, id); }
public List findAll() { return em.createQuery("SELECT e FROM Employee e", Employee.class).getResultList(); }
public Employee save(Employee e) { return em.merge(e); }
public void delete(Long id) { em.remove(em.getReference(Employee.class, id)); }
}
@Interceptor @Transactional
public class TransactionInterceptor {
@Inject UserTransaction utx;
@AroundInvoke
public Object manageTransaction(InvocationContext ctx) throws Exception {
boolean started = false;
try {
if (utx.getStatus() == Status.STATUS_NO_TRANSACTION) {
utx.begin(); started = true;
}
return ctx.proceed();
} catch (Exception e) {
if (started) utx.rollback();
throw e;
} finally {
if (started && utx.getStatus() == Status.STATUS_ACTIVE) utx.commit();
}
}
}
在 beans.xml 啟用:<interceptors><class>...TransactionInterceptor</class></interceptors>。
@SessionScoped
public class ExtendedPersistenceContext implements Serializable {
@Produces @PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager em;
public void flush() { em.flush(); }
public void clear() { em.clear(); }
}
控制器注入 EntityManager em 即自動獲得同一 Session 內共享的延伸持久化上下文,實現「Open Session in View」無需 Filter 手工管理。
// 事件物件
public class EmployeeChangedEvent {
private final Employee employee;
private final EventType type; // CREATED, UPDATED, DELETED
// constructor, getters
}
// 發送
@Inject @Any Event employeeEvent;
employeeEvent.fire(new EmployeeChangedEvent(emp, EventType.UPDATED));
// 觀察
public void onEmployeeChanged(@Observes EmployeeChangedEvent evt) {
// 發送通知、更新快取、記錄審計日誌...
}
src/main/java
├── com.example.domain // JPA 實體
├── com.example.repository // Repository (CDI Bean)
├── com.example.service // 業務邏輯 @Transactional
├── com.example.web // Servlet 控制器
├── com.example.cdi // Interceptor, Producer, Qualifier
└── com.example.event // 事件物件與觀察器
| 現象 | 原因 | 對策 |
|---|---|---|
| LazyInitializationException | Session 關閉後存取關聯 | 使用 EXTENDED PersistenceContext 或 DTO 投影 |
| 事務不生效 | Interceptor 未啟用或 Bean 非 CDI 管理 | 檢查 beans.xml、@Transactional 註解、非 final 方法 |
| Weld 啟動慢 | 掃描類路徑過大 | beans.xml 設定 bean-discovery-mode="annotated" |
| Tomcat 類載入衝突 | Hibernate/JPA API 版本不一 | 排除傳遞依賴,統一版本鎖定 |