重定向与转发
一、项目升级
展示数据
用户登录成功后,可以展示出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的访问,并调用方法:
- 调用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协议是无状态的,也就是无记忆能力;在项目中会产生一个问题,访问后会产生处理数据,但再次访问时,可能还需要重新再生成一次。
状态管理方式
有两种状态管理方式:(存数据)
服务器技术–Session会话管理
客户端技术-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"));
}
}
结论:
- 不同浏览器的访问session会创建不同的session对象
- 同一个浏览器的多次访问是同一个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:响应数据给客户端