1案例1-自动登录

1.1 需求及流程介绍

当用户直接访问登录后的页面(例如:购物车页面,订单页面等)直接显示出上一次登录的用户对象,这个功能就是自动登录的功能。如果用户在上一次登录的时候,没有勾选自动登录,那么用户直接访问登录后的页面时,不显示用户是谁,加购物车,提交订单等都需要让用户先登录。
(简单说就是跟jd一样。今天只做前一句话,后一句话是权限控制,是后面的内容。)

用户自动登录的流程介绍.png

1.2 技术分析

1:可以使用会话技术让服务器知道浏览器发送的多次请求。
2:需要使用过滤器技术,对用户发送的每一次请求都先进行过滤,过滤后,才让用户执行真正的资源。

1.3 过滤器filter概述

过滤器就是运行在服务器最前端的java小程序。
servlet,filter,listener合称为服务器的三大组件,都是被tomcat服务器创建,被服务调用。
servlet与filter的区别:
0:servlet能做的filter一定能做,filter能做的servlet不一定能做。
1:servlet通常用于专一的请求处理,而filter通常用于通用的请求处理;
2:filter一定优先于servlet执行;
进servlet之前要经过filter,从servlet出去的时候也要经过filter。

1.4 过滤器filter的应用场景

1:网站所有请求的中文参数乱码和响应乱码处理。
2:网站访问权限控制。
3:自动登录。
4:敏感字过滤。

1.5 过滤器filter的编写步骤

过滤器的英文名:filter,是javaEE定义的一个接口。
程序员只需实现filter接口,编写相应的方法即可。
image.png
image.png
image.png
关于filter接口中的doFilter方法的3个参数:
ServletRequest类型的参数:代表的是请求对象。
ServletResponse类型的参数:代表的是响应对象。
FilterChain类型的参数:代表的是用于放行的对象。(如果不放行,那么浏览器请求的资源将得不到任何响应)

1.6 自动登录案例的实现

1:案例环境搭建。(数据库创建,静态页面复制,jar包选择,工具类复制)
2:编写3层包结构。
3:将login.html文件修改成login.jsp文件。
4:编写LoginServlet,完成用户完整的登录流程,当用户登陆成功的时候需要将用户对象保存到session中。
5:编写service和dao。
6:修改loginServlet,当用户登录成功的时候,判断用户是否希望帮用户完成自动登录的动作。
如果希望,则创建一个cookie对象,并保存用户名和密码为7天
如果不希望,立刻删除原来的cookie。
7:编写一个过滤器,在过滤器中获取cookie,并使用cookie中的用户名和密码查询数据库,如果能查到则帮用户把用户对象保存到session中。

注意:
所谓的登录就是session中有用户对象。
部分参考代码:
说明:
页面里
用户名标签处:name=”username”
密码标签处:name=”password”
是否自动登录勾选选项处标签:name=”autoLogin” value=”ok”

过滤器AutoLoginFilter.java:

  1. package com.itheima.anli00_filter;
  2. import java.io.IOException;
  3. import javax.servlet.Filter;
  4. import javax.servlet.FilterChain;
  5. import javax.servlet.FilterConfig;
  6. import javax.servlet.ServletException;
  7. import javax.servlet.ServletRequest;
  8. import javax.servlet.ServletResponse;
  9. import javax.servlet.http.Cookie;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpSession;
  12. import com.itheima.anli02_service.UserService;
  13. import com.itheima.anli04_domain.User;
  14. /**
  15. * 完成自动登录的filter
  16. */
  17. public class AutoLoginFilter implements Filter {
  18. public void destroy() {
  19. }
  20. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  21. /*
  22. * 思路:
  23. *
  24. * 1:判断session中是不是已经拥有了user对象,如果有,则证明用户已经登录了,可以直接放行,否则继续判断下一步;
  25. * 2:获取浏览器携带的所有cookie,并查找出名字为auto的cookie;如果没有cookie,直接放行,否则继续下一步;
  26. * 3:获取cookie中的用户名和密码查询数据库;如果查询成功,则保存到seesion中,如果没查到,直接放行;
  27. *
  28. */
  29. HttpServletRequest r = (HttpServletRequest)request;
  30. HttpSession se = r.getSession();
  31. Object user = se.getAttribute("user");
  32. if(user==null){
  33. //需要继续下一步
  34. Cookie[] cs = r.getCookies();
  35. if(cs!=null){
  36. //说明有一部分cookie
  37. Cookie c=null;//准备使用c保存我们想要的cookie对象
  38. for (Cookie cookie : cs) {
  39. if(cookie.getName().equals("auto")){
  40. c=cookie;
  41. }
  42. }
  43. //判断有没有找到cookie
  44. if(c!=null){
  45. String value = c.getValue();
  46. String[] split = value.split("<itheima>");
  47. UserService us = new UserService();
  48. User u2 = us.findUserByUsernameAndPassword(split[0],split[1]);
  49. if(u2!=null){
  50. //帮这个用户保存session
  51. se.setAttribute("user",u2);
  52. }
  53. }
  54. }
  55. }
  56. //放行
  57. chain.doFilter(request, response);
  58. }
  59. public void init(FilterConfig fConfig) throws ServletException {
  60. }
  61. }

