J2EE 5.0 (Java EE 5) 引入 JSR 175 (Metadata Annotations) 與 JSR 250 (Common Annotations),徹底改變了「部署描述符至上」的配置模式。@Resource、@EJB、@WebServiceRef 等註解讓依賴注入、安全聲明、服務引用直接寫在 Java 類上,大幅減少 XML 配置。本文深度解析 Servlet、EJB、JSP Tag Handler 三大場景的註解實戰應用。
| 註解 | 作用目標 | 主要屬性 | 說明 |
|---|---|---|---|
@Resource | 類、欄位、方法 | name, type, authentication, shareable, mappedName | 注入資料源、JMS、環境項目等資源 |
@EJB | 類、欄位、方法 | name, beanInterface, beanName, mappedName, description | 注入 Session Bean (Local/Remote) |
@WebServiceRef | 類、欄位、方法 | name, type, wsdlLocation, value | 注入 Web Service 客戶端 |
@DeclareRoles | 類 | value (String[]) | 聲明 Servlet/EJB 使用的角色 |
@RolesAllowed | 類、方法 | value (String[]) | 指定可訪問的角色 (EJB 專用) |
@RunAs | 類 | value (String) | 指定執行身份角色 (傳播給 EJB) |
@PermitAll / @DenyAll | 方法 | 無 | 允許/拒絕所有角色訪問 |
@DeclareRoles({"arole"}) // 聲明 Servlet 使用的角色
@RunAs("myrole") // 呼叫 EJB 時傳播的角色
public class TestServlet extends HttpServlet {
@EJB // 自動查找實現 SlessLocal 的 Stateless Bean
private SlessLocal slessLocal;
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 程式化安全檢查
if (req.isUserInRole("arole")) {
String message = slessLocal.hello("World");
req.setAttribute("EJB_MESSAGE", message);
}
req.getRequestDispatcher("display.jsp").forward(req, resp);
}
}
SlessLocal 自動綁定對應 Bean,無需 JNDI 查找代碼isUserInRole("arole") 生效,否則永遠返回 falsemyrole 身份呼叫 EJB,滿足 EJB 端 @RolesAllowed("myrole") 檢查@Stateless // 聲明為無狀態會話 Bean
@Local({SlessLocal.class}) // 暴露本地介面
public class SlessBean implements SlessLocal {
@RolesAllowed({"myrole"}) // 僅允許 myrole 角色調用
public String hello(String message) {
return "Hello, " + message + ", " + new Date();
}
}
容器部署時自動生成 JNDI 名稱 (如 java:module/SlessBean),Servlet 端 @EJB 即可精準匹配。
限制:JSP 頁面本身不支援註解 (JSP 2.1+ 僅支援 @taglib 等指令),但 Tag Handler 類是普通 Java 類,完全支援註解注入。
public class DisplayInfoTagHandler extends SimpleTagSupport {
@Resource(name = "jdbc/__default") // JNDI 名稱對應資料源
private DataSource ds;
private String ejbMessage;
public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut();
try {
// 使用注入的資料源
int timeout = ds.getLoginTimeout();
if (ejbMessage != null && !ejbMessage.isEmpty()) {
out.println("<li> Ejb Message: " + ejbMessage);
}
out.println("<li> DataSource login timeout: " + timeout);
} catch (SQLException e) {
throw new JspException("DB error", e);
}
}
public void setEjbMessage(String ejbMessage) {
this.ejbMessage = ejbMessage;
}
}
<%@ taglib prefix="di" uri="https://example.com/tags" %>
<di:displayInfo ejbMessage="${requestScope.EJB_MESSAGE}" />
| 配置項 | 註解優先級 | web.xml/ejb-jar.xml 覆蓋 |
|---|---|---|
| 資源注入 (@Resource/@EJB) | 預設值 | XML 覆蓋註解 |
| 安全角色 (@RolesAllowed) | 預設值 | XML 覆蓋註解 |
| Servlet 映射 | 不支援 (需 @WebServlet Servlet 3.0+) | XML 唯一途徑 (Servlet 2.5) |
最佳實踐:基礎配置用註解 (類內聚合),環境差異 (JNDI 名稱、安全角色映射) 用 XML 覆蓋,實現「開發期零配置、部署期靈活調整」。
@Stateless
public class AuditedBean {
@PostConstruct
public void init() { /* 初始化資源 */ }
@PreDestroy
public void cleanup() { /* 釋放資源 */ }
@AroundInvoke
public Object audit(InvocationContext ctx) throws Exception {
long start = System.currentTimeMillis();
try { return ctx.proceed(); }
finally {
System.out.println(ctx.getMethod() + " took " + (System.currentTimeMillis() - start) + "ms");
}
}
@PostActivate
public void activate() { /* 狀態 Bean 活化 */ }
@PrePassivate
public void passivate() { /* 狀態 Bean 鈍化 */ }
}
@Autowired、@Value、@PostConstruct 等提供更豐富功能,但標準註解 (javax.annotation.*) 保證可移植性jakarta.annotation.*,升級時需批量替換 import@ConfigProperty、@Health、@Metrics 擴展雲原生能力J2EE 5.0 註解標準化了依賴注入、安全授權、生命週期管理等核心關注點,讓開發者從龐雜的 XML 描述符中解脫。掌握 @Resource/@EJB/@WebServiceRef 注入模式、@DeclareRoles/@RolesAllowed/@RunAs 安全傳播鏈、Tag Handler 註解注入技巧,即可在標準 Java EE 容器中構建清晰、可維護、可移植的企業級應用。