十一、Filter 过滤器
11.1 什么是Filter
访问某个servlet,可以先经过filter,再访问servlet,返回时也经过filter,画个图:
也就是在servlet之前加了一层过滤器。
有什么用呢?比如我们可以用它来处理中文乱码而不用在每个servlet中都加上这样一段代码:
req.setCharacterEncoding("utf8");
resp.setContentType("text/html;charset=utf8");
假如有这么一个servlet,直接输出那多半是中文乱码:
public class DealWithEncoding extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("解决乱码问题");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
解决方法:添加一个过滤器,用于设置编码。
怎么做呢?
-
导入依赖
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
-
实现Filter接口
注意!一定要是javax.servlet下的Filter
import javax.servlet.*; import java.io.IOException; public class CharacterEncodingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 观察控制台发现,过滤器在服务器启动时就已经初始化 System.out.println("filter初始化"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 解决乱码问题 servletRequest.setCharacterEncoding("utf8"); servletResponse.setContentType("text/html;charset=utf8"); // 过滤 filterChain.doFilter(servletRequest, servletResponse); System.out.println("过滤成功"); } @Override public void destroy() { // 再服务器关闭时才销毁 System.out.println("filter销毁"); } }
-
注册过滤器,和注册servlet一毛一样,url-pattern表示访问哪个servlet时要经过过滤器
<filter> <filter-name>test</filter-name> <filter-class>vip.yangsf.filter.CharacterEncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>test</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
配置好后,当我们访问相应的servlet,就会解决乱码问题。
11.2 Filter权限拦截
现在有这么三个jsp页面,要实现,用户名为admin即可跳转到成功页面,不为admin则跳转到失败页面:
-
登录界面,名为login.jsp
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %> <html> <head> <title>登录</title> </head> <body> <form action="/servlet/login" name="username" method="post"> 用户名:<input type="text" name="user"> <br> <input type="submit" value="登录"> </form> </body> </html>
-
登录成功页面,名为success.jsp
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %> <html> <head> <title>秘密乐园</title> </head> <body> vip用户页面 <a href="/servlet/logout">注销</a> </body> </html>
-
登录失败页面,名为failed.jsp
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %> <html> <head> <title>失败</title> </head> <body> <h1> 对不起,您没有权限 <br> <a href="/servlet/logout">返回登录页面</a> </h1> </body> </html>
通过代码我们也看出来了,我们写了一个servlet实现验证以及页面跳转,一个servlet实现注销,可以通过session验证:
/*为经常使用的常量创建一个常量类
public class Constant {
public final static String USER_NAME = "USER_NAME";
}
*/
public class CheckUser extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取表单数据
String user = req.getParameter("user");
// 验证用户名是否正确
if ("admin".equals(user)) {
req.getSession().setAttribute(Constant.USER_NAME, req.getSession().getId());
resp.sendRedirect("/sys/success.jsp");
} else {
resp.sendRedirect("/failed.jsp");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注册servlet:
<servlet>
<servlet-name>login3</servlet-name>
<servlet-class>vip.yangsf.servlet.CheckUser</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login3</servlet-name>
<url-pattern>/servlet/login</url-pattern>
</servlet-mapping>
通过删除session的值注销(创建销毁session耗资源,删掉值就可以了):
public class LogOut extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取session的值
String user = (String) req.getSession().getAttribute(Constant.USER_NAME);
// 如果有值就删除,并跳转到登录界面
if (user != null) {
req.getSession().removeAttribute(Constant.USER_NAME);
resp.sendRedirect("/Login.jsp");
} else {
resp.sendRedirect("/Login.jsp");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注册servlet:
<servlet>
<servlet-name>LogOut</servlet-name>
<servlet-class>vip.yangsf.servlet.LogOut</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogOut</servlet-name>
<url-pattern>/servlet/logout</url-pattern>
</servlet-mapping>
这样确实可以实现功能,但是,会有安全问题,比如我们直接访问localhost:8080/sys/success.jsp也能访问到登录成功页面,怎么办呢?
可以通过filter过滤掉这些请求:
public class LoginIndex implements Filter {
@Override
public void init(FilterConfig filterConfig) {
System.out.println("开启权限检测");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 把ServletRequest转成HttpServletRequest,因为要使用HttpServletRequest里面的方法
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 获取session的值
String user = (String) request.getSession().getAttribute(Constant.USER_NAME);
// 检查是否是恶意访问,如果没有用户名,就跳转到失败页面
if (request.getSession().getAttribute(Constant.USER_NAME) == null) {
response.sendRedirect("/failed.jsp");
}
// 过滤
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("服务已关闭");
}
}
注册filter:
<filter>
<filter-name>check</filter-name>
<filter-class>vip.yangsf.filter.LoginIndex</filter-class>
</filter>
<filter-mapping>
<filter-name>check</filter-name>
<url-pattern>/sys/*</url-pattern>
</filter-mapping>
访问sys目录下的文件就要经过这个filter
于是我们就用filter实现了权限拦截。
十二、Listener 监听器
用于监听一些事件,比如我们可以通过监听session的创建和销毁来统计在线人数,直接开干:
-
实现一个监听器接口(我们监听session就实现javax.servlet.http下的HttpSessionListener接口)
import javax.servlet.ServletContext; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class SessionListened implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { } }
-
监听session的创建和销毁,设置、获取上下文属性。
import javax.servlet.ServletContext; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class SessionListened implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { // 通过session事件获取session,然后获取上下文对象。 ServletContext context = httpSessionEvent.getSession().getServletContext(); // 在控制台输出sessionid System.out.println(httpSessionEvent.getSession().getId()); // 增加在线人数,通过设置servlet上下文实现+1 Integer onlineCount = (Integer) context.getAttribute("onlineCount"); if (onlineCount == null) { onlineCount = 1; } else { onlineCount++; } context.setAttribute("onlineCount", onlineCount); } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { ServletContext context = httpSessionEvent.getSession().getServletContext(); // 销毁session,通过设置上下文实现人数-1 httpSessionEvent.getSession().invalidate(); Integer onlineCount = (Integer) context.getAttribute("onlineCount"); if (onlineCount ==null) { onlineCount = 0; } else { onlineCount--; } context.setAttribute("onlineCount", onlineCount); } }
-
编写jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>在线人数</title> </head> <body> <h1> 当前在线人数:<span><%=this.getServletConfig().getServletContext().getAttribute("onlineCount")%></span> </h1> </body> </html>
-
注册监听器
<listener> <listener-class>vip.yangsf.listener.SessionListened</listener-class> </listener>
-
这样,我们就实现了统计在线人数的功能。