LoginServlet.java

package com.itheima.anli01_web;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.itheima.anli02_service.UserService;
import com.itheima.anli04_domain.User;

/**
 * 用户登录的servlet
 */
public class LoginServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //System.out.println(request);
        //1:参  调   存   转
        String un = request.getParameter("username");
        String ps = request.getParameter("password");
        String au = request.getParameter("autoLogin");
        //2:调用业务层
        UserService  us = new UserService();
        User u = us.findUserByUsernameAndPassword(un,ps);
        //3:存,一定要将用户保存到session对象中,
        if(u==null){
            //说明用户名或密码错误,回到login.jsp,让用户重新登录
            request.setAttribute("msg","用户名或密码错误");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
            return;//结束方法
        }
        //4:说明查到了用户,保存到session中
        request.getSession().setAttribute("user", u);
        //判断用户是否希望我们完成自动登录功能 
        Cookie c = new Cookie("auto",un+"<itheima>"+ps);
        c.setPath("/");
        if("ok".equals(au)){
            //说明用户希望自动登录,将cookie设置为7天
            c.setMaxAge(60*60*24*7);
        }else{
            //说明用户不希望自动登录,将cookie设置为0秒
            c.setMaxAge(0);
        }
        response.addCookie(c);
        //转发的购物网站,可以让用户去购物了
        request.getRequestDispatcher("/index.jsp").forward(request, response);
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

2 filter总结

2.1 Filter的生命周期

从filter创建一直到filter死亡,这个过程就是filter的生命周期。
Filter的生命周期方法有3个:
init() 初始化
doFilter() 执行过滤
destroy () 死亡

doFilter()方法:只要能匹配路径成功一定会执行,即使有多个filter都匹配了路径也会逐个执行。执行顺序仅仅与web.xml中filter-mapping的配置顺序有关,从上至下,逐个执行。
init()方法:随着tomcat启动就会创建filter对象。
destroy()方法:随着tomcat服务器正常停止而死亡。

2.2 多个filter顺序问题:

先后顺序仅仅与在web.xml的配置顺序有关。
filter.png

2.4 FilterConfig对象(了解)

在web.xml中的filter标签中配置init-param属性和属性值时,可以使用FilterConfig对象,获取这些初始化配置参数。
例如:

<filter>
    <display-name>AutoLoginFilter</display-name>
    <filter-name>AutoLoginFilter</filter-name>
    <filter-class>com.itheima.anli00_filter.AutoLoginFilter</filter-class>
    <init-param>
        <param-name>aaa</param-name>
        <param-value>111</param-value>
    </init-param>
</filter>

javaee的api中有。
常用API:
getInitParameter(属性名);

2.5 关于filter中的url-pattern的配置

在filter中有3种配置:
1:完全匹配 /xxx
2:目录匹配(通配符匹配) / (用的最多)
3:后缀名匹配
.xxx (偶尔使用)
注意:
三种匹配方式不分优先级,只要与路径匹配成功,都会执行,执行顺序仅与web.xml中的配置顺序有关。

2.6 关于filter的拦截方式问题

在filter-mapping中,也可以不使用url-pattern。
使用servlet-name标签替代(了解,不常用)
例如:
image.png
关于过滤的方式:
image.png

3 案例3-中文编码过滤器

3.1 需求

当用户访问工程中的任意一个servlet的时候,在servlet中无需再处理中文参数,也无需处理中文响应,都能保证参数和响应数据的正常。(不能乱码)

3.2 技术分析

