Filter
Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。
Filter介绍
- 入门案例:
@WebFilter(filterName = "MyFilter",urlPatterns = "/*")//过滤所有,包括jsp
public class MyFilter implements Filter { //过滤器优先级与类名有关,按字符串顺序
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器1...init...执行了");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse
servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("过滤器1...doFilter...放行前");
//放行(执行目标资源)
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("过滤器1...doFilter...放行后");
}
@Override
public void destroy() {
System.out.println("过滤器1...destroy...执行了");
}
}
运行结果:
可以看到过滤器随着服务器的开启初始化,且不能像servlet一样设置初始化时间(开启时还是第一次访问时);随着服务器的关闭销毁
- 配置:
注解式配置:
在自定义的Filter类上使用注解@WebFilter(“/*”) (上述案例即使用此配置方式)
xml配置:
在web.xml中进行过滤器的配置
<!-- web.xml配置方式:-->
<!--过滤器优先级与配置顺序有关-->
<filter>
<filter-name>myfilter2</filter-name>
<filter-class>MyFilter2</filter-class>
</filter>
<filter-mapping>
<filter-name>myfilter2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>myfilter1</filter-name>
<filter-class>MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myfilter1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 过滤器链及优先级问题:
每个过滤器实现某个特定的功能,一个过滤器检测多个Servlet。(匹配几个,检测几个)。
通常客户端对服务器请求之后,服务器调用Servlet之前会执行一组过滤器(多个过滤器),
那么这组过滤器就称为一条过滤器链。
在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。
在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,
则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
如果为注解的话,是按照类名的字符串顺序进行起作用的
如果web.xml,执行顺序与<filter-mapping>的配置顺序有关,从上往下
web.xml配置高于注解方式(两者共存互不干扰时)
注:同一个过滤器不要同时使用 web.xml 和 注解配置,会使服务器重复加载该过滤器:
- 过滤器的初始化参数:
//基于注解的:
@WebFilter(value="/*",initParams= {@WebInitParam(name = "version", value = "1.0")})
//基于xml配置:
<filter>
<filter-name>myfilter</filter-name>
<filter-class>MyFilter</filter-class>
<!--过滤器的初始化参数 -->
<init-param>
<param-name>version</param-name>
<param-value>1.0</param-value>
</init-param>
</filter>
使用示例:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器1...init...执行了");
System.out.println("初始化参数:版本号:"+filterConfig.getInitParameter("version"));
}
运行结果:
Filter的典型应用
可以实现 Web 应用程序中的预处理和后期处理逻辑 。
- 过滤器控制浏览器缓存:
对于目前现在的浏览器,get请求动态资源缓存问题已经解决。
对于静态资源部分浏览器(IE,FixFox)使用Cache-Control头和Expires头设置缓存时间。chrome浏览器设置不设置都无效而是每次都请求服务器。
对于静态资源服务器会采用304状态码控制是否再次发送数据,从而节省带宽;可以通过Cache-Control=no-store控制304无效。
- 禁止浏览器缓存动态页面:
@WebFilter(filterName = "NoCacheFilter",urlPatterns = "*.jsp")//jsp页面不要缓存好,静态页面要缓存好
public class NoCacheFilter implements Filter {
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse
resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("pragma", "no-cache");
response.setHeader("expires", "0");
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {}
}
- 使浏览器缓存静态资源:
@WebFilter(filterName = "CacheFilter",urlPatterns = {"*.html","*.css","*.jpg"})
public class CacheFilter implements Filter { //静态页面要缓存
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse
resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//这两个属性对chrome无效
response.setDateHeader("expires", System.currentTimeMillis() + 600000);
//毫秒-10分钟
response.setHeader("cache-control", "max-age=600");//秒-10分钟
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {}
}
响应报文头:
ETag:令牌
设置后用 chrome 第二次请求静态资源:
设置后用 ie 第二次请求静态资源:
- 自动登录:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<h2>用户登录</h2>
<form action="${pageContext.request.contextPath}/login" method="post">
<input type="text" name="username" placeholder="请输入用户名"><br/>
<input type="password" name="password" placeholder="请输入密码"><br/>
<input type="submit" value="登录">
${msg}
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>网站首页</title>
</head>
<body>
<h2>欢迎访问我的网站</h2>
当前用户:${empty username?"<a href='login.jsp'>请登录</a>":username}
</body>
</html>
@WebServlet(name = "LoginServlet",urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//乱码
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//接受数据
String username = request.getParameter("username");
String password = request.getParameter("password");
//判断
if(username.equals("admin")&&password.equals("123456")){
//正确
//放入session
request.getSession().setAttribute("username", username);
//创建cookie
String cookievalue=username+"#"+password;
//BASE64Encoder base64Encoder=new BASE64Encoder();
//String encode=base64Encoder.encode(cookievalue.getBytes("utf-8"));
String encode = Base64.getEncoder().encodeToString(cookievalue.getBytes("utf-8"));
Cookie cookie=new Cookie("userinfo", encode);
//1有效期
cookie.setMaxAge(60*60*24*7);
//2有效路径
cookie.setPath("/");
//3设置httponly
cookie.setHttpOnly(true);
response.addCookie(cookie);
response.sendRedirect(request.getContextPath()+"/index.jsp");
}else{
request.setAttribute("msg", "用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
@WebFilter(filterName = "AutoLoginFilter", urlPatterns = "/index.jsp")
public class AutoLoginFilter implements Filter {
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//处理
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//1先查看session有没有数据
String username = (String) request.getSession().getAttribute("username");
System.out.println("-------------" + username);
if (username == null) {
//2再查看cookie
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
String name = cookie.getName();
if (name.equals("userinfo")) {
String value = cookie.getValue();
BASE64Decoder decoder = new BASE64Decoder();
byte[] bs = decoder.decodeBuffer(value);
String userinfo = new String(bs, "utf-8");
String[] userinfos = userinfo.split("#");
if (userinfos[0].equals("admin") && userinfos[1].equals("123456")) {
//自动登录
request.getSession().setAttribute("username", userinfos[0]);
} else {
//删除
Cookie cookie1 = new Cookie("userinfo", "username");
cookie1.setMaxAge(0);
cookie1.setPath("/");
cookie1.setHttpOnly(true);
response.addCookie(cookie1);
}
}
}
}
}
//放行
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {}
}
- 过滤器解决编码:
public class CharacterEncodingFilter implements Filter {
private String defaultCharset = "utf-8";
private FilterConfig config;
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//获取配置中的编码
String charset = config.getInitParameter("charset");
if (charset == null) {
charset = defaultCharset;
}
System.out.println(charset);
req.setCharacterEncoding(charset);
resp.setContentType("text/html;charset=" + charset);
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
}
- 过滤器解决脏词:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@WebFilter(filterName = "DirtyFilter", urlPatterns = "/comment")
public class DirtyFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//放行(执行目标资源)
HttpServletRequest request = (HttpServletRequest) req;
DirtyHttpServletRequest drequest = new DirtyHttpServletRequest(request);
chain.doFilter(drequest, resp);
}
/*
增强方法的功能:
1 继承重写
2 装饰者模式
3 动态代理
*/
//此处使用"2 装饰者模式",HttpServletRequestWrapper为抽象类,DirtyHttpServletRequest为装饰者实现
static class DirtyHttpServletRequest extends HttpServletRequestWrapper {
private static List<String> dirtyWords = null;
static {
dirtyWords = Arrays.asList("狗蛋", "扯淡", "sb");
}
public DirtyHttpServletRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String paraValue = super.getParameter(name);
//过滤
for (String dirtyWord : dirtyWords) {
paraValue = paraValue.replaceAll(dirtyWord, "***");
}
System.out.println("过滤完成.......");
return paraValue;
}
}
}
- 响应内容压缩:
@WebFilter(filterName = "GzipFilter", urlPatterns = "*.jsp")
public class GzipFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletResponse response = (HttpServletResponse) resp;
GzipHttpServletResponse gresponse = new GzipHttpServletResponse(response);
response.setContentType("text/html;charset=utf-8");
//放行(执行目标资源)
chain.doFilter(req, gresponse);
//把内容压缩后再给浏览器
ByteArrayOutputStream baos = gresponse.getBaos();
System.out.println("压缩之前:" + baos.size());
//新的内存流
ByteArrayOutputStream newbaos = new ByteArrayOutputStream();
//压缩
GZIPOutputStream gzip = new GZIPOutputStream(newbaos);
gzip.write(baos.toByteArray());
gzip.flush();
gzip.close();
System.out.println("压缩之后:" + newbaos.size());
//添加响应头,告诉浏览器内容是压缩的
response.setHeader("Content-Encoding", "gzip");
//把压缩的内容给浏览器
response.getOutputStream().write(newbaos.toByteArray());
}
//装饰者模式
static class GzipHttpServletResponse extends HttpServletResponseWrapper {
private ByteArrayOutputStream baos;
private PrintWriter pw;
public GzipHttpServletResponse(HttpServletResponse response) {
super(response);
//初始化pw
baos = new ByteArrayOutputStream();
}
public ByteArrayOutputStream getBaos() {
if (pw != null) {
pw.flush();//把内容刷新到底层流
}
return baos;
}
@Override
public PrintWriter getWriter() throws IOException {
if (pw == null) {
pw = new PrintWriter(new OutputStreamWriter(baos, "utf-8"));
}
return pw;
}
}
}
- 实现图片防盗链:
http 协议中,如果从一个网页跳到另一个网页,http 头字段里面会带个 Referer。图片服务器通过检测 Referer 是否来自规定域名,来进行防盗链(防止其它网站盗用图片,浪费流量)
@WebFilter(filterName = "StealFilter",urlPatterns ={"*.jpg","*.png","*.bmp"})
public class StealFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {}
@Override
public void destroy() {}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request= (HttpServletRequest) req;
HttpServletResponse response= (HttpServletResponse) resp;
String referer = request.getHeader("referer");
System.out.println(referer+"---------------");
if(referer!=null&&referer.startsWith("http://localhost:8080"+request.getContextPath())){
//正常访问
chain.doFilter(req, resp);
}else{
request.getRequestDispatcher("/img/steal.jpg").forward(request, response);
}
}
}
Listener
监听器用于监听web应用中某些对象(request,session,application)、信息的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计访问人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等等。
实现原理:
Listener介绍
- 生命周期监听器:
ServletContext上下文监听器:
ServletContextListener接口
内部方法:
初始化:contextInitialized
销毁:contextDestroyed
Session监听器:
HttpSessionListener
内部方法:
sessionCreated:监听Session对象的创建
sessionDestroyed:监听Session对象的销毁
request监听器:
ServletRequestListener
监听request对象的初始化和销毁
内部方法:
1、requestInitialized:监听request对象的初始化
2、requestDestroyed:监听request对象的销毁
- 属性变化监听器:
ServletContext属性变化监听器:
ServletContextAttributeListener接口
内部的方法:
attributeAdded:监听属性的添加
attributeRemoved:监听属性的移除
attributeReplaced:监听属性的修改
session属性变化监听器:
HttpSessionAttributeListener
监听HttpSession的内容的变化
内部的方法:
attributeAdded:监听属性的添加
attributeRemoved:监听属性的移除
attributeReplaced:监听属性的修改
request属性变化监听器:
ServletRequestAttributeListener
监听属性内容变化
内部方法:
attributeAdded:监听属性的添加
attributeRemoved:监听属性的移除
attributeReplaced:监听属性的修改
- 感知型监听器:
对象从session添加或移除:
HttpSessionBindingListener
监听对象的添加和移除
内部方法:
valueBound:监听对象的绑定
valueUnbound:监听对象的解除绑定
对象钝化和活化:
HttpSessionActivationListener
监听session的钝化和活化
内部方法:
sessionWillPassivate:监听Session内部存储对象的钝化-写入
sessionDidActivate:监听Session内部存储对象的活化-读取
对应类需要实现序列化接口Serializable
在 web目录下创建META-INF文件夹,在META-INF文件夹中创建context.xml文件
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="qf">
</Store>
</Manager>
</Context>
<!-- maxIdleSwap: session中的对象多长时间不使用就被钝化 (分钟单位) -->
<!-- directory: 钝化后的对象的文件写到磁盘的哪个目录下 -->
<!-- 配置钝化的对象文件在${TOMCAT_HOME}/work/Catalina/localhost/YOUR_PROJECT/qf -->
- 监听器的两种配置:
在web.xml中进行配置:
<listener>
<!--直接写出自定义的监听器的类名即可-->
<listener-class>com.qf.web.listener.RequestLeftListener</listener-class>
</listener>
Servlet3.0之后新增的,使用注解@WebListener进行监听器的注册
Listener应用
- 生命周期监听器:
/**
* ServletContext监听器作用:1启动应用程序时提前加载类,做一些初始化工作 2关闭应用程序时,释放资源。关闭连接池
*/
//@WebListener
public class ApplicationListener implements ServletContextListener {
//上下文初始化
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext applcation = sce.getServletContext();
System.out.println("servletcontext创建了..."+applcation.toString());
}
//上下文销毁
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext applcation = sce.getServletContext();
System.out.println("servletcontext销毁了..."+applcation.toString());
}
}
/**
* sesssionListener: 1 初始化与用户相关的数据 (权限信息) 2 释放与用户有关的信息
* (1)统计当前访问用户
*/
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session创建了session:"+session.getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session销毁了session:"+session.getId());
}
}
/**
* RequestListener:统计应用程序一天请求次数。
*/
//@WebListener
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
System.out.println("请求对象销毁了..."+request.toString());
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
System.out.println("请求对象创建了..."+request.toString());
}
}
- 属性变化监听器:
//@WebListener
public class AttributeListener implements ServletContextAttributeListener, HttpSessionAttributeListener, ServletRequestAttributeListener {
//application属性变化
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
System.out.println("向application中添加:" + name + ":" + value);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
System.out.println("向application中移除:" + name + ":" + value);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
System.out.println("向application中替换:" + name + ":" + value);
}
//session属性变化
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
System.out.println("向session中添加:" + name + ":" + value);
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
System.out.println("向session中移除:" + name + ":" + value);
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
System.out.println("向session中替换:" + name + ":" + value);
}
//request属性变化
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
System.out.println("向request中添加:" + name + ":" + value);
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
System.out.println("向request中移除:" + name + ":" + value);
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
System.out.println("向request中替换:" + name + ":" + value);
}
}
- 实现访问人数统计:
//@WebListener
public class VisitCountListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
ServletContext application = se.getSession().getServletContext();
Integer count = (Integer) application.getAttribute("count");
if (count == null) {
application.setAttribute("count", 1);
} else {
count++;
application.setAttribute("count", count);
}
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
ServletContext application = se.getSession().getServletContext();
Integer count = (Integer) application.getAttribute("count");
if (count > 0) {
count--;
application.setAttribute("count", count);
}
}
}
- 自定义Session管理器:
session会话的默认有效时间为30分钟,需要使用开发人员自己管理session的有效期,不需要系统管理。
功能1:把所有的session放入集合中。
功能2:遍历集合中每个session, 判断session有多长时间没有被访问了,如果超过1分钟,置session为失效
//@WebListener
public class SessionManagerListener implements HttpSessionListener, ServletContextListener {
private Timer timer;
private static List<HttpSession> sessions;
static {
sessions = Collections.synchronizedList(new LinkedList<>());
}
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("定时器启动了....");
//初始化timer
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (sessions) {
Iterator<HttpSession> it = sessions.iterator();
while (it.hasNext()) {
//判断是否超过1沒有访问
HttpSession session = it.next();
long d = System.currentTimeMillis() - session.getLastAccessedTime();
if (d > 60000) {
System.out.println("session失效了:" + session.getId());
session.invalidate();
it.remove();
}
}
}
}
}, 0, 1000);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
timer.cancel();
System.out.println("定时器关闭了....");
}
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("添加了一个session");
sessions.add(se.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
}
}
- 感知型监听器:
public class User implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable {
private String userid;
private String username;
private String password;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("绑定session:" + this.userid + "..." + this.username);
//获取application
ServletContext application = event.getSession().getServletContext();
Integer logincount = (Integer) application.getAttribute("logincount");
if (logincount == null) {
logincount = 1;
} else {
logincount++;
}
application.setAttribute("logincount", logincount);
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("解除绑定session:" + this.userid + "..." + this.username);
ServletContext application = event.getSession().getServletContext();
Integer logincount = (Integer) application.getAttribute("logincount");
if (logincount > 0) {
logincount--;
}
application.setAttribute("logincount", logincount);
}
//钝化(序列化)
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("sessionid:" + session.getId() + "对象钝化了...." + this.toString());
}
//活化(反序列)
@Override
public void sessionDidActivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("sessionid:" + session.getId() + "对象活化了...." + this.toString());
}
}
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="qq">
</Store>
</Manager>
</Context>
<!-- maxIdleSwap: session中的对象多长时间不使用就被钝化 (分钟单位) -->
<!-- directory: 钝化后的对象的文件写到磁盘的哪个目录下 -->
<!-- 配置钝化的对象文件在${TOMCAT_HOME}/work/Catalina/localhost/YOUR_PROJECT/qq -->