重定向与转发

一、项目升级

展示数据

用户登录成功后,可以展示出admin的表中所有信息

//登录成功后,进入展示数据页面
Admin login = adminService.login(username, password);
if(login==null){
    resp.getWriter().write("登录失败!");
}else{
    resp.getWriter().write("<h1>登录成功!</h1>");
    resp.getWriter().write("<a href='showAll'>展示数据</a>");
}

在展示的控制层调用业务层,并展示数据

@WebServlet("/showAll")
public class ShowController extends HttpServlet {
    private AdminService adminService = new AdminServiceImpl();
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8"); //响应乱码

        //调用业务层,完成展示数据功能
        List<Admin> list = adminService.showAdmins();
        PrintWriter printWriter = response.getWriter();
        if(list!=null){
            printWriter.println("<html>");
            printWriter.println("<head>");
            printWriter.println("<meta charset='UTF-8'>");
            printWriter.println("<title>显示所有</title>");
            printWriter.println("</head>");
            printWriter.println("<body>");
            printWriter.println("<table border='1'>");
            printWriter.println("   <tr>");
            printWriter.println("       <td>username</td>");
            printWriter.println("       <td>password</td>");
            printWriter.println("       <td>phone</td>");
            printWriter.println("       <td>address</td>");
            printWriter.println("   </tr>");
            for(Admin admin : list){
                printWriter.println("   <tr>");
                printWriter.println("       <td>"+admin.getUsername()+"</td>");
                printWriter.println("       <td>"+admin.getPassword()+"</td>");
                printWriter.println("       <td>"+admin.getPhone()+"</td>");
                printWriter.println("       <td>"+admin.getAddress()+"</td>");
                printWriter.println("   </tr>");
            }
            printWriter.println("</table>");
            printWriter.println("</body>");
            printWriter.println("</html>");
        }else{
            printWriter.println("数据为空!!");
        }
    }
}

展示的业务层处理

@Override
public List<Admin> showAdmins() { //展示所有数据
    DBUtils.begin();  //开启事务
    try {
        List<Admin> list = adminDao.selectAdmins();
        DBUtils.commit();  //提交事务
        return list;  //返回集合
    }catch (Exception e){
        DBUtils.rollbock();  //回滚事务
    }
    return null;
}

分层处理思路

在展示数据的控制层中既要完成接收参数并调用业务层处理;而且还需要展示内容到前端;不符合单一职责原则。且不方便进行代码的分离

单一职责原则:往往一个方法只完成一种功能即可

二、跳转方式

前面分析了展示页面数据需要分离,分离则需要跳转,有两种跳转方式:1.重定向 2.转发

问题:跳转时需要携带数据进行跳转,使用哪种方式更合适呢?

转发

通过案例分析转发进行调整的特点: 从AServlet使用转发调整到BServlet

转发特点:

服务器内部的跳转;发了一次请求;在目标Servlet中可取到request存的值;

在url路径中不会改变请求路径

重定向

重定向特点:

客户端的重新跳转;发了二次请求;在目标重定向的Servlet中可取不到request存的值;

在url路径中会改变请求路径

测试案例

@WebServlet("/a")
public class AServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("进入了AServlet");
        //通过转发跳转到BServlet   服务器内部的跳转
        //req.getRequestDispatcher("b").forward(req,resp);

        //通过重定向跳转到BServlet  客户端的重新发请求跳转
        //resp.sendRedirect("b");

        //结论:跳转可以使用这两种,基本上没太大区别;关键在于传数据
        req.setAttribute("key","my data"); //req存储数据 参数1:key 参数2:value
        //req.getRequestDispatcher("b").forward(req,resp);  //转发
        resp.sendRedirect("b");
    }
}
@WebServlet("/b")
public class BServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("已经进入到BServlet");
        System.out.println(req.getAttribute("key")); //只有转发才能获取到数据
    }
}

项目完善

在展示数据的控制层进行调用业务层获取数据;并request存值+转发跳转到展示页面的控制层

结论:如果需要存值,则使用转发跳转;这样,跳转到目标Servlet中也能取到数据

