Servlet
標籤 : Java與Web
Listener-監聽器
Listener爲在Java Web中進行事件驅動編程提供了一整套事件類和監聽器接口.Listener監聽的事件源分爲ServletContext
/HttpSession
/ServletRequest
三個級別:
- ServletContext級別
Listener | 場景 |
---|---|
ServletContextListener |
響應ServletContext 生命週期事件(創建/銷燬),在ServletContext 創建/銷燬時分別調用其相應的方法. |
ServletContextAttributeListener |
響應ServletContext 屬性的添加/刪除/替換事件. |
- HttpSession級別
Listener | 場景 |
---|---|
HttpSessionListener |
響應Session生命週期事件(創建/銷燬). |
HttpSessionAttributeListener |
響應Session**屬性**的添加/刪除/替換事件. |
HttpSessionBindingListener |
實現了該接口的JavaBean會在被 Session添加/刪除時做出響應. |
HttpSessionActivationListener |
實現了該接口的JavaBean會在被Session 鈍化/活化時做出響應. |
- ServletRequest級別
Listener | 場景 |
---|---|
ServletRequestListener |
響應ServletRequest 的創建/刪除事件. |
ServletRequestAttributeListener |
響應ServletRequest 屬性的添加/刪除/替換事件. |
註冊
創建監聽器只需實現相關接口即可,但只有將其註冊到Servlet容器中,纔會被容器發現,這樣才能在發生事件時,驅動監聽器執行.Listener的註冊方法有註解和部署描述符兩種:
1. @WebListener
在Servlet 3.0中, 提供了@WebListener
註解:
@WebListener
public class ListenerClass implements ServletContextListener {
// ...
}
2. 部署描述符
<listener>
<listener-class>com.fq.web.listener.ListenerClass</listener-class>
</listener>
注: 由於
HttpSessionBindingListener
/HttpSessionActivationListener
是直接綁定在JavaBean上, 而並非綁定到Session等域對象, 因此可以不同註冊.
示例
加載Spring容器
- ContextLoaderListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
- web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
統計HTTP請求耗時
監控
ServletRequest
的創建/銷燬事件, 以計算HTTP處理耗時
/**
* @author jifang.
* @since 2016/5/4 15:17.
*/
@WebListener
public class PerforationStatListener implements ServletRequestListener {
private static final Logger LOGGER = Logger.getLogger("PerforationStatListener");
private static final String START = "Start";
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
request.setAttribute(START, System.nanoTime());
}
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
long start = (Long)request.getAttribute(START);
long ms = (System.nanoTime() - start)/1000;
String uri = request.getRequestURI();
LOGGER.info(String.format("time token to execute %s : %s ms", uri, ms));
}
}
HttpSessionBindingListener
當JavaBean實現HttpSessionBindingListener
接口後,就可以感知到本類對象被添加/移除Session事件:
- Listener
public class Product implements Serializable, HttpSessionBindingListener {
private int id;
private String name;
private String description;
private double price;
public Product(int id, String name, String description, double price) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
// ...
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("bound...");
}
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("un_bound...");
}
}
- Servlet
private static final String FLAG = "flag";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Boolean flag = (Boolean) getServletContext().getAttribute(FLAG);
if (flag == null || !flag) {
request.getSession().setAttribute("product", new Product(8, "水晶手鍊", "VunSun微色天然水晶手鍊女款", 278.00));
getServletContext().setAttribute(FLAG, true);
} else {
request.getSession().removeAttribute("product");
getServletContext().setAttribute(FLAG, !flag);
}
}
HttpSessionActivationListener
爲節省內存, Servlet容器可以對Session屬性進行遷移或序列化.一般當內存較低時,相對較少訪問的對象可以序列化到備用存儲設備中(鈍化);當需要再使用該Session時,容器又會把對象從持久化存儲設備中再反序列化到內存中(活化).HttpSessionActivationListener
就用於感知對象鈍化/活化事件:
對於鈍化/活化,其實就是讓對象序列化/反序列化穿梭於內存與持久化存儲設備中.因此實現
HttpSessionActivationListener
接口的JavaBean也需要實現Serializable
接口.
- 在conf/context.xml配置鈍化時間
<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="sessions"/>
</Manager>
</Context>
- JavaBean
public class Product implements Serializable, HttpSessionActivationListener {
private int id;
private String name;
private String description;
private double price;
// ...
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("passivate...");
}
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("Activate...");
}
}
將Product加入Session一分鐘不訪問後, 該對象即會序列化到磁盤, 並調用sessionWillPassivate()
方法, 當再次使用該對象時, Servlet容器會自動活化該Session, 並調用sessionDidActivate()
方法.
Filter-過濾器
Filter是指攔截請求,並可以對
ServletRequest
/ServletResponse
進行處理的一個對象.由於其可配置爲攔截一個或多個資源,因此可用於處理登錄/加(解)密/會話檢查/圖片適配等問題.
Filter中常用的有Filter
/FilterChain
/FilterConfig
三個接口:
Filter | 描述 |
---|---|
void init(FilterConfig filterConfig) |
Called by the web container to indicate to a filter that it is being placed into service. |
void destroy() |
Called by the web container to indicate to a filter that it is being taken out of service. |
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) |
The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain. |
過濾器必須實現Filter
接口, 當應用程序啓動時,Servlet容器自動調用過濾器init()
方法;當服務終止時,自動調用destroy()
方法.當每次請求與過濾器資源相關資源時,都會調用doFilter()
方法;由於doFilter()
可以訪問ServletRequest
/ServletResponse
,因此可以在Request中添加屬性,或在Response中添加一個響應頭,甚至可以對Request/Response進行修飾/替換,改變他們的行爲(詳見下).
FilterChain | 描述 |
---|---|
void doFilter(ServletRequest request, ServletResponse response) |
Causes the next filter in the chain to be invoked, or if the calling filter is the last filter in the chain, causes the resource at the end of the chain to be invoked. |
FilterChain
中只有一個doFilter()
方法, 該方法可以引發調用鏈中下一過濾器或資源本身被調用.如果沒有在Filter
的doFilter()
中調用FilterChain
的doFilter()
方法,那麼程序的處理將會在此處停止,不會再繼續請求.
- 示例: Filter解決GET/POST編碼問題
/**
* @author jifang.
* @since 2016/5/2 11:55.
*/
public class CharsetEncodingFilter implements Filter {
private static final String IGNORE_URI = "ignore_uri";
private static final String URI_SEPARATOR = ",";
private Set<String> ignoreUris = new HashSet<String>();
public void init(FilterConfig config) throws ServletException {
String originalUris = config.getInitParameter(IGNORE_URI);
if (originalUris != null) {
String[] uris = originalUris.split(URI_SEPARATOR);
for (String uri : uris) {
this.ignoreUris.add(uri);
}
}
}
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
String uri = request.getRequestURI();
if (!ignoreUris.contains(uri)) {
if (request.getMethod().equals("GET")) {
request = new EncodingRequest(request);
} else {
request.setCharacterEncoding("UTF-8");
}
}
chain.doFilter(request, resp);
}
private static final class EncodingRequest extends HttpServletRequestWrapper {
public EncodingRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
try {
value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
return value;
}
}
}
注:
HttpServletRequestWrapper
介紹見Decorator-裝飾者部分.
註冊/配置
編寫好過濾器後, 還需對其進行註冊配置,配置過濾器的目標如下:
- 確定過濾器要攔截的目標資源;
- 傳遞給
init()
方法的啓動初始值; - 爲過濾器命名.
- web.xml
<filter>
<filter-name>CharsetEncodingFilter</filter-name>
<filter-class>com.fq.web.filter.CharsetEncodingFilter</filter-class>
<init-param>
<param-name>ignore_uri</param-name>
<param-value>/new_servlet.do,/hello_http_servlet.do</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharsetEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
也可用
@WebFilter
註解,其配置方式簡單且與部署描述符類似,因此在此就不再贅述.
FilterConfig
前面介紹了Filter
/FilterChain
兩個接口,下面介紹FilterConfig
接口, 其最常用的方法是getInitParameter()
, 獲取過濾器的初始化參數, 以完成更精細化的過濾規則.不過他還提供瞭如下實用方法:
FilterConfig | 描述 |
---|---|
String getFilterName() |
Returns the filter-name of this filter as defined in the deployment descriptor. |
String getInitParameter(String name) |
Returns a String containing the value of the named initialization parameter, or null if the initialization parameter does not exist. |
Enumeration<String> getInitParameterNames() |
Returns the names of the filter’s initialization parameters as an Enumeration of String objects, or an empty Enumeration if the filter has no initialization parameters. |
ServletContext getServletContext() |
Returns a reference to the ServletContext in which the caller is executing. |
攔截方式
過濾器的攔截方式有四種: REQUEST / FORWARD / INCLUDE / ERROR
- REQUEST : (默認)直接訪問目標資源時執行(地址欄直接訪問/表單提交/超鏈接/重定向等只要在地址欄中可看到目標資源路徑,就是REQUEST)
- FORWARD : 轉發訪問執行(
RequestDispatcher
中forward()
方法) - INCLUDE : 包含訪問執行(
RequestDispatcher
中include()
方法) - ERROR : 當目標資源在web.xml中配置爲中時,並且出現異常,轉發到目標資源時, 執行該過濾器.
<filter>
<filter-name>CharsetEncodingFilter</filter-name>
<filter-class>com.fq.web.filter.CharsetEncodingFilter</filter-class>
<init-param>
<param-name>ignore_path</param-name>
<param-value>/new_servlet.do</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharsetEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>
Decorator-裝飾者
Servlet中有4個包裝類ServletRequestWrapper
/ServletResponseWrapper
/HttpServletRequestWrapper
/HttpServletResponseWrapper
,可用來改變Servlet請求/響應的行爲, 這些包裝類遵循裝飾者模式(Decorator).
由於他們爲所包裝的Request/Response中的每一個對等方法都提供了默認實現,因此通過繼承他們, 只需覆蓋想要修改的方法即可.沒必要實現原始ServletRequest
/ServletResponse
/…接口的每一個方法.
實例-頁面靜態化
HttpServletRequestWrapper
在解決GET編碼時已經用到, 下面我們用HttpServletResponseWrapper
實現頁面靜態化.
頁面靜態化是在第一次訪問時將動態生成的頁面(JSP/Servlet/Velocity等)保存成HTML靜態頁面文件存放到服務器,再有相同請求時,不再執行動態頁面,而是直接給用戶響應已經生成的靜態頁面.
- Filter & Decorator
/**
* @author jifang.
* @since 2016/5/7 9:40.
*/
public class PageStaticizeFilter implements Filter {
private static final String HTML_PATH_MAP = "html_path_map";
private static final String STATIC_PAGES = "/static_pages/";
private ServletContext context;
public void init(FilterConfig filterConfig) throws ServletException {
this.context = filterConfig.getServletContext();
this.context.setAttribute(HTML_PATH_MAP, new HashMap<String, String>());
}
public void destroy() {
}
@SuppressWarnings("All")
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
Map<String, String> htmlPathMap = (Map<String, String>) context.getAttribute(HTML_PATH_MAP);
String htmlName = request.getServletPath().replace("/", "_") + ".html";
String htmlPath = htmlPathMap.get(htmlName);
// 尚未生成靜態頁面
if (htmlPath == null) {
htmlPath = context.getRealPath(STATIC_PAGES) + "/" + htmlName;
htmlPathMap.put(htmlName, htmlPath);
PageStaticizeResponse sResponse = new PageStaticizeResponse(response, htmlPath);
chain.doFilter(request, sResponse);
sResponse.close();
}
String redirectPath = context.getContextPath() + STATIC_PAGES + htmlName;
response.sendRedirect(redirectPath);
}
private static final class PageStaticizeResponse extends HttpServletResponseWrapper {
private PrintWriter writer;
public PageStaticizeResponse(HttpServletResponse response, String path) throws FileNotFoundException, UnsupportedEncodingException {
super(response);
writer = new PrintWriter(path, "UTF-8");
}
@Override
public PrintWriter getWriter() throws IOException {
return this.writer;
}
public void close() {
this.writer.close();
}
}
}
- 註冊
<filter>
<filter-name>PageStaticzeFilter</filter-name>
<filter-class>com.fq.web.filter.PageStaticizeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>PageStaticzeFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
注: 在此只是提供一個頁面靜態化思路, 由於代碼中是以Servlet-Path粒度來生成靜態頁面, 粒度較粗, 細節方面肯定會有所疏漏(但粒度過細又會導致生成HTML頁面過多), 因此這份代碼僅供參考, 不可用於實際項目(關於該Filter所攔截的jsp頁面, 可參考上篇博客的購物車案例).