`
luckaway
  • 浏览: 137881 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Servlet的Filter实现页面缓存

阅读更多
java有多个开源的缓存系统都支持页面缓存的,如OScache、Ehcache。
这个例子就是从Ehcache里挖出来的,并做了些改造和简化,但原理在此例子中都是完全体现出来了。该例子只供大家学习用,企业应用还是需要做一些修改的。因为页面数据只是直接存放到HashMap里。

CacheFilter.java
页面数据就是存放到HashMap里,key是url。
public class CacheFilter implements Filter {

	public static final String HEADER_LAST_MODIFIED = "Last-Modified";
	public static final String HEADER_CONTENT_TYPE = "Content-Type";
	public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
	public static final String HEADER_EXPIRES = "Expires";
	public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
	public static final String HEADER_CACHE_CONTROL = "Cache-Control";
	public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";

	private static final String REQUEST_FILTERED = "cache_filter_" + CacheFilter.class.getName();

	private final Map<String, ResponseContent> cache = new HashMap<String, ResponseContent>();

	// Last Modified parameter
	private static final long LAST_MODIFIED_INITIAL = -1;

	// Expires parameter
	private static final long EXPIRES_ON = 1;

	private int time = 60 * 60;
	private long lastModified = LAST_MODIFIED_INITIAL;
	private long expires = EXPIRES_ON;
	private long cacheControlMaxAge = -60;

	@Override
	public void destroy() {

	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
			ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		//避免重复调用
		if (isFilteredBefore(request)) {
			chain.doFilter(request, res);
			return;
		}
		request.setAttribute(REQUEST_FILTERED, Boolean.TRUE);

		String key = getCacheKey(request);

		ResponseContent responseContent = cache.get(key);
		if (responseContent != null) {//如果当前的URL已经有对应的响应内容
			responseContent.writeTo(res);
			return;
		}
//用CacheHttpServletResponseWrapper来代替HttpServletResponse,用于记录HttpServletResponse输出的内容。
		CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper((HttpServletResponse) res,
				time * 1000L, lastModified, expires, cacheControlMaxAge);
		chain.doFilter(request, cacheResponse);
		cacheResponse.flushBuffer();
		// Store as the cache content the result of the response
		cache.put(key, cacheResponse.getContent());
	}

	private String getCacheKey(HttpServletRequest request) {
		StringBuilder builder = new StringBuilder(request.getRequestURI());
		if (StringUtils.isNotEmpty(request.getQueryString())) {
			builder.append("_").append(request.getQueryString());
		}
		return builder.toString();
	}

	/**
	 * Checks if the request was filtered before, so guarantees to be executed
	 * once per request. You can override this methods to define a more specific
	 * behaviour.
	 * 
	 * @param request checks if the request was filtered before.
	 * @return true if it is the first execution
	 */
	public boolean isFilteredBefore(ServletRequest request) {
		return request.getAttribute(REQUEST_FILTERED) != null;
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub
	}
}



HttpServletResponseWrapper.java
用ResponseContent记录HttpServletResponse输出的信息
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Locale;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 缓存的HttpServletResponseWrapper,它会把{@link HttpServletResponse}的部分数据记录到{@link ResponseContent}里。
 */
public class CacheHttpServletResponseWrapper extends HttpServletResponseWrapper {
	private final Log log = LogFactory.getLog(this.getClass());

	private PrintWriter cachedWriter = null;
	private ResponseContent result = null;
	private SplitServletOutputStream cacheOut = null;
	private int status = SC_OK;
	private long cacheControl = -60;

	public CacheHttpServletResponseWrapper(HttpServletResponse response) {
		this(response, Long.MAX_VALUE, CacheFilter.EXPIRES_ON, CacheFilter.LAST_MODIFIED_INITIAL, -60);
	}


	public CacheHttpServletResponseWrapper(HttpServletResponse response, long time, long lastModified, long expires,
			long cacheControl) {
		super(response);
		this.result = new ResponseContent();
		this.cacheControl = cacheControl;

		// setting a default last modified value based on object creation and
		// remove the millis
		if (lastModified == CacheFilter.LAST_MODIFIED_INITIAL) {
			long current = System.currentTimeMillis();
			current = current - (current % 1000);
			result.setLastModified(current);
			super.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, result.getLastModified());
		}
		// setting the expires value
		if (expires == CacheFilter.EXPIRES_TIME) {
			result.setExpires(result.getLastModified() + time);
			super.setDateHeader(CacheFilter.HEADER_EXPIRES, result.getExpires());
		}
		// setting the cache control with max-age
		if (this.cacheControl == CacheFilter.MAX_AGE_TIME) {
			// set the count down
			long maxAge = System.currentTimeMillis();
			maxAge = maxAge - (maxAge % 1000) + time;
			result.setMaxAge(maxAge);
			super.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + time / 1000);
		} else if (this.cacheControl != CacheFilter.MAX_AGE_NO_INIT) {
			result.setMaxAge(this.cacheControl);
			super.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-this.cacheControl));
		} else if (this.cacheControl == CacheFilter.MAX_AGE_NO_INIT) {
			result.setMaxAge(this.cacheControl);
		}
	}
	/**
	 * Get a response content
	 * 
	 * @return The content
	 */
	public ResponseContent getContent() {
		// Flush the buffer
		try {
			flush();
		} catch (IOException ignore) {
		}
		// Create the byte array
		result.commit();
		// Return the result from this response
		return result;
	}

	/**
	 * Set the content type
	 * 
	 * @param value The content type
	 */
	public void setContentType(String value) {
		if (log.isDebugEnabled()) {
			log.debug("ContentType: " + value);
		}
		super.setContentType(value);
		result.setContentType(value);
	}

	/**
	 * Set a header field
	 * 
	 * @param name The header name
	 * @param value The header value
	 */
	public void setHeader(String name, String value) {
		if (log.isDebugEnabled()) {
			log.debug("header: " + name + ": " + value);
		}
		if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
			result.setContentType(value);
		}

		if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
			result.setContentEncoding(value);
		}
		super.setHeader(name, value);
	}

	/**
	 * Add a header field
	 * 
	 * @param name The header name
	 * @param value The header value
	 */
	public void addHeader(String name, String value) {
		if (log.isDebugEnabled()) {
			log.debug("header: " + name + ": " + value);
		}

		if (CacheFilter.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
			result.setContentType(value);
		}

		if (CacheFilter.HEADER_CONTENT_ENCODING.equalsIgnoreCase(name)) {
			result.setContentEncoding(value);
		}

		super.addHeader(name, value);
	}

	/**
	 * We override this so we can catch the response status. Only responses with
	 * a status of 200 (<code>SC_OK</code>) will be cached.
	 */
	public void setStatus(int status) {
		super.setStatus(status);
		this.status = status;
	}

	/**
	 * We override this so we can catch the response status. Only responses with
	 * a status of 200 (<code>SC_OK</code>) will be cached.
	 */
	public void sendError(int status, String string) throws IOException {
		super.sendError(status, string);
		this.status = status;
	}

	/**
	 * We override this so we can catch the response status. Only responses with
	 * a status of 200 (<code>SC_OK</code>) will be cached.
	 */
	public void sendError(int status) throws IOException {
		super.sendError(status);
		this.status = status;
	}

	/**
	 * We override this so we can catch the response status. Only responses with
	 * a status of 200 (<code>SC_OK</code>) will be cached.
	 */
	public void setStatus(int status, String string) {
		super.setStatus(status, string);
		this.status = status;
	}

	/**
	 * We override this so we can catch the response status. Only responses with
	 * a status of 200 (<code>SC_OK</code>) will be cached.
	 */
	public void sendRedirect(String location) throws IOException {
		this.status = SC_MOVED_TEMPORARILY;
		super.sendRedirect(location);
	}

	/**
	 * Retrieves the captured HttpResponse status.
	 */
	public int getStatus() {
		return status;
	}

	/**
	 * Set the locale
	 * 
	 * @param value The locale
	 */
	public void setLocale(Locale value) {
		super.setLocale(value);
		result.setLocale(value);
	}

	/**
	 * Get an output stream
	 * 
	 * @throws IOException
	 */
	public ServletOutputStream getOutputStream() throws IOException {
		// Pass this faked servlet output stream that captures what is sent
		if (cacheOut == null) {
			cacheOut = new SplitServletOutputStream(result.getOutputStream(), super.getOutputStream());
		}
		return cacheOut;
	}

	/**
	 * Get a print writer
	 * 
	 * @throws IOException
	 */
	public PrintWriter getWriter() throws IOException {
		if (cachedWriter == null) {
			String encoding = getCharacterEncoding();
			if (encoding != null) {
				cachedWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), encoding));
			} else { // using the default character encoding
				cachedWriter = new PrintWriter(new OutputStreamWriter(getOutputStream()));
			}
		}

		return cachedWriter;
	}

	/**
	 * Flushes all streams.
	 * 
	 * @throws IOException
	 */
	private void flush() throws IOException {
		if (cacheOut != null) {
			cacheOut.flush();
		}

		if (cachedWriter != null) {
			cachedWriter.flush();
		}
	}

	public void flushBuffer() throws IOException {
		super.flushBuffer();
		flush();
	}
}





ResponseContent.java
响应后的内容,就是需要cache的对象,包含页面内容和Content-Type、Last-Modified、Content-Encoding等一些响应头的信息。
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Locale;
import java.util.zip.GZIPInputStream;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

/**
 * Holds the servlet response in a byte array so that it can be held in the
 * cache (and, since this class is serializable, optionally persisted to disk).
 * 
 * @version $Revision: 362 $
 * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
 */
public class ResponseContent implements Serializable {
	private static final long serialVersionUID = 1L;
	private transient ByteArrayOutputStream bout = new ByteArrayOutputStream(1000);
	private Locale locale = null;
	private String contentEncoding = null;
	private String contentType = null;
	private byte[] content = null;
	private long expires = Long.MAX_VALUE;
	private long lastModified = -1;
	private long maxAge = -60;

	public String getContentType() {
		return contentType;
	}

	/**
	 * Set the content type. We capture this so that when we serve this data
	 * from cache, we can set the correct content type on the response.
	 */
	public void setContentType(String value) {
		contentType = value;
	}

	public long getLastModified() {
		return lastModified;
	}

	public void setLastModified(long value) {
		lastModified = value;
	}

	public String getContentEncoding() {
		return contentEncoding;
	}

	public void setContentEncoding(String contentEncoding) {
		this.contentEncoding = contentEncoding;
	}

	/**
	 * Set the Locale. We capture this so that when we serve this data from
	 * cache, we can set the correct locale on the response.
	 */
	public void setLocale(Locale value) {
		locale = value;
	}

	/**
	 * @return the expires date and time in miliseconds when the content will be
	 *         stale
	 */
	public long getExpires() {
		return expires;
	}

	/**
	 * Sets the expires date and time in miliseconds.
	 * 
	 * @param value time in miliseconds when the content will expire
	 */
	public void setExpires(long value) {
		expires = value;
	}

	/**
	 * Returns the max age of the content in miliseconds. If expires header and
	 * cache control are enabled both, both will be equal.
	 * 
	 * @return the max age of the content in miliseconds, if -1 max-age is
	 *         disabled
	 */
	public long getMaxAge() {
		return maxAge;
	}

	/**
	 * Sets the max age date and time in miliseconds. If the parameter is -1,
	 * the max-age parameter won't be set by default in the Cache-Control
	 * header.
	 * 
	 * @param value sets the intial
	 */
	public void setMaxAge(long value) {
		maxAge = value;
	}

	/**
	 * Get an output stream. This is used by the
	 * {@link SplitServletOutputStream} to capture the original (uncached)
	 * response into a byte array.
	 * 
	 * @return the original (uncached) response, returns null if response is
	 *         already committed.
	 */
	public OutputStream getOutputStream() {
		return bout;
	}

	/**
	 * Gets the size of this cached content.
	 * 
	 * @return The size of the content, in bytes. If no content exists, this
	 *         method returns <code>-1</code>.
	 */
	public int getSize() {
		return (content != null) ? content.length : (-1);
	}

	/**
	 * Called once the response has been written in its entirety. This method
	 * commits the response output stream by converting the output stream into a
	 * byte array.
	 */
	public void commit() {
		if (bout != null) {
			content = bout.toByteArray();
			bout = null;
		}
	}

	/**
	 * Writes this cached data out to the supplied <code>ServletResponse</code>.
	 * 
	 * @param response The servlet response to output the cached content to.
	 * @throws IOException
	 */
	public void writeTo(ServletResponse response) throws IOException {
		writeTo(response, false, false);
	}

	/**
	 * Writes this cached data out to the supplied <code>ServletResponse</code>.
	 * 
	 * @param response The servlet response to output the cached content to.
	 * @param fragment is true if this content a fragment or part of a page
	 * @param acceptsGZip is true if client browser supports gzip compression
	 * @throws IOException
	 */
	public void writeTo(ServletResponse response, boolean fragment, boolean acceptsGZip) throws IOException {
		// Send the content type and data to this response
		if (contentType != null) {
			response.setContentType(contentType);
		}

		if (fragment) {
			// Don't support gzip compression if the content is a fragment of a
			// page
			acceptsGZip = false;
		} else {
			// add special headers for a complete page
			if (response instanceof HttpServletResponse) {
				HttpServletResponse httpResponse = (HttpServletResponse) response;

				// add the last modified header
				if (lastModified != -1) {
					httpResponse.setDateHeader(CacheFilter.HEADER_LAST_MODIFIED, lastModified);
				}

				// add the expires header
				if (expires != Long.MAX_VALUE) {
					httpResponse.setDateHeader(CacheFilter.HEADER_EXPIRES, expires);
				}

				// add the cache-control header for max-age
				if (maxAge == CacheFilter.MAX_AGE_NO_INIT || maxAge == CacheFilter.MAX_AGE_TIME) {
					// do nothing
				} else if (maxAge > 0) { // set max-age based on life time
					long currentMaxAge = maxAge / 1000 - System.currentTimeMillis() / 1000;
					if (currentMaxAge < 0) {
						currentMaxAge = 0;
					}
					httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + currentMaxAge);
				} else {
					httpResponse.addHeader(CacheFilter.HEADER_CACHE_CONTROL, "max-age=" + (-maxAge));
				}

			}
		}

		if (locale != null) {
			response.setLocale(locale);
		}

		OutputStream out = new BufferedOutputStream(response.getOutputStream());

		if (isContentGZiped()) {
			if (acceptsGZip) {
				((HttpServletResponse) response).addHeader(CacheFilter.HEADER_CONTENT_ENCODING, "gzip");
				response.setContentLength(content.length);
				out.write(content);
			} else {
				// client doesn't support, so we have to uncompress it
				ByteArrayInputStream bais = new ByteArrayInputStream(content);
				GZIPInputStream zis = new GZIPInputStream(bais);

				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				int numBytesRead = 0;
				byte[] tempBytes = new byte[4196];

				while ((numBytesRead = zis.read(tempBytes, 0, tempBytes.length)) != -1) {
					baos.write(tempBytes, 0, numBytesRead);
				}

				byte[] result = baos.toByteArray();

				response.setContentLength(result.length);
				out.write(result);
			}
		} else {
			// the content isn't compressed
			// regardless if the client browser supports gzip we will just
			// return the content
			response.setContentLength(content.length);
			out.write(content);
		}
		out.flush();
	}

	/**
	 * @return true if the content is GZIP compressed
	 */
	public boolean isContentGZiped() {
		return "gzip".equals(contentEncoding);
	}


SplitServletOutputStream.java

package com.dukuai.metis.search.servlet;

import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletOutputStream;

/**
 * Extends the base <code>ServletOutputStream</code> class so that the stream
 * can be captured as it gets written. This is achieved by overriding the
 * <code>write()</code> methods and outputting the data to two streams - the
 * original stream and a secondary stream that is designed to capture the
 * written data.
 * 
 * @version $Revision: 393 $
 * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
 */
public class SplitServletOutputStream extends ServletOutputStream {
	OutputStream captureStream = null;
	OutputStream passThroughStream = null;

	/**
	 * Constructs a split output stream that both captures and passes through
	 * the servlet response.
	 * 
	 * @param captureStream The stream that will be used to capture the data.
	 * @param passThroughStream The pass-through
	 *            <code>ServletOutputStream</code> that will write the
	 *            response to the client as originally intended.
	 */
	public SplitServletOutputStream(OutputStream captureStream, OutputStream passThroughStream) {
		this.captureStream = captureStream;
		this.passThroughStream = passThroughStream;
	}

	/**
	 * Writes the incoming data to both the output streams.
	 * 
	 * @param value The int data to write.
	 * @throws IOException
	 */
	public void write(int value) throws IOException {
		captureStream.write(value);
		passThroughStream.write(value);
	}

	/**
	 * Writes the incoming data to both the output streams.
	 * 
	 * @param value The bytes to write to the streams.
	 * @throws IOException
	 */
	public void write(byte[] value) throws IOException {
		captureStream.write(value);
		passThroughStream.write(value);
	}

	/**
	 * Writes the incoming data to both the output streams.
	 * 
	 * @param b The bytes to write out to the streams.
	 * @param off The offset into the byte data where writing should begin.
	 * @param len The number of bytes to write.
	 * @throws IOException
	 */
	public void write(byte[] b, int off, int len) throws IOException {
		captureStream.write(b, off, len);
		passThroughStream.write(b, off, len);
	}

	/**
	 * Flushes both the output streams.
	 * 
	 * @throws IOException
	 */
	public void flush() throws IOException {
		super.flush();
		captureStream.flush(); // why not?
		passThroughStream.flush();
	}

	/**
	 * Closes both the output streams.
	 * 
	 * @throws IOException
	 */
	public void close() throws IOException {
		super.close();
		captureStream.close();
		passThroughStream.close();
	}

}



分享到:
评论

相关推荐

    serlvet 的过滤器实现缓存机制

    在本篇文章中,我们将深入探讨如何使用Servlet过滤器来实现缓存机制,以此提高Web应用的性能。 首先,我们需要了解什么是缓存。缓存是一种存储技术,用于临时存储频繁访问的数据,以便快速检索。在Web应用中,通过...

    java servletfilter实现全站动转静

    Java Servlet Filter实现全站动态转静态是一种常见的优化网站性能的技术,它通过在服务器端拦截请求,将原本需要动态生成的页面转换为预先生成的静态HTML文件进行返回,从而减轻服务器负担,提高页面加载速度,提升...

    servlet filter

    在Servlet规范中,Filter通过实现`javax.servlet.Filter`接口来创建。这个接口定义了三个方法:`init()`, `doFilter()`, 和 `destroy()`。`init()`方法在Filter实例被创建并添加到Filter链中时调用,用于初始化...

    servlet filter大全

    "servlet filter大全"这个主题涵盖了多种常见的过滤器设置,旨在提高应用的功能性和安全性。下面我们将详细探讨这些过滤器及其用途。 1. **字符集过滤器**: 这种过滤器的主要任务是确保请求和响应中的字符集正确...

    Web应用与开发作业

    (2)有3个http响应头字段可以禁止浏览器缓存当前页面,它们在Servlet中的示例代码如下。 response.setDateHeader("Expires",-1); response.setHeader("Cache-Control","no-cache"); response.setHeader("Pragma",...

    使用filter验证session用户和页面缓存问题处理

    综上所述,通过使用Java的Servlet Filter,我们可以实现对用户session的有效验证,防止未授权访问,并且可以控制页面不被浏览器缓存,提高系统的安全性。在实际开发中,我们可能还需要结合其他安全措施,如CSRF防护...

    servlet过滤器技术实例,

    在本实例中,我们将深入探讨Servlet过滤器(Filter)的使用和实现,以及它在实际应用中的重要性。 一、Servlet过滤器简介 Servlet过滤器遵循Java Servlet规范,通过实现`javax.servlet.Filter`接口来创建自定义过滤...

    使用filter拦截servlet和jsp页面的内容,进行过滤后输出

    本文将详细介绍如何使用filter来对servlet和jsp页面的内容进行过滤,并在过滤后输出。 首先,了解Servlet Filter的基本概念。Filter是Java Servlet API的一部分,它允许开发者在请求到达目标Servlet或JSP之前以及...

    OSCACHE配置URL实现页面缓存的Fliter(修改配置无需重启)

    标题 "OSCACHE配置URL实现页面缓存的Filter(修改配置无需重启)" 提示我们讨论的是一个使用OSCache(OpenSymphony Cache)库来缓存Web应用程序中特定URL页面的过滤器配置。OSCache是一个开源的Java缓存框架,用于提高...

    jsp 页面缓存

    这时,可以使用Servlet Filter或自定义Tag Library来拦截请求,根据条件判断是否从缓存中读取数据,或者在数据更新后更新缓存。例如,我们可以使用Spring框架的`Cache`抽象层来管理缓存,或者使用第三方库如Ehcache...

    servlet实现分页效果

    本教程将探讨如何使用Servlet来实现一个高效的分页效果,类似于百度搜索引擎的展示方式。分页是网页应用中常见的功能,它能帮助用户浏览大量数据而无需一次性加载所有内容,从而提高用户体验。 首先,我们需要理解...

    检验用户名、密码、字符编码、页面缓存----Filter的基本使用

    总结,Filter是Java Web开发中的强大工具,它可以用于实现多种功能,包括用户认证、数据验证、字符编码管理和页面缓存控制等。熟练掌握Filter的使用,能帮助我们构建更加安全、高效和灵活的Web应用程序。通过实践和...

    Servlet_Filter

    同样,Filter也可以在Servlet响应后对响应内容进行操作,如添加缓存控制头,加密敏感信息等。 在实际开发中,Filter的使用可以提高代码的可维护性和复用性,因为它们将通用的逻辑从Servlet中分离出来。例如,通过...

    Filter(过滤器)简介和工作原理

    * 缓存处理:可以使用 Filter 来实现缓存处理,例如缓存常用的静态资源。 * 数据压缩:可以使用 Filter 来实现数据压缩,例如压缩 HTML、CSS 和 JavaScript 文件。 Filter 是 Java EE 中的一种强大且灵活的组件,...

    java shiro实现退出登陆清空缓存

    这里没有给出具体的缓存清理代码,因为实际应用中可能涉及不同的缓存实现(如 Redis、Memcached 或 Ehcache),清理缓存的方法会因缓存技术而异。 总之,通过扩展 Shiro 的 `LogoutFilter`,我们可以在用户登出时...

    基于Servlet的图书馆管理系统

    2. **JSP(JavaServer Pages)**:JSP是Java Web开发中的视图技术,与Servlet配合使用,负责生成动态网页内容。在本系统中,JSP用于创建用户界面,如登录页面、图书列表、借阅操作等,通过内置的Java表达式和脚本...

    servlet3 api 文档

    Servlet3.1引入了预定义的Filter和Servlet,例如`ContainerLoginService`和`ErrorPage`,这些预定义的组件可以帮助开发者更快地实现常见的功能,如用户认证和错误页面处理。 4. **WebSocket支持**: 该版本添加了...

    JSP Servlet 学习笔记源码

    了解缓存机制、减少不必要的数据库查询、使用高效的模板技术等都是提升JSP Servlet应用性能的关键。 通过学习和实践这些知识点,开发者不仅能掌握JSP Servlet的基本用法,还能深入理解Web开发的原理,为构建高效、...

Global site tag (gtag.js) - Google Analytics