在servlet中获取参数的方法有3个,分别是:getParameter getParameterValues getParameterMap
只要对这3个方法进行增强,即可实现中文参数乱码问题的结果。
响应的时候,只需在调用getWriter或getOutputStream之前,设置响应的Content-Type头信息即可。
使用过滤器就能完成这个事情。

3.3 增强一个类的方法的技术

方式1:继承,重写方法。
缺点:
子类与父类完全绑在了一起,耦合度太高,代码不灵活。(此案例中和tomcat耦合度太高,换个服务器不用tomcat了直接就不好使了)
好处:
简单,好写,快捷。
方式2:装饰者模式
(ps:关于各种各样的设计模式,不管是什么设计模式,都是在抽取工具类的时候才会用)
装饰者模式是指A类与B类是兄弟类,将来面向接口开发的时候,使用A类中的方法,替代B类中的方法,那么此时就可以称为A装饰了B。A就是装饰者,B就是被装饰者。

好处:
代码灵活度相对高,只要不更换接口,子类可以替换。
使用前提:
在装饰者中,必须能使用被装饰者的对象,且需要与被装饰者实现相同的接口。
代码步骤:
1:编写一个类,实现与被装饰者相同的接口。(成为兄弟)
2:在类中定义一个接口类型的成员变量用于保存被装饰者对象。
3:编写增强后的方法即可。(增强方法)
本案例中由于直接使用HttpServletRequest接口,需要重写的方法太多,因此选择继承HttpServletRequestWrapper类来与HttpServletRequest接口产生关系。

3.4 过滤器代码实现:

实际装饰的是request对象。
无标题.png

package com.itheima.anli00_filter;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

/**
 * 我们自己采用装饰者模式,对reqeust对象进行增强的工具类
 */
public class MyEncodingFilter implements Filter {
    public void destroy() {
    }
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1:将request和response转成子接口类型
        HttpServletRequest r = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        //2:获取请求方式,如果是get请求,则使用装饰者模式解决乱码,如果是post请求,则直接面向原始的request对象,调用setCharacterEncoding方法解决乱码
        String method = r.getMethod();
        //3:先解决响应乱码问题
        res.setContentType("text/html;charset=utf-8");
        if("GET".equalsIgnoreCase(method)){
            //说明需要使用装饰者
            MyRequest z = new MyRequest(r);
            //放行,传递装饰后的对象
            chain.doFilter(z, res);
        }else{
            //可以直接解决
            r.setCharacterEncoding("utf-8");
            chain.doFilter(r, res);
        }
    }
    public void init(FilterConfig fConfig) throws ServletException {
    }
}

class MyRequest extends HttpServletRequestWrapper{
    //定义一个接口类型的成员变量,用于保存被装饰的对象
    private HttpServletRequest request ;
    //为了解决多次调用方法的时候,第一次不乱,之后反而乱码的问题,我们需要定义一个flag,让转换的过程仅执行一次
    private boolean flag = true;//默认可以进行转换
    public MyRequest(HttpServletRequest request) {
        super(request);
        this.request = request;
    }
    //增强方法;
    //1:增强getParameterMap
    public Map<String, String[]> getParameterMap() {
        //1:先使用request获取原始的浏览器传递过来的参数
        Map<String, String[]> map = request.getParameterMap();
        //2:此时如果map中有中文参数,一定是乱码的!!!  仅考虑get请求,post请求直接面向原始对象解决即可
        if(flag){
            Set<String> set = map.keySet();
            for (String key : set) {
                String[] vs = map.get(key);
                //对数组中的参数进行处理,处理后需要将参数重新保存回数组
                for(int i=0;i<vs.length;i++){
                    //只要对数组的每一个元素进行了修改,那么map中的数组中的元素自然就跟着变化了(已经解决中文乱码了)
                    try {
                        vs[i]=new String(vs[i].getBytes("iso8859-1"),"utf-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        throw new RuntimeException();
                    }
                }
            }
            //修改flag为false
            flag = false;
        }
        //返回已经解决了乱码的map
        return map;
    }
    //重写getParameterValues方法,这个方法中的数据,都可以直接从map中获取,因为map已经解决了乱码
    @Override
    public String[] getParameterValues(String name) {
        return getParameterMap().get(name);
    }
    @Override
    public String getParameter(String name) {
        String[] vs = getParameterValues(name);
        if(vs==null){
            return "";
        }
        return vs[0];
    }
}