一、基本概念

1.1 前言

web开发:

  • 静态web
    • html、css
    • 提供给所有人看的数据不会发生改变
  • 动态web
    • 提供给所有人看的数据始终会发生变化
    • 技术栈:Servlet/JSP,ASP,PHP

在java中,动态web资源的开发的技术统称为javaweb

1.2 web应用程序

web应用程序:可以提供浏览器访问的程序

  • a.html,b.html….多个web资源,这些web资源可以被外界访问,对外界提供服务
  • URL
  • 这个统一的web资源会被放在同一个文件夹下,web应用程序 —> Tomcat:服务器
  • 一个人web应用有多个部分组成(静态web、动态web)
    • html、css、js
    • jsp、servlet
    • java程序
    • jar包
    • 配置文件

1.3 静态web

  • *.htm , *.html ,如果服务器上存在,我们就可以读取

image-20220520162342774

  • 静态web存在的缺点
    • web页面无法动态更新
      • 轮播图,点击特效:伪动态
      • javascript
      • VBscript

1.4 动态web

页面会动态展示:“web页面展示的效果因人而异”

image-20220520163713527

缺点:

  • 加入服务器的动态web资源出现了错误,我们需要重新编写我们的后台程序,重新发布
    • 停机维护

优点:

  • 可以动态更新web页面
  • 可以与数据库交互(数据持久化:注册、商品信息等)
    • JDBC

二、web服务器

2.1 技术讲解

ASP:

  • 微软:国内最早流行的就是asp

  • 在html中嵌入了VB的脚本代码,ASP+COM

  • 在ASP开发中,基本上一个页面都有几千行的业务代码,维护成本高

  • C#

  • IIS服务器

PHP:

  • php开发速度快,功能强大,跨平台,代码很简单
  • 无法承载大访问量的情况(局限性)

JSP/Servlet:

  • sun公司(被Oracle收购)主推的B/S架构(B/S:浏览器和服务器;C/S:客户端和服务器)
  • 基于java语言(所有的大公司或者一些开源的组件都是用java写的)
  • 可以承载三高问题(高并发、高可用、高性能)带来的影响

2.2 web服务器

服务器是一种被动操作,用来处理用户的请求和给用户一些响应信息

IIS

微软的(ASP);Windows中自带的

Tomcat

​ Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为比较流行的Web 应用服务器。

​ Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,它是最佳的选择。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。

​ 诀窍是,当配置正确时,Apache 为HTML页面服务,而Tomcat 实际上运行JSP 页面和Servlet。Tomcat最新版本为10.0.14

image-20220520170704991

apache目录:

image-20220520172135614

  • bin:存放一些执行文件(*.exe),可以直接使用

  • include:包含很多c语言的文件(跟系统底层有关)

  • jre:运行环境

    • lib:存放jar包(平时用的java.lang)
    • java核心jar包是rt.jar

三、Tomcat

3.1 文件夹作用

image-20220526174144870

  • bin:启动、关闭的脚本文件

  • conf:配置文件

  • lib:依赖的jar包

  • logs:日志文件

  • temp:存放一些临时文件

  • webapps:存放网站的文件

    • -ROOT

    • -MyWeb

      • WEB-INF

        - classes:java程序
            
        - lib:web应用所依赖的jar包
            
        - web.xml:网站配置文件
        
      • index.html:默认的首页

      • static

        - css
        - js
        - img
        
  • work:工作目录

启动、关闭Tomacat:

进入bin目录,双击 startup.bat 启动 Tomcat,shutdown.bat 关闭Tomcat

访问 127.0.0.1:8080 默认端口为 8080

image-20220526174906692

image-20220526174747847

核心配置文件(conf/server.xml)

例如配置主机的名字:

image-20220527155615325

Engine 组件是 Servlet 处理器的一个实例,即 Servlet 引擎

name 是引擎的逻辑名称,在日志和错误消息中会碰到

defaultHost 是指定默认主机,分配主机来执行用户请求

Host 组件位于 Engine 容器中用于接收请求并进行相应处理的虚拟主机。通过该容器可以运行 Servlet 或者 JSP 来处理请求。

每个Host容器都是一个虚拟主机,对应于不同的域名

