实现Filter接口的Java类就被称作为过滤器
public class FilterDemo1 implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//执行这一句,说明放行(让下一个过滤器执行,如果没有过滤器了,就执行执行目标资源)
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
过滤器和Servlet是一样的,需要部署到Web服务器上的。
<filter>
用于注册过滤器
<filter>
<filter-name>FilterDemo1</filter-name>
<filter-class>FilterDemo1</filter-class>
<init-param>
<param-name>word_file</param-name>
<param-value>/WEB-INF/word.txt</param-value>
</init-param>
</filter>
<filter-name>
用于为过滤器指定一个名字,该元素的内容不能为空。
<filter-class>
元素用于指定过滤器的完整的限定类名。
<init-param>
元素用于为过滤器指定初始化参数,它的子元素指定参数的名字,<param-value>
指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数。
<filter-mapping>
元素用于设置一个Filter 所负责拦截的资源。
一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径
<filter-mapping>
<filter-name>FilterDemo1</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-name>
子元素用于设置filter的注册名称。该值必须是在元素中声明过的过滤器的名字
<url-pattern>
设置 filter 所拦截的请求路径(过滤器关联的URL样式)
<servlet-name>
指定过滤器所拦截的Servlet名称。
<dispatcher>
指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher>
子元素用来指定 Filter 对资源的多种调用方式进行拦截。
子元素可以设置的值及其意义:
REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
@WebFilter(filterName = "FilterDemo1",urlPatterns = "/*")
上面的配置是“/*”,所有的Web资源都需要途径过滤器
如果想要部分的Web资源进行过滤器过滤则需要指定Web资源的名称即可!
上面已经说过了,过滤器的doFilter()方法是极其重要的,FilterChain接口是代表着所有的Filter,FilterChain中的doFilter()方法决定着是否放行下一个过滤器执行(如果没有过滤器了,就执行目标资源)。
首先在过滤器的doFilter()中输出一句话,并且调用chain对象的doFilter()方法
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("我是过滤器1");
//执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
chain.doFilter(req, resp);
}
我们来访问一下test.jsp页面:
我们发现test.jsp(我们的目标资源)成功访问到了,并且在服务器上也打印了字符串!
我们来试试把chain.doFilter(req, resp);
这段代码注释了看看!
test.jsp页面并没有任何的输出(也就是说,并没有访问到jsp页面)。
直接看下面的代码。我们已经知道了”准备放行“会被打印在控制台上和test.jsp页面也能被访问得到,但“放行完成“会不会打印在控制台上呢?
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("准备放行");
//执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
chain.doFilter(req, resp);
System.out.println("放行完成");
}
答案也非常简单,肯定会打印在控制台上的。我们来看看:
注意,它的完整流程顺序是这样的:客户端发送http请求到Web服务器上,Web服务器执行过滤器,执行到”准备放行“时,就把字符串输出到控制台上,接着执行doFilter()方法,Web服务器发现没有过滤器了,就执行目标资源(也就是test.jsp)。目标资源执行完后,回到过滤器上,继续执行代码,然后输出”放行完成“
我们再多加一个过滤器,看看执行顺序。
过滤器1
System.out.println("过滤器1开始执行");
//执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源)
chain.doFilter(req, resp);
System.out.println("过滤器1开始完毕");
过滤器2
System.out.println("过滤器2开始执行");
chain.doFilter(req, resp);
System.out.println("过滤器2开始完毕");
Servlet
System.out.println("我是Servlet1");
当我们访问Servlet1的时候,看看控制台会出现什么:
执行顺序是这样的:先执行FilterDemo1,放行,执行FilterDemo2,放行,执行Servlet1,Servlet1执行完回到FilterDemo2上,FilterDemo2执行完毕后,回到FilterDemo1上
注意:过滤器之间的执行顺序看在web.xml文件中mapping的先后顺序的,如果放在前面就先执行,放在后面就后执行!如果是通过注解的方式配置,就比较urlPatterns的字符串优先级
filter的三种典型应用:
1、可以在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
2、在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
3、在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//让Web资源不缓存,很简单,设置http中response的请求头即可了!
//我们使用的是http协议,ServletResponse并没有能够设置请求头的方法,所以要强转成HttpServletRequest
//一般我们写Filter都会把他俩强转成Http类型的
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setDateHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
//放行目标资源的response已经设置成不缓存的了
chain.doFilter(request, response);
}
实体:
private String username ;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
//各种setter和getter
集合模拟数据库
public class UserDB {
private static List<User> users = new ArrayList<>();
static {
users.add(new User("aaa", "123"));
users.add(new User("bbb", "123"));
users.add(new User("ccc", "123"));
}
public static List<User> getUsers() {
return users;
}
public static void setUsers(List<User> users) {
UserDB.users = users;
}
}
开发dao
public User find(String username, String password) {
List<User> userList = UserDB.getUsers();
//遍历List集合,看看有没有对应的username和password
for (User user : userList) {
if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
return user;
}
}
return null;
}
<form action="${pageContext.request.contextPath}/LoginServlet">
用户名<input type="text" name="username">
<br>
密码<input type="password" name="password">
<br>
<input type="radio" name="time" value="10">10分钟
<input type="radio" name="time" value="30">30分钟
<input type="radio" name="time" value="60">1小时
<br>
<input type="submit" value="登陆">
</form>
//得到客户端发送过来的数据
String username = request.getParameter("username");
String password = request.getParameter("password");
UserDao userDao = new UserDao();
User user = userDao.find(username, password);
if (user == null) {
request.setAttribute("message", "用户名或密码是错的!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
//如果不是为空,那么在session中保存一个属性
request.getSession().setAttribute("user", user);
request.setAttribute("message", "恭喜你,已经登陆了!");
//如果想要用户关闭了浏览器,还能登陆,就必须要用到Cookie技术了
Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + user.getPassword());
//设置Cookie的最大声明周期为用户指定的
cookie.setMaxAge(Integer.parseInt(request.getParameter("time")) * 60);
//把Cookie返回给浏览器
response.addCookie(cookie);
//跳转到提示页面
request.getRequestDispatcher("/message.jsp").forward(request, response);
HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request = (HttpServletRequest) req;
//如果用户没有关闭浏览器,就不需要Cookie做拼接登陆了
if (request.getSession().getAttribute("user") != null) {
chain.doFilter(request, response);
return;
}
//用户关闭了浏览器,session的值就获取不到了。所以要通过Cookie来自动登陆
Cookie[] cookies = request.getCookies();
String value = null;
for (int i = 0; cookies != null && i < cookies.length; i++) {
if (cookies[i].getName().equals("autoLogin")) {
value = cookies[i].getValue();
}
}
//得到Cookie的用户名和密码
if (value != null) {
String username = value.split("\\.")[0];
String password = value.split("\\.")[1];
UserDao userDao = new UserDao();
User user = userDao.find(username, password);
if (user != null) {
request.getSession().setAttribute("user", user);
}
}
chain.doFilter(request, response);
效果:
我们直接把用户名和密码都放在了Cookie中,这是明文的。懂点编程的人就会知道你的账号了。
于是乎,我们要对密码进行加密!
Cookie cookie = new Cookie("autoLogin", user.getUsername() + "." + md5.md5(user.getPassword()));
在过滤器中,加密后的密码就不是数据库中的密码的。所以,我们得在Dao添加一个功能【根据用户名,找到用户】
public User find(String username) {
List<User> userList = UserDB.getUsers();
//遍历List集合,看看有没有对应的username和password
for (User user : userList) {
if (user.getUsername().equals(username)) {
return user;
}
}
return null;
}
在过滤器中,比较Cookie带过来的md5密码和在数据库中获得的密码(也经过md5)是否相同
//得到Cookie的用户名和密码
if (value != null) {
String username = value.split("\\.")[0];
String password = value.split("\\.")[1];
//在Cookie拿到的密码是md5加密过的,不能直接与数据库中的密码比较
UserDao userDao = new UserDao();
User user = userDao.find(username);
//通过用户名获得用户信息,得到用户的密码,用户的密码也md5一把
String dbPassword = md5.md5(user.getPassword());
//如果两个密码匹配了,就是正确的密码了
if (password.equals(dbPassword)) {
request.getSession().setAttribute("user", user);
}
}
全部评论