//调用业务层,完成展示数据功能
List<Admin> list = adminService.showAdmins();
if(list!=null){
    request.setAttribute("admins",list);  //request存值+转发跳转
    request.getRequestDispatcher("showJSP").forward(request,response);
}else{
    response.getWriter().write("数据未找到");
}

在展示页面的控制层进行获取存的值,并显示到前端

@WebServlet("/showJSP")
public class ShowJSP extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Admin> list = (List<Admin>) request.getAttribute("admins");
        PrintWriter printWriter = response.getWriter();
        if(list!=null){
            printWriter.println("<html>");
            //...
        }
    }
}

三、生命周期

Servlet的生命周期共分为四个阶段:构造、初始化、处理servcie、销毁

测试案例

@WebServlet("/life")
//执行过程:1.构造  2.初始化  3.service  4.销毁(重启时会先触发销毁)
//多次访问时:构造和初始化只执行一次  service方法触发多次
//单例设计--单实例多处理 这种设计可以节省内存资源
public class LifeServlet extends HttpServlet {
    public LifeServlet(){
        System.out.println("构造方法...");
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("初始化方法...");
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("service处理...");
    }
    @Override
    public void destroy() {
        System.out.println("销毁方法...");
    }
}

执行流程

Servlet的执行流程:

从浏览器输入http://localhost:8080/life访问映射路径,在web.xml(注解)中进行了映射路径的解析,解析成真实的LifeServlet的访问,并调用方法:

  1. 调用servlet方法 2. 调用init方法 3. 调用service方法

访问service方法完成后,则响应回客户端,展示服务器的资源;如果重启了,则会调用destroy方法

四、Servlet特性

线程安全

多个用户访问Servlet时,客户端可以并发访问Servlet,那么Servlet中的共享数据,需要考虑安全性问题

处理线程安全

处理线程安全的方式有以下几种:

1.将Servlet变为多例的Servlet(不推荐-性能特别低)

2.有共享数据(成员属性)则加锁处理

3.尽可能的使用局部变量(天生线程安全)

@WebServlet("/safe")
public class SafeServlet extends HttpServlet {
    //private String res = "返回失败";  //共享数据
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //synchronized (this) {
            String res = "返回成功";  //局部变量,天生线程安全
            resp.getWriter().write(res);
        //}
    }
}

问题:分层项目中,控制层需要实例化业务层对象,那么业务层对象是否为共享数据,需要进行加锁处理吗?

业务层对象不用考虑线程安全,因为业务层对象没有篡改数据

五、状态管理

概述

前面的HTTP协议是无状态的,也就是无记忆能力;在项目中会产生一个问题,访问后会产生处理数据,但再次访问时,可能还需要重新再生成一次。

状态管理方式

有两种状态管理方式:(存数据)

  1. 服务器技术–Session会话管理

  2. 客户端技术-Cookie会话管理

六、Cookie会话

概述:以键值对方式存储到客户端文件的方式

好处:减少服务器压力;

弊端:cookie数据容易被删除,被禁用;不安全;不同浏览器之间是隔离的;且不能跨域访问

场景:换肤功能,浏览记录,购物车等

应用

cookie主要有三个功能:设置cookie,获取cookie,清除cookie

设置cookie:

@WebServlet("/setcook")
public class SetCookie extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie cookie = new Cookie("name","zsf");
        //cookie.setPath("/webs"); //设置只有哪个路径能访问cookie,一般默认,当前路径可以访问即可
        cookie.setMaxAge(60*60*24);  //设置有效期,单位为秒 >0 有效期的值 =0 清除cookie
        //设置cookie:从服务器设置到客户端--resp
        resp.addCookie(cookie);
        resp.getWriter().write("cookie success");
    }
}

获取cookie:

@WebServlet("/getcook")
public class GetCookie extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取cookie:客户端数据到达服务器--req
        Cookie[] cookies = req.getCookies();
        if(cookies!=null){
            for(Cookie c:cookies){
                System.out.println(c.getName()+"--"+c.getValue());
            }
        }
    }
}

中文Cookie处理

cookie存储的值为中文时的处理方式:

@WebServlet("/zhcook")
public class ZhCookie extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = "张三丰";  //根据用户名进行编码处理 URLEncoder.encode
        Cookie cookie = new Cookie("name",URLEncoder.encode(name,"utf-8"));
        cookie.setMaxAge(60*60*24);
        resp.addCookie(cookie);
    }
}
//获取cookie:客户端数据到达服务器--req
Cookie[] cookies = req.getCookies();
if(cookies!=null){
    for(Cookie c:cookies){
        if("name".equals(c.getName())){ //获取用户名后进行解码
            System.out.println(URLDecoder.decode(c.getValue(),"utf-8"));
        }
    }
}

七、Session会话

Session是服务器的技术,数据存储到服务器中的;但是在浏览器中会保留sessionId,用于匹配上一次访问的session。

Session原理

访问Servlet时可以创建当前用户的session;且对应的在浏览器中产生sessionId;当下一次再次访问当前Servlet时,会先查看浏览器中是否有sessionId,如果有,则不用重新在服务器创建Session,直接使用即可;如果浏览器中没有sessionId(第一次或重新打开的浏览器则没有sessionId),则需要在服务器中创建Session对象,且浏览器中重新产生sessionId。

特点

一个用户的多次访问属于同一个会话(同一个session对象);不同用户的访问则属于不同会话(session不同)

测试案例:

设置Session,并存储值

@WebServlet("/setsess")
public class SetSession extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();  //第一次访问则创建;不是第一次则获取对象
        System.out.println("sessionId: "+session.getId());
        //在session域中存值
        session.setAttribute("username","凤姐");
    }
}

获取Session,取到存储的值

@WebServlet("/getsess")
public class GetSession extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        System.out.println("get sessionId-->"+session.getId());
        //结论:如果设置session后关闭了浏览器,则此处取不到值
        System.out.println("取值:"+session.getAttribute("username"));
    }
}

结论:

  1. 不同浏览器的访问session会创建不同的session对象
  2. 同一个浏览器的多次访问是同一个session,所以存的值,可以下次访问时获取出来

生命周期

开始:第一次访问session(req.getSession())或访问到jsp时(内置session),产生Session对象

结束:

  • 浏览器关闭,则失效
  • Session超时,则失效
    • session.setMaxInactiveInterval(seconds);//设置最大有效时间(单位:秒)
  • 手工销毁,则失效
    • session.invalidate();//登录退出、注销
@WebServlet("/clearsess")
public class ClearSession extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(); //创建session
        System.out.println("清除 sessionId:"+session.getId());
        //有效期过了后,则销毁session
        //session.setMaxInactiveInterval(20);  //设置session有效期  单位:秒
        session.invalidate();  //手动销毁
    }
}

八、总结与作业

总结

1.项目升级
登录成功后,展示数据;分层-控制层和展示数据层分离
2.跳转方式(重点)
两种跳转:转发、重定向; 区别
跳转测试案例-传值跳转; 项目完善
3.servlet的生命周期
四个阶段-单例设计;执行流程
4.Servlet特性
Servlet中线程安全分析;处理线程安全方式
5.状态管理
概述-存数据; 状态管理方式-cookie,session
6.cookie会话
概述;好处与弊端;应用-设置,获取,清除cookie
7.Session会话(重点)
概述;原理;特点-同一用户多次访问属于同一会话;生命周期

作业

1.在login.html中添加记住我的复选框,在点击登录,成功后,存储cookie; 然后再访问另一个Servlet打印出存储的cookie值
注意:无需写三层架构,只需在Servlet中指定“zs”则认为登录成功

2.从前端输入用户名到指定Servlet,并在当前Servlet中将值转发到另一个Servlet,并使用request取出该值

3.从前端输入用户名到指定Servlet,并在当前Servlet中将值重定向到另一个Servlet,并使用session取出该值

晨考

1.200,404,500状态码 分别代表什么意思
200-页面成功访问 404-找不到服务器 500-服务器异常
2.获取用户提交的参数使用的方法是_req.getParameter()_
3.给用户响应时使用的方法是__resp.getWriter().write()___
4.访问Servlet的注解是什么?里面的name,value,loadOnStartup分别是什么意思?
@WebServlet  name-Servlet名 value-映射路径   ;自启动加载
5.转发和重定向的区别
转发-一次请求,服务器内部请求 重定向-两次请求,客户端重新发的请求
6.request与response对象分别用于做什么?
request-接收客户端参数
response:响应数据给客户端