在 Engine 中定义的多个虚拟主机的Host主机名称中至少要有一个跟 defaultHost 定义的主机名称同名

  • 当我们浏览器访问一个域名(a.com)时,首先会查找本地的host文件来解析域名,若是解析到了127.0.0.1(本地主机),就会查找本地的web服务,找到tomcat之后,tomcat读取并搜索server.xml,找到127.0.0.1对应的虚拟主机Host;

    <Engine name="Catalina" defaultHost="localhost">
    	<Host name="localhost"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            #这样能正常访问tomcat页面
         
    <Engine name="Catalina" defaultHost="tp.io">
    	<Host name="tp.io"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            #正常访问(host文件已经将 tp.io 解析到 127.0.0.1)
    • 假如没有对应的虚拟主机Host,就会将请求发往默认的虚拟主机(Engine default)
    <Engine name="Catalina" defaultHost="localhost">
    	<Host name="tp.io"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            #正常访问(host文件已经将 tp.io 解析到 127.0.0.1)
            
    <Engine name="Catalina" defaultHost="tp.io">
    	<Host name="a.com"  appBase="webapps"
                unpackWARs="true" autoDeploy="true">
            #无法访问 (host文件已经将 tp.io 解析到 127.0.0.1)

    上面四个例子可以发现,Engine的defaultHost需要直接定义为主机的名字(没有映射关系)

    而Host的name可以是映射关系(host文件)中的主机名字

    最好Host的name 和 defaultHost 至少有一对名字保持一致

    • 假如既没有对应的虚拟主机Host,默认的虚拟主机又不存在,就前往下一步
  • 若是没解析到,就会去访问DNS解析,最后结束

image-20220527173216398

默认端口号:

  • tomcat:8080
  • mysql:3306
  • http:80
  • https:443

四、Servlet

4.1 简介

  • servlet是sun公司开发动态web的一门技术
  • sun在这些API中提供一个接口叫做:Servlet,如果你想开发一个Servlet小程序,只需要完成两个小步骤:
    • 编写一个类,实现Servlet接口
    • 把开发好的java类部署到web服务器中

把实现了Servlet接口的java程序叫做:Servlet

4.2 HelloSevlet

Servlet接口sun公司有两个默认的实现类:HttpServlet、GenericServlet

1、关于Maven父子工程的理解:

在父项目中:

<modules>
  <module>servlet-01</module>
</modules>

在子项目中:

<parent>
       <artifactId>Maven01</artifactId>
       <groupId>com.my.maven01</groupId>
       <version>1.0-SNAPSHOT</version>
   </parent>

父项目中的jar包,子项目可以直接使用

2、编写一个Servlet程序

  • 编写一个普通类
  • 实现Servlet接口,这里我们直接继承HttpServlet

查看HttpServlet源码:

public abstract class HttpServlet extends GenericServlet implements Serializable{
    
}
//HttpServlet 又继承了 GenericServlet

查看GenericServlet源码:

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {

}
//GenericServlet 又继承了 Servlet

