过滤器是Servlet的高级特性之一,也别把它想得那么高深,只不过是实现Filter接口的Java类罢了!java
首先,咱们来看看过滤器究竟Web容器的哪处:web
从上面的图咱们能够发现,当浏览器发送请求给服务器的时候,先执行过滤器,而后才访问Web的资源。服务器响应Response,从Web资源抵达浏览器以前,也会途径过滤器。数据库
咱们很容易发现,过滤器能够比喻成一张滤网。咱们想一想现实中的滤网能够作什么:在泡茶的时候,过滤掉茶叶。那滤网是怎么过滤茶叶的呢?规定大小的网孔,只要网孔比茶叶小,就能够实现过滤了!编程
引伸在Web容器中,过滤器能够作:过滤一些敏感的字符串【规定不能出现敏感字符串】、避免中文乱码【规定Web资源都使用UTF-8编码】、权限验证【规定只有带Session或Cookie的浏览器,才能访问web资源】等等等,过滤器的做用很是大,只要发挥想象就能够有意想不到的效果浏览器
也就是说:当须要限制用户访问某些资源时、在处理请求时提早处理某些资源、服务器响应的内容对其进行处理再返回、咱们就是用过滤器来完成的!缓存
直接举例子来讲明吧:服务器
若是我没有用到过滤器:浏览器经过http请求发送数据给Servlet,若是存在中文,就必须指定编码,不然就会乱码!markdown
jsp页面提交中文数据给Servlet处理cookie
<form action="${pageContext.request.contextPath}/Demo1" method="post"> <input type="text" name="username"> <input type="submit" value="提交"> </form>
Servlet中如何解决中文乱码问题,个人其余博文中有:http://blog.csdn.net/hon_3y/article/details/54632004session
也就是说:若是我每次接受客户端带过来的中文数据,在Serlvet中都要设定编码。这样代码的重复率过高了!!!!
有过滤器的状况就不同了:只要我在过滤器中指定了编码,可使全站的Web资源都是使用该编码,而且重用性是很是理想的!
只要Java类实现了Filter接口就能够称为过滤器!Filter接口的方法也十分简单:
其中init()和destory()方法就不用多说了,他俩跟Servlet是同样的。只有在Web服务器加载和销毁的时候被执行,只会被执行一次!
值得注意的是doFilter()方法,它有三个参数(ServletRequest,ServletResponse,FilterChain),从前两个参数咱们能够发现:过滤器能够完成任何协议的过滤操做!
那FilterChain是什么东西呢?咱们看看:
FilterChain是一个接口,里面又定义了doFilter()方法。这到底是怎么回事啊??????
咱们能够这样理解:过滤器不仅仅只有一个,那么咱们怎么管理这些过滤器呢?在Java中就使用了链式结构。把全部的过滤器都放在FilterChain里边,若是符合条件,就执行下一个过滤器(若是没有过滤器了,就执行目标资源)。
上面的话好像有点拗口,咱们能够想象生活的例子:如今我想在茶杯上能过滤出石头和茶叶出来。石头在一层,茶叶在一层。因此茶杯的过滤装置应该有两层滤网。这个过滤装置就是FilterChain,过滤石头的滤网和过滤茶叶的滤网就是Filter。在石头滤网中,茶叶是属于下一层的,就把茶叶放行,让茶叶的滤网过滤茶叶。过滤完茶叶了,剩下的就是茶(茶就能够比喻成咱们的目标资源)
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()方法决定着是否放行下一个过滤器执行(若是没有过滤器了,就执行目标资源)。
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println("我是过滤器1"); //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源) chain.doFilter(req, resp); }
咱们发现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)。目标资源执行完后,回到过滤器上,继续执行代码,而后输出”放行完成“
咱们再多加一个过滤器,看看执行顺序。
System.out.println("过滤器1开始执行"); //执行这一句,说明放行(让下一个过滤器执行,或者执行目标资源) chain.doFilter(req, resp); System.out.println("过滤器1开始完毕");
System.out.println("过滤器2开始执行"); chain.doFilter(req, resp); System.out.println("过滤器2开始完毕");
System.out.println("我是Servlet1");
当咱们访问Servlet1的时候,看看控制台会出现什么:
执行顺序是这样的:先执行FilterDemo1,放行,执行FilterDemo2,放行,执行Servlet1,Servlet1执行完回到FilterDemo2上,FilterDemo2执行完毕后,回到FilterDemo1上
注意:过滤器之间的执行顺序看在web.xml文件中mapping的前后顺序的,若是放在前面就先执行,放在后面就后执行!若是是经过注解的方式配置,就比较urlPatterns的字符串大小
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; } }
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()));
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的用户名和密码 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); } }