標籤 : Java與Webhtml
Listener爲在Java Web中進行事件驅動編程提供了一整套事件類和監聽器接口.Listener監聽的事件源分爲ServletContext
/HttpSession
/ServletRequest
三個級別:java
Listener | 場景 |
---|---|
ServletContextListener |
響應ServletContext 生命週期事件(建立/銷燬),在ServletContext 建立/銷燬時分別調用其相應的方法. |
ServletContextAttributeListener |
響應ServletContext 屬性的添加/刪除/替換事件. |
Listener | 場景 |
---|---|
HttpSessionListener |
響應Session生命週期事件(建立/銷燬). |
HttpSessionAttributeListener |
響應Session**屬性**的添加/刪除/替換事件. |
HttpSessionBindingListener |
實現了該接口的JavaBean會在被 Session添加/刪除時作出響應. |
HttpSessionActivationListener |
實現了該接口的JavaBean會在被Session 鈍化/活化時作出響應. |
Listener | 場景 |
---|---|
ServletRequestListener |
響應ServletRequest 的建立/刪除事件. |
ServletRequestAttributeListener |
響應ServletRequest 屬性的添加/刪除/替換事件. |
建立監聽器只需實現相關接口便可,但只有將其註冊到Servlet容器中,纔會被容器發現,這樣才能在發生事件時,驅動監聽器執行.Listener的註冊方法有註解和部署描述符兩種:web
在Servlet 3.0中, 提供了@WebListener
註解:spring
@WebListener
public class ListenerClass implements ServletContextListener {
// ...
}
<listener>
<listener-class>com.fq.web.listener.ListenerClass</listener-class>
</listener>
注: 因爲
HttpSessionBindingListener
/HttpSessionActivationListener
是直接綁定在JavaBean上, 而並不是綁定到Session等域對象, 所以能夠不一樣註冊.apache
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());
}
}
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
監控
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));
}
}
當JavaBean實現HttpSessionBindingListener
接口後,就能夠感知到本類對象被添加/移除Session事件:服務器
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...");
}
}
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);
}
}
爲節省內存, Servlet容器能夠對Session屬性進行遷移或序列化.通常當內存較低時,相對較少訪問的對象能夠序列化到備用存儲設備中(鈍化);當須要再使用該Session時,容器又會把對象從持久化存儲設備中再反序列化到內存中(活化).HttpSessionActivationListener
就用於感知對象鈍化/活化事件:markdown
對於鈍化/活化,其實就是讓對象序列化/反序列化穿梭於內存與持久化存儲設備中.所以實現
HttpSessionActivationListener
接口的JavaBean也須要實現Serializable
接口.session
<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>
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()
方法.app
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()
方法,那麼程序的處理將會在此處中止,不會再繼續請求.
/** * @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()
方法的啓動初始值;<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
註解,其配置方式簡單且與部署描述符相似,所以在此就再也不贅述.
前面介紹了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
RequestDispatcher
中forward()
方法)RequestDispatcher
中include()
方法)<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>
Servlet中有4個包裝類ServletRequestWrapper
/ServletResponseWrapper
/HttpServletRequestWrapper
/HttpServletResponseWrapper
,可用來改變Servlet請求/響應的行爲, 這些包裝類遵循裝飾者模式(Decorator).
因爲他們爲所包裝的Request/Response中的每個對等方法都提供了默認實現,所以經過繼承他們, 只需覆蓋想要修改的方法便可.不必實現原始ServletRequest
/ServletResponse
/…接口的每個方法.
HttpServletRequestWrapper
在解決GET編碼時已經用到, 下面咱們用HttpServletResponseWrapper
實現頁面靜態化.
頁面靜態化是在第一次訪問時將動態生成的頁面(JSP/Servlet/Velocity等)保存成HTML靜態頁面文件存放到服務器,再有相同請求時,再也不執行動態頁面,而是直接給用戶響應已經生成的靜態頁面.
/** * @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頁面, 可參考上篇博客的購物車案例).