编写的程序:

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //ServletOutputStream outputStream = resp.getOutputStream(); //两个方法都可以
        PrintWriter writer = resp.getWriter(); //响应流
        writer.print("hello sevlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

3、编写Servlet的映射

为什么:我们写的是java程序,但是要通过浏览器访问,而浏览器需要连接web服务器,所以需要在web服务中注册我们写的servlet

还需要给浏览器一个访问的路径(web.xml):

<!-- 注册servlet-->
<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.my.servlet.HelloServlet</servlet-class>
</servlet>
<!-- servlet的请求路径-->
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

4、配置tomcat

注意:配置项目发布的路径就可以了(我这里设置为s1)

5、启动测试

访问http://localhost:8080/s1/hello

image-20220531221951783

image-20220531222004029

五、Servlet原理

5.1 图解

image-20220602184733080

5.2 Mapping 问题

1、一个Servlet可以指定一个映射路径

<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

2、一个Servlet可以指定多个映射路径

<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello1</url-pattern>
    </servlet-mapping>
<servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello2</url-pattern>
    </servlet-mapping>

3、一个Servlet可以指定通用路径

<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello/*</url-pattern>
</servlet-mapping>

4、一个Servlet可以指定后缀和前缀(前面不能加请求路径,例如:/*.do 或者 /hello/*.do

<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>*.siii0</url-pattern>  
</servlet-mapping>

这样访问也是符合的:/hello/123.siii0

只要以 .siii0 结尾就行

5、默认请求路径(少用)

<!-- 不会进入根目录的index页面,直接进入我们写的页面,index页面被覆盖了 -->
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

5.2.1 优先级问题

指定了固有的固有的映射路径优先级最高,没有找到就走默认的处理请求

因此就可以定义自己的404页面

public class Error extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer = resp.getWriter();
        writer.print("<h1>404\n没有找到该页面哦</h1>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}
<servlet>
        <servlet-name>error</servlet-name>
        <servlet-class>com.my.servlet.Error</servlet-class>
    </servlet>
    <!-- servlet的请求路径 设置为默认路径 访问未注册的servlet就会返回该页面 404
        当然初始页面也是该页面 -->
    <servlet-mapping>
        <servlet-name>error</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

最终效果:

http://localhost:8080/s1/

image-20220602210936874

http://localhost:8080/s1/hello

image-20220602211002196

http://localhost:8080/s1/hellod11xsf

image-20220602211028293

5.3 ServletContext

web容器启动的时候,它为每个web程序都创建一个ServletContext对象,它代表了当前的web应用

5.3.1 共享数据

setAttribute() 方法设置变量,getAttribute() 获取变量

一个Servlet中保存的数据,可以被另外一个Servlet拿到

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String username = "siii0";
        context.setAttribute("username",username);  //在ServletContext中设置一个键值对

        resp.getWriter().print("hello");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
public class GetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String username = (String) context.getAttribute("username");
		
        //设置编码,防止中文乱码
        resp.setCharacterEncoding("utf-8");  
        resp.setContentType("text/html");
        resp.getWriter().print("名字"+username);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
<!-- web.xml 设置映射关系 -->
<servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.my.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>getname</servlet-name>
        <servlet-class>com.my.servlet.GetServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>getname</servlet-name>
        <url-pattern>/getname</url-pattern>
    </servlet-mapping>

这样就能实现不同的Servlet之间的访问:

(未设置ServletContext对象中的值时)

image-20220606162707349

(设置了ServletContext对象中的值后)

image-20220606162741622

5.3.2 获得初始化参数

web.xml 中设置初始化参数,利用getInitParameter() 方法读取

该页面映射到了 /gp

<!-- 设置初始化参数 -->
    <context-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
    </context-param>
public class ServletDemo03 extends HttpServlet {   
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String url = context.getInitParameter("url");
        resp.getWriter().print(url);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

image-20220606165201251

5.3.3 请求转发

利用 getRequestDispatcher() 方法获取 RequestDispatcher 对象

利用该对象的 foward() 方法转发请求

该页面映射到了 /sd4

public class ServletDemo04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        //获取目标请求 转发的对象
        //RequestDispatcher requestDispatcher = context.getRequestDispatcher("/gp");
        //requestDispatcher.forward(req,resp);  //转发请求

        context.getRequestDispatcher("/gp").forward(req,resp); //和上面一样
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

image-20220606165225126

可以看到 /sd4/gp 显示的输出是一样的,但是请求转发不同于重定向:

请求转发:A向B发出请求,B然后向C发出请求,最后A从B得到C的响应

重定向:A向B发出请求,B让A向C发出请求,最后A直接得到C的响应

image-20220606165706680

5.3.4 读取资源文件

利用getResourceAsStream() 方法将文件转换为文件流

利用 Properties 对象的 getProperty() 方法获取文件流中的参数

Properties

  • 在java目录下新建properties
  • 在resources目录下新建properties

发现:都被打包到了同一路径下:classes,我们俗称这个路径为:classpath

思路:需要一个文件流

public class ServletDemo05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        //将资源文件转换为流,获取一个流对象
        InputStream is = context.getResourceAsStream("/WEB-INF/classes/b.properties");

        //导入流对象后就可以读取其中的数据
        Properties properties = new Properties();
        properties.load(is);

        String username = properties.getProperty("username");
        String password = properties.getProperty("password");

        resp.getWriter().print(username+":"+password);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
#b.properties
username=root
password=123456

image-20220606173243549

5.4 HttpServletResponse

web服务器接收到客户端的http请求的时候,针对这个请求,分别创建一个代表请求的HttpServletRequest对象,代表响应的HttpServletResponse对象

  • 如果要获取客户端请求过来的参数,找HttpServletRequest
  • 如果要向客户端响应一些信息,找HttpServletResponse

5.4.1 简单分类

  • 负责向客户端发送数据的方法
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
  • 负责向浏览器发送响应头的方法
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentType(String var1);
//等等
  • 响应状态码(常量)
int SC_CONTINUE = 100;
int SC_OK = 200;
int SC_USE_PROXY = 305;
int SC_NOT_FOUND = 404;
int SC_INTERNAL_SERVER_ERROR = 500;
//等等的所有状态码

5.4.2 常见应用(下载本地文件)

  • 向浏览器输出信息
  • 下载文件

1、获取下载文件的路径

2、下载文件的文件名

3、让浏览器支持下载我们需要的东西

4、获取下载文件的输入流

5、创建缓冲区

6、获取OutputStream对象

7、将FileOutputStream流写入到buffer缓冲区

8、使用OutputStream将缓冲区的数据输出到客户端

public class FileServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        1、获取下载文件的路径
        String realpath="D:\\IDEA-pros\\project\\Maven01\\response-downloadfile\\src\\main\\resources\\1.jpg";
        String realpath2=this.getServletContext().getContextPath();
        String realpath3=this.getServletContext().getRealPath("1.jpg");
        System.out.println(realpath2);
        System.out.println(realpath3);
//        2、下载文件的文件名(windows路径 \)
        String filename=realpath.substring(realpath.lastIndexOf("\\")+1);
//        3、让浏览器支持下载我们需要的东西
        //编码防止中文文件名可能出现乱码
        resp.addHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(filename,"utf-8"));
//        4、获取下载文件的输入流
        FileInputStream in = new FileInputStream(realpath);
//        5、创建缓冲区
        int len;
        byte[] buffer = new byte[1024];
//        6、获取OutputStream对象
        ServletOutputStream out = resp.getOutputStream();
//        7、将FileInputStream流写入到buffer缓冲区
//        8、使用OutputStream将缓冲区的数据输出到客户端
        while((len=in.read(buffer))>0){
            out.write(len);
        }
        out.close();
        in.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

这里是将文件路径写死了,还有其他方法

期间遇到一个问题:出现了0字节下载

发现问题出在写入缓冲区操作的while判断上

我一开始写的是 !=0

改为 >0 就正常了

image-20220608231316582

这是输出的内容,之所以不能用getRealPath 就是这个原因:他获取的是tomcat的web项目的路径

//另外一种获取文件名的方式
//因为图片被存储在了web项目的classes目录中,所以可以用这种方法
String realpath = this.getClass().getClassLoader().getResource("1.jpg").getPath();
//这里的路径是java给的路径,所以是 / ,还需要改
String filename=realpath.substring(realpath.lastIndexOf("/")+1);

image-20220608232011931

5.4.3 验证码功能

  • 前端实现(js)
  • 后端实现,需要用到java的图片类,生成一个图片

1、让浏览器三秒刷新一次(自动刷新验证码)

2、在内存中创建一个图片(刚开始是空的)

3、得到图片(创建画笔类设置图片背景颜色,字体颜色,最后给图片写数据)

4、设置响应头、缓存(让验证码图片正常工作)

5、把图片写给浏览器

public class ImageServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //让浏览器三秒刷新一次
        resp.setHeader("refresh","3");
        //在内存中创建一个图片
        BufferedImage bufferedImage = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
        //得到图片
        Graphics2D graphics = (Graphics2D) bufferedImage.getGraphics(); //画笔
        //设置图片的背景颜色
        graphics.setColor(Color.white);
        graphics.fillRect(0,0,80,20);
        //给图片写数据
        graphics.setColor(Color.blue);
        graphics.setFont(new Font(null,Font.BOLD,20)); //字体格式,大小
        graphics.drawString(RandomNum(),0,20); //字体坐标

        //告诉浏览器,这个请求用图片的方式打开
        resp.setContentType("image/jpeg");
        //网站存在缓存,不让浏览器缓存
        resp.setDateHeader("expires",-1);
        resp.setHeader("Cache-Control","no-cache");
        resp.setHeader("Pragma","no-cache");

        //把图片写给浏览器
        ImageIO.write(bufferedImage,"jpg",resp.getOutputStream());

    }

    //生成随机七位字符串
    private String RandomNum(){
        Random random = new Random();
        String num=random.nextInt(9999999)+"";//七位数
        //String是不可更改的常量,StringBuffer是字符串的变量
        StringBuffer sb = new StringBuffer();
        //保证是七位数
        for(int i=0;i<7-num.length();i++){
            sb.append("0");
        }
        num = sb.toString()+num;
        return num;
    }

image-20220609233755122

image-20220609233741245

5.4.4 实现重定向

image-20220612191924367

B一个web资源收到客户端A的请求后,B会通知A客户端去访问web资源C,这个过程就叫做重定向

常见场景:

  • 用户登录
void sendRedirect(String var1) throws IOException;
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect("/resp/image");
        /**
         * 相当于这两个操作:
         * resp.setHeader("Location","/resp/image");
         * resp.setStatus(302);
         */
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

请问请求转发和重定向的区别?

相同点:

  • 页面都会跳转

不同点

  • 请求转发 url不会变化
  • 重定向 url会改变

image-20220612195324238

顺便结合重定向,写一个简易的登陆成功页面:

登录表单

<%-- 页面编码,否则中文出现乱码 --%>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<h2>Hello World!</h2>

<%-- 这里提交的路径需要是项目的路径 --%>
<%-- ${pageContext.request.contextPath}代表当前项目
     写法类似于 this.getServletContext().getContextPath(); --%>
<form action="${pageContext.request.contextPath}/login" method="get">
    用户名: <input type="text" name="username">
    密码: <input type="text" name="password">
    <input type="submit">
</form>
</body>
</html>

image-20220612230736168

登陆成功页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>success</h1>
</body>
</html>

image-20220612230923178

请求处理

public class RequestTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理请求
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        System.out.println(username+":"+password);
        resp.sendRedirect("/resp/success.jsp");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

重定向的时候一定要注意 路径问题 ,否则会报404

<form action="${pageContext.request.contextPath}/login" method="get">

上面就是在jsp中获取当前项目路径的方法

5.5 HttpServletRequest

HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器,Http请求的所有信息会被封装到HttpServletRequest中,通过HttpServletRequest的方法,获取到客户端的所有信息

image-20220615213737707

public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");

        String username = req.getParameter("username");//防止请求的数据出现中文乱码
        String password = req.getParameter("password");//防止响应的数据乱码
        String[] hobbies = req.getParameterValues("hobby");
        System.out.println("=================");
        System.out.println("欢迎!"+username+"!\n你的密码为:"+password+"\n你的爱好为:");
        System.out.println(Arrays.toString(hobbies));

        this.getServletContext().getRequestDispatcher("/success.jsp").forward(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h1>登陆成功</h1>
<h2>你的用户名为:${pageContext.request.getParameter("username")}<br>
    密码为:${pageContext.request.getParameter("password")}
</h2>
</body>
</html>

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
<div style="text-align: center">
    <h1>登录</h1>
    <form action="${pageContext.request.contextPath}/login" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        爱好:
        <input type="checkbox" name="hobby" value="唱">唱
        <input type="checkbox" name="hobby" value="跳">跳
        <input type="checkbox" name="hobby" value="rap">rap
        <br>
        <input type="submit" name="提交" value="提交">
    </form>
</div>
</body>
</html>

image-20220615213954811

image-20220615214009877

image-20220615213932809

注意重定向和请求转发的细节(区别上面讲了):

  • 重定向:需要包含项目根目录的路径
this.getServletContext().getRequestDispatcher("/success.jsp").forward(req,resp);
  • 请求转发:不需要包含项目根目录
resp.sendRedirect("/resp/image");

关于jsp,就需要一些前端的知识了

六、Cookie、Session

6.1 会话

会话:用户打开了一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器,这个过程可以称之为会话

有状态会话:

一个同学来过教室,下次再来教室,我们会知道这个同学,曾经来过,称之为有状态会话

一个网站,怎样证明你来过?

客户端 服务端

1、服务端给客户端一个信件,客户端下次访问带上这个信件就可以了(cookie)

2、服务器登记你来过了,下次你来的时候我匹配你(session)

6.2 保存会话的两种技术

cookie

  • 客户端技术(响应、请求)

session

  • 服务器技术(利用这个技术,可以保存用户的会话信息,我们可以把信息或数据放在session中)

常见场景:网站登录后再次访问,不用登陆可以直接访问

public class CookieDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //服务器,告诉你你来的时间,把这个时间封装为一个信件,下次来带上,我就知道你来了

        //解决中文乱码
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");

        PrintWriter out = resp.getWriter();

        //Cookie 服务端从客户端获取
        Cookie[] cookies = req.getCookies();  //数组,说明cookie可能有多个

        //判断cookie是否存在
        if(cookies!=null){
            out.print("你上次访问的时间是:");

            for (int i = 0; i < cookies.length; i++) {
                Cookie cookie = cookies[i];
                //获取cookie的名字
                if("lastLoginTime".equals(cookie.getName())){
                    //获取cookie中的值
                    long l = Long.parseLong(cookie.getValue());
                    Date date = new Date(l);
                    out.print(date.toLocaleString());
                }
            }
        }
        else {
            System.out.println("这是你第一次访问本站");
        }
        
		//写在循环外,每次都能更新
        resp.addCookie(new Cookie("lastLoginTime", System.currentTimeMillis()+""));

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

这里由于第一次请求中cookie也有东西,所以else中的内容就无法触发了

image-20220618233606683

第二次访问:

image-20220618233624848

并且我们需要携带的cookie保存在本地:

image-20220618233738663

cookie:一般保存在本地的用户目录下 appdata

一个网站cookie是否存在上限?

  • 一个cookie只能保存一个信息
  • 一个web站点可以给浏览器发送多个cookie,最多存放20个cookie
  • cookie大小有限制4kb
  • 300个cookie浏览器上限

删除cookie:

  • 不设置有效期,关闭浏览器,自动失效
  • 设置有效期为0
Cookie cookie = new Cookie("lastLoginTime", System.currentTimeMillis() + "");  //键值对需要一致
cookie.setMaxAge(0); //设置cookie有效期为0,是cookie直接失效
resp.addCookie(cookie);

键值对一致,才能把原先的cookie替换

编码解码:

为了解决出现乱码的情况

URLEncoder.encode("饼干")
URLDecoder.decode(cookie.getValue())

image-20220620200138376

6.4 session

什么是session:

  • 服务器会给每个用户(浏览器),创建一个session对象
  • 一个session独占一个浏览器,只要浏览器没有关闭,session就一直存在
  • 用户登陆之后,可以访问整个网站的链接

image-20220625175021610

session和cookie的区别:

  • Cookie是把用户的数据写到用户的浏览器,浏览器保存(可以保存多个)
  • Session把用户的数据写到用户独占的Session中,服务器端保存(保存重要的信息,减少服务器资源的浪费)
  • Session对象由服务器创建

使用场景:

  • 保存一个用户的信息
  • 购物车信息
  • 在整个网站中经常使用的数据,将它保存在Session中
public class SessionDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //解决乱码
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");

        //获取session
        HttpSession session = req.getSession();
        String sessionId = session.getId();

        //存入session
        session.setAttribute("name",new Person("siii0",1));

        if(session.isNew()){
            resp.getWriter().write("session是新创建的,ID:"+sessionId);
        }
        else{
            resp.getWriter().write("session已经创建了,ID:"+sessionId);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}
//注销session
HttpSession session = req.getSession();
session.removeAttribute("name");
//手动注销
session.invalidate();

会话自动过期:web.xml配置

<!-- 设置session的默认失效时间 -->
<session-config>
    <!-- session 失效时间单位为分钟 -->
    <session-timeout>1</session-timeout>
</session-config>