1.过滤器监听器概念引入
过滤器:
Filter也称为过滤器,它是Servlet中最实用的技术,Web开发人员通过Filter技术,对web服务器管理所有的web资源:例如jsp,servlet,静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
过滤器主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
过滤器是如何实现功能的:
1.在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest,根据需要检查HttpServletRequest,也可以修改HttpServletRequest的请求头和数据。
2.在HttpServletResponse到达客户端之前,拦截HttpServletResponse。根据需要检查HttpServletResponse,也可以修改HttpServletResponse的响应头和响应体中的数据。
3.Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,web服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,doFilter方法中有一个filterChain对象,用于继续传递给下一个filter。在传递之前我们可以定义过滤请求的功能,在传递之后我们可以定义过滤响应的功能。
总结:来时过滤请求,去时过滤响应。我们可以通过过滤器实现比如权限控制等功能,根据用户的不同权限开放不同的页面
2.过滤器的编写和配置
采用三步走的策略使用filter:
1.开发后台资源(静态资源 如html,css或者动态资源 如Servlet,jsp)
2.开发Filter,实现javax.servlet.Filter接口,重写抽象方法(init,destroy,doFilter)
3.在web.xml中配置Filter拦截哪些资源
1.Servlet
package com.example.filter;/**
* @Author:zhoayu
* @Date:2023/12/12 23:07
* @Description:com.example.filter
* @version:1.0
*/
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName MyServlet
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/12
*/
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("myservlet 执行了");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().print("myservlet响应了");
}
}
2.Filter
package com.example.filter;/**
* @Author:zhoayu
* @Date:2023/12/12 23:12
* @Description:com.example.filter
* @version:1.0
*/
import javax.servlet.*;
import java.io.IOException;
/**
* @ClassName MyFilter
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/12
*/
public class MyFilter implements Filter {
//过滤器初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器对象创建了");
}
//过滤器销毁方法
@Override
public void destroy() {
System.out.println("过滤器对象销毁了");
}
//过滤动作
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤器开始过滤");
}
}
3.在web.xml中进行配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>myservlet1</servlet-name>
<servlet-class>com.example.filter.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myservlet1</servlet-name>
<url-pattern>/myservlet1.do</url-pattern>
</servlet-mapping>
<filter>
<filter-name>filter1</filter-name>
<filter-class>com.example.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<!--对哪个web资源的请求和响应进行过滤-->
<!--当请求url的请求路径是/myservlet1.do时,过滤器拦截请求/响应进行过滤。
如果这里的url-pattern是/,则过滤所有请求(不包含jsp),如果是/*,则过滤所有请求(包含jsp)-->
<url-pattern>/myservlet1.do</url-pattern>
<!--还可以直接指定servlet名称来指定对哪些servlet进行过滤,可以同时配置对多个servlet进行过滤-->
<servlet-name>myservlet1</servlet-name>
</filter-mapping>
</web-app>
请求servlet:
发现没有响应
后台日志
通过看后台日志可以发现:1.过滤器对象在服务器启动时被创建 2.过滤器拦截了我们的请求,并且我们的servlet中的逻辑没有执行(我们需要在doFilter中手动放行请求才可以)
过滤器放行请求
对Filter做一些修改,在执行完对请求的过滤逻辑后,放行请求,并在目标资源做出响应后,再次对响应进行过滤:
package com.example.filter;/**
* @Author:zhoayu
* @Date:2023/12/12 23:12
* @Description:com.example.filter
* @version:1.0
*/
import javax.servlet.*;
import java.io.IOException;
/**
* @ClassName MyFilter
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/12
*/
public class MyFilter implements Filter {
//过滤器初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器对象创建了");
}
//过滤器销毁方法
@Override
public void destroy() {
System.out.println("过滤器对象销毁了");
}
//过滤动作
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤器对请求做出过滤");
//放行请求,交给过滤器链继续进行过滤(过滤器1->过滤器2->过滤器3...->资源),目前我们只有一个过滤器,所以一旦放行 直接到达目标资源
chain.doFilter(request,response);
/*..目标资源逻辑..*/
//对响应做出过滤
System.out.println("过滤器对响应做出过滤");
}
}
此时再次请求可以看到,目标资源顺利返回了:
通过后台日志可以总结出过滤器-资源的执行顺序:
服务器启动->过滤器创建->请求->过滤器1对请求作出过滤->过滤器2对请求作出过滤->过滤器3对请求作出过滤->…->目标资源执行逻辑,做出响应->…->过滤器3对响应作出过滤->过滤器2对响应作出过滤->过滤器1对响应作出过滤->响应
3.过滤器的生命周期
同Servlet对象一样,Filter对象的创建也是交给web服务器完成的,在web服务器创建和使用及最后销毁filter时,会调用filter对应的方法。
filter的初始化方法:
default public void init(FilterConfig filterConfig) throws ServletException {}
和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,读取web.xml中的配置,完成对象的初始化功能,从而为后续的用户请求做好拦截的准备工作(Filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
filter的拦截请求方法:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
doFilter方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL或者Servlet时,过滤器将先执行doFilter方法。FilterChain参数用于将请求放行,让其访问后续资源(如果有过滤器,就到下一个过滤器,如果没有 就放行到目标资源)。
filter的销毁方法:
default public void destroy() {}
Filter对象创建后会驻留在内存,当web应用被移除或者服务器停止时才销毁。destroy方法在Web容器销毁Filter对象之前被调用,该方法在Filter的生命周期中仅执行一次,我们可以在这个方法中执行一些释放过滤器使用的资源等操作。
总结
1.Web容器启动时,会创建Filter对象并执行init()方法,这个Filter对象是单例(重复的请求不会重复创建Filter对象)
2.当请求Filter拦截的资源时,Filter对象执行doFilter()方法,doFilter()方法对请求&响应进行过滤
3.Web容器关闭时,销毁Filter对象,执行destroy()方法
4.过滤器链的使用
在一个Web应用中,我们可以针对一个Web资源写多个Filter,并把它们组合起来成为一个过滤器链。
Web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter对象的doFilter()方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象作为入参传递给doFilter()方法。在doFilter()方法中,开发人员如果调用了FilterChain对象的doFilter方法,web服务器会检查FilterChain对象中是否还有Filter,如果有,则调用第二个Filter,如果没有,则调用目标资源。
使用过滤器链的好处是我们可以将不同的过滤逻辑分散到多个过滤器中,分工明确,避免出现一个过滤器做太多的业务处理,降低了代码的耦合度。这体现了单一职责的设计原则,应用了责任链的代码设计模式
ps:过滤器的执行顺序是由filter-mapping标签决定的
Filter1:
package com.example.filter;/**
* @Author:zhoayu
* @Date:2023/12/13 22:32
* @Description:com.example.filter
* @version:1.0
*/
import javax.servlet.*;
import java.io.IOException;
/**
* @ClassName Filter1
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/13
*/
public class Filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤器1执行了过滤请求动作");
chain.doFilter(request,response);
System.out.println("过滤器1执行了过滤响应动作");
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
Filter2:
package com.example.filter;/**
* @Author:zhoayu
* @Date:2023/12/13 22:33
* @Description:com.example.filter
* @version:1.0
*/
import javax.servlet.*;
import java.io.IOException;
/**
* @ClassName Filter2
* @Description //TODO
* @Author zhaoyu
* @Date 2023/12/13
*/
public class Filter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤器2执行了过滤请求动作");
chain.doFilter(request,response);
System.out.println("过滤器2执行了过滤响应动作");
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
web.xml:
<filter>
<filter-name>f1</filter-name>
<filter-class>com.example.filter.Filter1</filter-class>
</filter>
<filter>
<filter-name>f2</filter-name>
<filter-class>com.example.filter.Filter2</filter-class>
</filter>
<!--注意这里定义filter-mapping的顺序决定了一个filterchain中filter的顺序-->
<filter-mapping>
<filter-name>f1</filter-name>
<servlet-name>myservlet1</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>f2</filter-name>
<servlet-name>myservlet1</servlet-name>
</filter-mapping>
请求目标资源:
从后台日志分析Filter过滤请求和过滤响应时的执行顺序:
Web容器启动->Filter对象创建->Filter对象的init()方法执行->请求目标资源->Filter1过滤请求->Filter2过滤请求->目标资源执行逻辑作出响应->Filter2过滤响应->Filter1过滤响应->…->Web容器停止->Filter对象销毁
这里Filter在FilterChain中的顺序是通过Filter的filter-mapping标签在web.xml中的定义顺序来决定的。
过滤器可以修改请求,放行请求,甚至拦截请求不放行
5.过滤器初始化参数配置
同servlet一样(ServletConfig),filter也可以通过web.xml进行初始化配置,在初始化时,将参数封装进FilterConfig并在调用init方法时将FilterConfig对象作为实参传入。我们可以在init方法中获取参数。FilterConfig接口为我们提供了以下功能:
//过滤器初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器对象创建了");
}
//获得Filter的名称
public String getFilterName();
//返回指定名称的初始化参数的值,如果不存在对应参数则返回null
public String getInitParameter(String name);
//返回所有初始化参数的名字的枚举集合
public Enumeration<String> getInitParameterNames();
//返回ServletContext对象
public ServletContext getServletContext();
我们可以在web.xml中对Filter对象的初始化参数进行配置,这些key-value会在web容器启动时被设置到FilterConfig对象中,接着会把FilterConfig对象传入Filter实现类的init方法中
<filter>
<filter-name>f1</filter-name>
<filter-class>com.example.filter.Filter1</filter-class>
<init-param>
<param-name>name</param-name>
<param-value>zy</param-value>
</init-param>
<init-param>
<param-name>gender</param-name>
<param-value>nan</param-value>
</init-param>
<init-param>
<param-name>age</param-name>
<param-value>20</param-value>
</init-param>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
接着我们在上述的Filter实现类中重写init方法:
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//获取Filter名称
System.out.println(filterConfig.getFilterName());
//获取参数
System.out.println(filterConfig.getInitParameter("name"));
//获取所有参数名
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String pname = initParameterNames.nextElement();
System.out.println(filterConfig.getInitParameter(pname));
}
}
6.过滤器中使用注解
使用注解来开发过滤器,通过使用注解来简化web.xml的配置:
使用注解模式开发Filter:
package com.example.filter;/**
* @Author:zhoayu
* @Date:2024/1/2 15:58
* @Description:com.example.filter
* @version:1.0
*/
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import java.io.IOException;
/**
* @ClassName FilterWithAnnotation
* @Description //TODO
* @Author zhaoyu
* @Date 2024/1/2
*/
@WebFilter(filterName = "filterwithannotation",
urlPatterns = {"/myservlet1.do","/myservlet2.do"},
servletNames = {"myservlet","myservlet2"},
initParams = {@WebInitParam(name = "name",value = "zy"),@WebInitParam(name = "charset",value = "utf-8")}
)
public class FilterWithAnnotation implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FilterWithAnnotation执行了");
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
在web.xml配置filter时,可以通过在中的filter1和filter2的顺序来制定过滤器的顺序。但是使用注解模式时,怎么指定过滤器在过滤器链中的顺序呢?
使用注解模式开发Filter时,Filter的顺序是根据过滤器名称 即filterName=xxx 的字典序来确定的。
所以我们可以使用规范的命名方式来定义filter的顺序,eg:Filter0_myfilter1,Filter1_myfilter2…
7.过滤器处理POST乱码
login.jsp:提交账号密码
<%--
Created by IntelliJ IDEA.
User: zhaoyu
Date: 2024/1/2
Time: 16:16
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
please login ... ... <br/>
<form action="${pageContext.request.contextPath}/loginController.do" method="post">
用户名:<input type="text" name="user"> <br/>
密码:<input type="text" name="pwd"> <br/>
<input type="submit" value="提交">
</form>
</body>
</html>
package com.example.test;/**
* @Author:zhoayu
* @Date:2024/1/2 16:18
* @Description:com.example.test
* @version:1.0
*/
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
/**
* @ClassName ServletLogin
* @Description //TODO
* @Author zhaoyu
* @Date 2024/1/2
*/
@WebServlet("/loginController.do")
public class loginController implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//获取用户名&密码
String name = req.getParameter("user");
String pwd = req.getParameter("pwd");
System.out.println(name);
System.out.println(pwd);
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
post请求乱码:
我们可以设置字符集编码格式为UTF-8(将字符集编码格式的设置交给过滤器来做):对所有请求,都将其字符集格式设置为UTF-8。这样做的好处是不用在所有Servlet的service方法中手动执行
req.setCharacterEncoding("UTF-8");
LoginFilter:
package com.example.test;/**
* @Author:zhoayu
* @Date:2024/1/2 16:33
* @Description:com.example.test
* @version:1.0
*/
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import java.io.IOException;
/**
* @ClassName LoginFilter
* @Description //TODO
* @Author zhaoyu
* @Date 2024/1/2
*/
@WebFilter(filterName = "Filter0_loginFilter",
urlPatterns = "/*",
initParams = @WebInitParam(name="charset",value="UTF-8"))
public class LoginFilter implements Filter {
String charset = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
charset = filterConfig.getInitParameter("charset");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//拦截所有请求,将其字符集格式设置为UTF-8
//可以把UTF-8写到FilterConfig的初始参数中,不要在这里写死
request.setCharacterEncoding(charset);
System.out.println(charset); // UTF-8
//放行
chain.doFilter(request,response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
通过这种方式解决了中文乱码的问题:
8.过滤器控制登陆
需求:通过过滤器控制,只有登陆过之后可以反复进入welcome.jsp欢迎页,如果没有登陆,提示用户进入登陆页进行登录操作。(类似cookie-session小例子)
welcome.jsp:
<%--
Created by IntelliJ IDEA.
User: zhaoyu
Date: 2024/1/2
Time: 16:45
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<b>欢迎${sessionScope.user.username}登陆!!!</b>
</body>
</html>
User实体类:
package com.example.test2;/**
* @Author:zhoayu
* @Date:2024/1/2 16:47
* @Description:com.example.test2
* @version:1.0
*/
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @ClassName User
* @Description //TODO
* @Author zhaoyu
* @Date 2024/1/2
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private String username;
private String password;
}
login.jsp:
<%--
Created by IntelliJ IDEA.
User: zhaoyu
Date: 2024/1/2
Time: 16:16
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
please login ... ... <br/>
<form action="${pageContext.request.contextPath}/loginController.do" method="post">
用户名:<input type="text" name="user"> <br/>
密码:<input type="text" name="pwd"> <br/>
<input type="submit" value="提交">
</form>
</body>
</html>
因为我们还没有定义拦截器,所以现在是直接能够访问到welcome.jsp的,同时因为session域中没有User对象,所以此时${sessionScope.user.username}也是获取不到值的
我们现在要做的是:除了login.jsp外,只要没有登陆过 不允许用户访问任何资源。
拦截器Filter1_LoginFilter.java:
package com.example.test2;/**
* @Author:zhoayu
* @Date:2024/1/2 16:51
* @Description:com.example.test2
* @version:1.0
*/
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @ClassName Filter1_LoginFilter
* @Description //TODO
* @Author zhaoyu
* @Date 2024/1/2
*/
@WebFilter(filterName = "Filter1_loginFilter",urlPatterns = "/*") // 拦截访问任何资源的请求
public class Filter1_LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//不管登陆没登陆都要进行放行的:请求login.jsp的请求
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String uri = httpServletRequest.getRequestURI(); // /application_contest/资源路径
System.out.println(uri); // /demo5_war_exploded/welcome.jsp
//请求登陆页面or登陆servlet(提交用户密码)的请求直接放行
//这里放行static目录下的资源是为了防止login.jsp上有static目录下资源的引用,让这些静态资源能够正常展示
if(uri.endsWith("login.jsp") || uri.endsWith("loginController.do") || uri.contains("/static/")){
chain.doFilter(httpServletRequest,response);
return;
}
//需要登陆之后才能访问的资源,如果用户之前没登陆过,重定向到login.jsp上
HttpSession session = httpServletRequest.getSession();
Object user = session.getAttribute("user");
if(user!=null){
//如果登陆过,放行
chain.doFilter(httpServletRequest,response);
}else{
//没有登陆过,跳转至登陆页
//这里注意两点:
// 1.session域的范围 不能跨用户请求 所以可以这样判断该用户是否登陆过。同时注意session的生命周期(cookie或session一方失效则失效,无法通过sessionid获得对应的session了)
// 2.过滤器这里的参数req和resp其实是ServletRequest和ServletResponse的子类:HttpServletRequest和HttpServletResponse
// 所以这里能这样强转以获得更多的api
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
httpServletResponse.sendRedirect("/login.jsp");
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
loginContoller.java:
package com.example.test;/**
* @Author:zhoayu
* @Date:2024/1/2 16:18
* @Description:com.example.test
* @version:1.0
*/
import com.example.test2.User;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName ServletLogin
* @Description //TODO
* @Author zhaoyu
* @Date 2024/1/2
*/
@WebServlet("/loginController.do")
public class loginController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户名&密码
String name = req.getParameter("user");
String pwd = req.getParameter("pwd");
System.out.println(name);
System.out.println(pwd);
//连接数据库校验账号密码 这里省略
//登陆成功,将用户信息放入session域
User user = new User(name,pwd);
req.getSession().setAttribute("user",user);
//请求转发跳转到欢迎页
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/welcome.jsp");
requestDispatcher.forward(req,resp);
}
}