day08、Tomcat-Servlet-HTTP
1、Web 服务器 - Tomcat
1.1、概念
-
什么是 JavaWeb ?
-
Web:全球广域网,也称为万维网(www),能够通过浏览器访问的网站
JavaWeb: 使用 Java技术进行web互联网开发
-
-
JavaWeb 技术栈
-
B/S 架构:Browser/Server,浏览器/服务器 架构模式,它的特点是,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web资源,服务器把Web资源发送给浏览器即可。
好处:易于维护升级,服务器端升级后,客户端无需任何处理就可以使用到新的版本
-
静态资源:HTML、CSS、JavaScript、图片等。负责页面展现
动态资源:Servlet、JSP 等。负责逻辑处理和数据处理
数据库:负责存储数据
HTTP协议:定义通信规则
Web服务器:负责解析 HTTP 协议,解析请求数据,并发送响应数据
1.2、什么是web服务器
Web服务器是一个应该程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是"提供网上信息浏览服务"。
1.3、Tomcat
Tomcat的相关概念:
-
Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。
-
概念中提到了JavaEE规范,那什么又是JavaEE规范呢?
JavaEE: Java Enterprise Edition,Java企业版。指Java企业级开发的技术规范总和。包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF。
-
因为Tomcat支持Servlet/JSP规范,所以Tomcat也被称为Web容器、Servlet容器。Servlet需要依赖Tomcat才能运行。
-
Tomcat的官网: https://tomcat.apache.org/ 从官网上可以下载对应的版本进行使用。
Tomcat的LOGO
小结
通过这一节的学习,我们需要掌握以下内容:
- Web服务器的作用
封装HTTP协议操作,简化开发
可以将Web项目部署到服务器中,对外提供网上浏览服务
- Tomcat是一个轻量级的Web服务器,支持Servlet/JSP少量JavaEE规范,也称为Web容器,Servlet容器。
1.3.1、 基本使用
Tomcat总共分两部分学习,先来学习Tomcat的基本使用,包括Tomcat的下载、安装、卸载、启动和关闭。
下载
直接从官网下载
大家可以自行下载,也可以直接使用资料中已经下载好的资源,
Tomcat的软件程序 资料/2. Tomcat/apache-tomcat-8.5.68-windows-x64.zip
Tomcat的源码 资料/2. Tomcat/tomcat源码/apache-tomcat-8.5.68-src.zip
安装
Tomcat是绿色版,直接解压即可
-
在D盘的software目录下,将
apache-tomcat-8.5.68-windows-x64.zip
进行解压缩,会得到一个apache-tomcat-8.5.68
的目录,Tomcat就已经安装成功。注意,Tomcat在解压缩的时候,解压所在的目录可以任意,但最好解压到一个不包含中文和空格的目录,因为后期在部署项目的时候,如果路径有中文或者空格可能会导致程序部署失败。
-
打开
apache-tomcat-8.5.68
目录就能看到如下目录结构,每个目录中包含的内容需要认识下,bin:目录下有两类文件,一种是以
.bat
结尾的,是Windows系统的可执行文件,一种是以.sh
结尾的,是Linux系统的可执行文件。webapps:就是以后项目部署的目录
到此,Tomcat的安装就已经完成。
卸载
卸载比较简单,可以直接删除目录即可
启动
双击: bin\startup.bat
启动后,通过浏览器访问 http://localhost:8080
能看到Apache Tomcat的内容就说明Tomcat已经启动成功。
注意: 启动的过程中,控制台有中文乱码,需要修改conf/logging.prooperties
关闭
关闭有三种方式
- 直接x掉运行窗口:强制关闭[不建议]
- bin\shutdown.bat:正常关闭
- ctrl+c: 正常关闭
配置
修改端口
- Tomcat默认的端口是8080,要想修改Tomcat启动的端口号,需要修改 conf/server.xml
注: HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号。
启动时可能出现的错误
-
Tomcat的端口号取值范围是0-65535之间任意未被占用的端口,如果设置的端口号被占用,启动的时候就会包如下的错误
-
Tomcat启动的时候,启动窗口一闪而过: 需要检查JAVA_HOME环境变量是否正确配置
部署
-
Tomcat部署项目: 将项目放置到webapps目录下,即部署完成。
-
将
资料/2. Tomcat/hello
目录拷贝到Tomcat的webapps目录下 -
通过浏览器访问
http://localhost/hello/a.html
,能看到下面的内容就说明项目已经部署成功。但是呢随着项目的增大,项目中的资源也会越来越多,项目在拷贝的过程中也会越来越费时间,该如何解决呢?
-
-
一般JavaWeb项目会被打包称war包,然后将war包放到Webapps目录下,Tomcat会自动解压缩war文件
-
将
资料/2. Tomcat/haha.war
目录拷贝到Tomcat的webapps目录下 -
Tomcat检测到war包后会自动完成解压缩,在webapps目录下就会多一个haha目录
-
通过浏览器访问
http://localhost/haha/a.html
,能看到下面的内容就说明项目已经部署成功。
-
至此,Tomcat的部署就已经完成了,至于如何获得项目对应的war包,后期我们会借助于IDEA工具来生成。
1.4、 Web项目结构
Web项目的结构分为:开发中的项目和开发完可以部署的Web项目,这两种项目的结构是不一样的,我们一个个来介绍下:
-
Maven Web项目结构: 开发中的项目
-
开发完成部署的Web项目
- 开发项目通过执行Maven打包命令package,可以获取到部署的Web项目目录
- 编译后的Java字节码文件和resources的资源文件,会被放到WEB-INF下的classes目录下
- pom.xml中依赖坐标对应的jar包,会被放入WEB-INF下的lib目录下
1.5、创建Maven Web项目
介绍完Maven Web的项目结构后,接下来使用Maven来创建Web项目,创建方式有两种:使用骨架和不使用骨架
方式1:使用Idea提供的JavaEnterprise选项创建web项目
【第一步】选择New Module—>JavaEnterprise,然后一路下一步即可
【第二步】删除pom.xml中多余的坐标,以及删除多余的java类和jsp文件
说明:2020版及以后版本的idea JavaEnterprise默认创建Maven Web项目,之前版本的Idea JavaEnterprise不是创建Maven Web项目
方式2:使用JBLJavaToWeb插件将普通的maven项目转换成web项目【吐血推荐】
【第一步】idea中安装JBLJavaToWeb插件,可能需要重启idea客户端
【第二步】创建普通的maven java项目,使用JBLJavaToWeb插件转换成web项目
说明:这种方式创建出来的web项目,没有多余的坐标。
1.6、 IDEA使用Tomcat
- Maven Web项目创建成功后,通过Maven的package命令可以将项目打包成war包,将war文件拷贝到Tomcat的webapps目录下,启动Tomcat就可以将项目部署成功,然后通过浏览器进行访问即可。
- 然而我们在开发的过程中,项目中的内容会经常发生变化,如果按照上面这种方式来部署测试,是非常不方便的
- 如何在IDEA中能快速使用Tomcat呢?
在IDEA中集成使用Tomcat有两种方式,分别是集成本地Tomcat和Tomcat Maven插件,此处只演示Tomcat Maven插件
1.7、Tomcat Maven插件
在IDEA中使用本地Tomcat进行项目部署,相对来说步骤比较繁琐,所以我们需要一种更简便的方式来替换它,那就是直接使用Maven中的Tomcat插件来部署项目,具体的实现步骤,只需要两步,分别是:
-
在pom.xml中添加Tomcat插件
<build> <plugins> <!--Tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <!--设置端口号--> <port>8080</port> <!--解决GET请求中文参数乱码问题--> <uriEncoding>utf-8</uriEncoding> <!--/虚拟路径,表示项目在tomcat服务器中的命名,如果没有设置,默认使用artifactID--> <path>/demo</path> </configuration> </plugin> </plugins> </build>
-
使用Maven Helper插件快速启动项目,选中项目,右键–>Run Maven --> tomcat7:run
注意:
- 如果选中项目并右键点击后,看不到Run Maven和Debug Maven,这个时候就需要在IDEA中下载Maven Helper插件,具体的操作方式为: File --> Settings --> Plugins --> Maven Helper —> Install,安装完后按照提示重启IDEA,就可以看到了。
- Maven Tomcat插件目前只有Tomcat7版本,没有更高的版本可以使用
- 使用Maven Tomcat插件,要想修改Tomcat的端口和访问路径,可以直接修改pom.xml
<build>
<plugins>
<!--Tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port><!--访问端口号 -->
<!--项目访问路径
未配置访问路径: http://localhost:80/tomcat-demo2/a.html
配置/后访问路径: http://localhost:80/a.html
如果配置成 /hello,访问路径会变成什么?
答案: http://localhost:80/hello/a.html
-->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
小结
通过这一节的学习,大家要掌握在IDEA中使用Tomcat的两种方式,集成本地Tomcat和使用Maven的Tomcat插件。后者更简单,推荐大家使用,但是如果对于Tomcat的版本有比较高的要求,要在Tomcat7以上,这个时候就只能用前者了。
1.8、idea中配置maven tomcat7模板
把下面的配置复制到"第⑥步模板内容"处就行了
<!--添加maven tomcat7 插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!--设置端口号-->
<port>8080</port>
<!--解决GET请求中文参数乱码问题-->
<uriEncoding>utf-8</uriEncoding>
</configuration>
</plugin>
2、Servlet
2.1 简介
-
Servlet是JavaWeb最为核心的内容,它是Java提供的一门动态web资源开发技术。
-
使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。
-
Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet
介绍完Servlet是什么以后,接下来我们就按照快速入门
->执行流程
->生命周期
->体系结构
->urlPattern配置
->XML配置
的学习步骤,一步步完成对Servlet的知识学习,首选我们来通过一个入门案例来快速把Servlet用起来。
2.2 快速入门-注解配置
需求分析: 编写一个Servlet类,并使用IDEA中Tomcat插件进行部署,最终通过浏览器访问所编写的Servlet程序。
具体的实现步骤为:
- 创建Web项目
web-demo
,导入Servlet依赖坐标
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
此处为什么需要添加该标签?
provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
-->
<scope>provided</scope>
</dependency>
- 创建:定义一个类,实现Servlet接口,并重写接口中所有方法,并在service方法中输入一句话
package com.itheima.web;
import javax.servlet.*;
import java.io.IOException;
public class ServletDemo1 implements Servlet {
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("servlet hello world~");
}
public void init(ServletConfig servletConfig) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
- 配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
@WebServlet("/demo1")
- 访问:启动Tomcat,浏览器中输入URL地址访问该Servlet
http://localhost:8080/web-demo/demo1
- 器访问后,在控制台会打印
servlet hello world~
说明servlet程序已经成功运行。
至此,Servlet的入门案例就已经完成,大家可以按照上面的步骤进行练习了。
2.3 快速入门-XML配置【了解】
前面对应Servlet的配置,我们使用的是@WebServlet,这个是Servlet从3.0版本后开始支持注解配置,3.0版本前只支持XML配置文件的配置方法。
对于XML的配置步骤有两步:
- 编写Servlet类
package com.itheima.web;
import javax.servlet.*;
import java.io.IOException;
public class ServletDemo1 implements Servlet {
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("servlet hello world~");
}
public void init(ServletConfig servletConfig) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
- 在web.xml中配置该Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- Servlet全类名 -->
<servlet>
<!-- servlet的名称,名字任意-->
<servlet-name>demo1</servlet-name>
<!--servlet的类全名-->
<servlet-class>com.itheima.web.ServletDemo1</servlet-class>
</servlet>
<!--Servlet 访问路径 -->
<servlet-mapping>
<!-- servlet的名称,要和上面的名称一致-->
<servlet-name>demo1</servlet-name>
<!-- servlet的访问路径-->
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
</web-app>
这种配置方式和注解比起来,确认麻烦很多,所以建议大家使用注解来开发。但是大家要认识上面这种配置方式,因为并不是所有的项目都是基于注解开发的。
2.4 执行流程
Servlet程序已经能正常运行,但是我们需要思考个问题: 我们并没有创建ServletDemo1类的对象,也没有调用对象中的service方法,为什么在控制台就打印了servlet hello world~
这句话呢?
要想回答上述问题,我们就需要对Servlet的执行流程进行一个学习。
- 浏览器发出
http://localhost:8080/web-demo/demo1
请求,从请求中可以解析出三部分内容,分别是localhost:8080
、web-demo
、demo1
- 根据
localhost:8080
可以找到要访问的Tomcat Web服务器 - 根据
web-demo
可以找到部署在Tomcat服务器上的web-demo项目 - 根据
demo1
可以找到要访问的是项目中的哪个Servlet类,根据@WebServlet后面的值进行匹配
- 根据
- 找到ServletDemo1这个类后,Tomcat Web服务器就会为ServletDemo1这个类创建一个对象,然后调用对象中的service方法
- ServletDemo1实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用
- service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互
小结
介绍完Servlet的执行流程,需要大家掌握两个问题:
- Servlet由谁创建?Servlet方法由谁调用?
Servlet由web服务器创建,Servlet方法由web服务器调用
- 服务器怎么知道Servlet中一定有service方法?
因为我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法
2.5 生命周期
介绍完Servlet的执行流程后,我们知道Servlet是由Tomcat Web服务器帮我们创建的。
接下来咱们再来思考一个问题:Tomcat什么时候创建的Servlet对象?
要想回答上述问题,我们就需要对Servlet的生命周期进行一个学习。
-
生命周期: 对象的生命周期指一个对象从被创建到被销毁的整个过程。
-
Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:
- 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
-- 默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,那么第一个访问的人等待的时间就比较长,用户的 体验就比较差,那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置? @WebServlet(urlPatterns = "/demo1",loadOnStartup = 1) loadOnstartup的取值有两类情况 (1)负整数:第一次访问时创建Servlet对象 (2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高 -- 执行构造方法 -- 执行 init()方法
- 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次
- 请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的==service()==方法对请求进行处理
- 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的==destroy()==方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
-
通过案例演示下上述的生命周期
package com.itheima.web; import javax.servlet.*; import javax.servlet.annotation.WebServlet; import java.io.IOException; /** * Servlet生命周期方法 */ @WebServlet(urlPatterns = "/demo2",loadOnStartup = 1) public class ServletDemo2 implements Servlet { /** * 初始化方法 * 1.调用时机:默认情况下,Servlet被第一次访问时,调用 * * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用 * 2.调用次数: 1次 * @param config * @throws ServletException */ public void init(ServletConfig config) throws ServletException { System.out.println("init..."); } /** * 提供服务 * 1.调用时机:每一次Servlet被访问时,调用 * 2.调用次数: 多次 * @param req * @param res * @throws ServletException * @throws IOException */ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("servlet hello world~"); } /** * 销毁方法 * 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用 * 2.调用次数: 1次 */ public void destroy() { System.out.println("destroy..."); } public ServletConfig getServletConfig() { return null; } public String getServletInfo() { return null; } }
注意:如何才能让Servlet中的destroy方法被执行?
在Terminal命令行中,先使用mvn tomcat7:run
启动,然后再使用ctrl+c
关闭tomcat
小结
这节中需要掌握的内容是:
- Servlet对象在什么时候被创建的?
默认是第一次访问的时候被创建,可以使用@WebServlet(urlPatterns = “/demo2”,loadOnStartup = 1)的loadOnStartup 修改成在服务器启动的时候创建。
- Servlet生命周期中涉及到的三个方法,这三个方法是什么?什么时候被调用?调用几次?
涉及到三个方法,分别是 init()、service()、destroy()
init方法在Servlet对象被创建的时候执行,只执行1次
service方法在Servlet被访问的时候调用,每访问1次就调用1次
destroy方法在Servlet对象被销毁的时候调用,只执行1次
2.6 方法介绍
Servlet中总共有5个方法,我们已经介绍过其中的三个,剩下的两个方法作用分别是什么?
我们先来回顾下前面讲的三个方法,分别是:
- 初始化方法,在Servlet被创建时执行,只执行一次
void init(ServletConfig config)
- 提供服务方法, 每次Servlet被访问,都会调用该方法
void service(ServletRequest req, ServletResponse res)
- 销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet
void destroy()
剩下的两个方法是:
- 获取Servlet信息
String getServletInfo()
//该方法用来返回Servlet的相关信息,没有什么太大的用处,一般我们返回一个空字符串即可
public String getServletInfo() {
return "";
}
- 获取ServletConfig对象
ServletConfig getServletConfig()
ServletConfig对象,在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可。具体如何操作?
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
/**
* Servlet方法介绍
*/
@WebServlet(urlPatterns = "/demo3",loadOnStartup = 1)
public class ServletDemo3 implements Servlet {
private ServletConfig servletConfig;
/**
* 初始化方法
* 1.调用时机:默认情况下,Servlet被第一次访问时,调用
* * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用
* 2.调用次数: 1次
* @param config
* @throws ServletException
*/
public void init(ServletConfig config) throws ServletException {
this.servletConfig = config;
System.out.println("init...");
}
public ServletConfig getServletConfig() {
return servletConfig;
}
/**
* 提供服务
* 1.调用时机:每一次Servlet被访问时,调用
* 2.调用次数: 多次
* @param req
* @param res
* @throws ServletException
* @throws IOException
*/
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("servlet hello world~");
}
/**
* 销毁方法
* 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
* 2.调用次数: 1次
*/
public void destroy() {
System.out.println("destroy...");
}
public String getServletInfo() {
return "";
}
}
getServletInfo()和getServletConfig()这两个方法使用的不是很多,大家了解下。
2.7 体系结构
通过上面的学习,我们知道要想编写一个Servlet就必须要实现Servlet接口,重写接口中的5个方法,虽然已经能完成要求,但是编写起来还是比较麻烦的,因为我们更关注的其实只有service方法,那有没有更简单方式来创建Servlet呢?
要想解决上面的问题,我们需要先对Servlet的体系结构进行下了解:
因为我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义Servlet,会通过继承HttpServlet
具体的编写格式如下:
@WebServlet("/demo4")
public class ServletDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//TODO GET 请求方式处理逻辑
System.out.println("get...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//TODO Post 请求方式处理逻辑
System.out.println("post...");
}
}
- 要想发送一个GET请求,请求该Servlet,只需要通过浏览器发送
http://localhost:8080/web-demo/demo4
,就能看到doGet方法被执行了 - 要想发送一个POST请求,请求该Servlet,单单通过浏览器是无法实现的,这个时候就需要编写一个form表单来发送请求,在webapp下创建一个
a.html
页面,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/web-demo/demo4" method="post">
<input name="username"/><input type="submit"/>
</form>
</body>
</html>
启动测试,即可看到doPost方法被执行了。
Servlet的简化编写就介绍完了,接着需要思考两个问题:
- HttpServlet中为什么要根据请求方式的不同,调用不同的方法?
- 如何调用?
针对问题一,我们需要回顾之前的知识点前端发送GET和POST请求的时候,参数的位置不一致,GET请求参数在请求行中,POST请求参数在请求体中,为了能处理不同的请求方式,我们得在service方法中进行判断,然后写不同的业务处理,这样能实现,但是每个Servlet类中都将有相似的代码,针对这个问题,有什么可以优化的策略么?
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/demo5")
public class ServletDemo5 implements Servlet {
public void init(ServletConfig config) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//如何调用?
//获取请求方式,根据不同的请求方式进行不同的业务处理
HttpServletRequest request = (HttpServletRequest)req;
//1. 获取请求方式
String method = request.getMethod();
//2. 判断
if("GET".equals(method)){
// get方式的处理逻辑
}else if("POST".equals(method)){
// post方式的处理逻辑
}
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
要解决上述问题,我们可以对Servlet接口进行继承封装,来简化代码开发。
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class MyHttpServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest)req;
//1. 获取请求方式
String method = request.getMethod();
//2. 判断
if("GET".equals(method)){
// get方式的处理逻辑
doGet(req,res);
}else if("POST".equals(method)){
// post方式的处理逻辑
doPost(req,res);
}
}
protected void doPost(ServletRequest req, ServletResponse res) {
}
protected void doGet(ServletRequest req, ServletResponse res) {
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
有了MyHttpServlet这个类,以后我们再编写Servlet类的时候,只需要继承MyHttpServlet,重写父类中的doGet和doPost方法,就可以用来处理GET和POST请求的业务逻辑。接下来,可以把ServletDemo5代码进行改造
@WebServlet("/demo5")
public class ServletDemo5 extends MyHttpServlet {
@Override
protected void doGet(ServletRequest req, ServletResponse res) {
System.out.println("get...");
}
@Override
protected void doPost(ServletRequest req, ServletResponse res) {
System.out.println("post...");
}
}
将来页面发送的是GET请求,则会进入到doGet方法中进行执行,如果是POST请求,则进入到doPost方法。这样代码在编写的时候就相对来说更加简单快捷。
类似MyHttpServlet这样的类Servlet中已经为我们提供好了,就是HttpServlet,翻开源码,大家可以搜索service()
方法,你会发现HttpServlet做的事更多,不仅可以处理GET和POST还可以处理其他五种请求方式。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
小结
通过这一节的学习,要掌握:
- HttpServlet的使用步骤
继承HttpServlet
重写doGet和doPost方法
- HttpServlet原理
获取请求方式,并根据不同的请求方式,调用不同的doXxx方法
2.8 urlPattern配置
Servlet类编写好后,要想被访问到,就需要配置其访问路径(urlPattern)
-
一个Servlet,可以配置多个urlPattern
package com.itheima.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * urlPattern: 一个Servlet可以配置多个访问路径 */ @WebServlet(urlPatterns = {"/demo7","/demo8"}) public class ServletDemo7 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo7 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
在浏览器上输入
http://localhost:8080/web-demo/demo7
,http://localhost:8080/web-demo/demo8
这两个地址都能访问到ServletDemo7的doGet方法。 -
urlPattern配置规则
-
精确匹配
/** * UrlPattern: * * 精确匹配 */ @WebServlet(urlPatterns = "/user/select") public class ServletDemo8 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo8 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径
http://localhost:8080/web-demo/user/select
-
目录匹配
package com.itheima.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 目录匹配: /user/* */ @WebServlet(urlPatterns = "/user/*") public class ServletDemo9 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo9 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径
http://localhost:8080/web-demo/user/任意
思考:
- 访问路径
http://localhost:8080/web-demo/user
是否能访问到demo9的doGet方法? - 访问路径
http://localhost:8080/web-demo/user/a/b
是否能访问到demo9的doGet方法? - 访问路径
http://localhost:8080/web-demo/user/select
是否能访问到demo9还是demo8的doGet方法?
答案是: 能、能、demo8,进而我们可以得到的结论是
/user/*
中的/*
代表的是零或多个层级访问目录同时精确匹配优先级要高于目录匹配。 - 访问路径
-
扩展名匹配
package com.itheima.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 扩展名匹配: *.do */ @WebServlet(urlPatterns = "*.do") public class ServletDemo10 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo10 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径
http://localhost:8080/web-demo/任意.do
注意:
- 如果路径配置的不是扩展名,那么在路径的前面就必须要加
/
否则会报错
- 如果路径配置的是
*.do
,那么在*.do的前面不能加/
,否则会报错
- 如果路径配置的不是扩展名,那么在路径的前面就必须要加
-
任意匹配
package com.itheima.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 任意匹配: / */ @WebServlet(urlPatterns = "/") public class ServletDemo11 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo11 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径
http://localhost:8080/demo-web/任意
package com.itheima.web; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebServlet; /** * UrlPattern: * * 任意匹配: /* */ @WebServlet(urlPatterns = "/*") public class ServletDemo12 extends MyHttpServlet { @Override protected void doGet(ServletRequest req, ServletResponse res) { System.out.println("demo12 get..."); } @Override protected void doPost(ServletRequest req, ServletResponse res) { } }
访问路径`http://localhost:8080/demo-web/任意
注意:
/
和/*
的区别?-
当我们的项目中的Servlet配置了 “/”,会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时都会走这个Servlet
-
当我们的项目中配置了"/*",意味着匹配任意访问路径
-
DefaultServlet是用来处理静态资源,如果配置了"/"会把默认的覆盖掉,就会引发请求静态资源的时候没有走默认的而是走了自定义的Servlet类,最终导致静态资源不能被访问
-
-
小结
-
urlPattern总共有四种配置方式,分别是精确匹配、目录匹配、扩展名匹配、任意匹配
-
五种配置的优先级为 精确匹配 > 目录匹配> 扩展名匹配 > /* > / ,无需记,以最终运行结果为准。
2.9 使用Idea Servlet模板快速创建Servlet
- 使用Servlet模板快速创建Servlet
- 修改Idea Servlet模板
3、HTTP
3.1 简介
HTTP概念
HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。
- 数据传输的规则指的是请求数据和响应数据需要按照指定的格式进行传输。
- 如果想知道具体的格式,可以打开浏览器,点击
F12
打开开发者工具,点击Network
来查看某一次请求的请求数据和响应数据具体的格式内容,如下图所示:
注意:在浏览器中如果看不到上述内容,需要清除浏览器的浏览数据。chrome浏览器可以使用ctrl+shift+Del进行清除。
所以学习HTTP主要就是学习请求和响应数据的具体格式内容。
HTTP协议特点
HTTP协议有它自己的一些特点,分别是:
-
基于TCP协议: 面向连接,安全
TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在数据传输方面更安全。
-
基于请求-响应模型的:一次请求对应一次响应
请求和响应是一一对应关系
-
HTTP协议是无状态协议:对于事物处理没有记忆能力。每次请求-响应都是独立的
无状态指的是客户端发送HTTP请求给服务端之后,服务端根据请求响应数据,响应完后,不会记录任何信息。这种特性有优点也有缺点,
- 缺点:多次请求间不能共享数据
- 优点:速度快
请求之间无法共享数据会引发的问题,如:
- 京东购物,
加入购物车
和去购物车结算
是两次请求, - HTTP协议的无状态特性,加入购物车请求响应结束后,并未记录加入购物车是何商品
- 发起去购物车结算的请求后,因为无法获取哪些商品加入了购物车,会导致此次请求无法正确展示数据
具体使用的时候,我们发现京东是可以正常展示数据的,原因是Java早已考虑到这个问题,并提出了使用
会话技术(Cookie、Session)
来解决这个问题。具体如何来做,我们后面会详细讲到。刚才提到HTTP协议是规定了请求和响应数据的格式,那具体的格式是什么呢?
3.2 请求数据格式
3.2.1 格式介绍
请求数据总共分为三部分内容,分别是请求行、请求头、请求体
-
请求行: HTTP请求中的第一行数据,请求行包含三块内容,分别是 GET[请求方式] /[请求URL路径] HTTP/1.1[HTTP协议及版本]
请求方式有七种,最常用的是GET和POST
-
请求头: 第二行开始,格式为key: value形式
请求头中会包含若干个属性,常见的HTTP请求头有:
Host: 表示请求的主机名 User-Agent: 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0 ...Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...)like Gecko; Accept:表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有; Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页; Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate等。
这些数据有什么用处?
举例说明:服务端可以根据请求头中的内容来获取客户端的相关信息,有了这些信息服务端就可以处理不同的业务需求,比如:
- 不同浏览器解析HTML和CSS标签的结果会有不一致,所以就会导致相同的代码在不同的浏览器会出现不同的效果
- 服务端根据客户端请求头中的数据获取到客户端的浏览器类型,就可以根据不同的浏览器设置不同的代码来达到一致的效果
- 这就是我们常说的浏览器兼容问题
-
请求体: POST请求的最后一部分,存储请求参数
如上图红线框的内容就是请求体的内容,请求体和请求头之间是有一个空行隔开。此时浏览器发送的是POST请求,为什么不能使用GET呢?这时就需要回顾GET和POST两个请求之间的区别了:
- GET请求请求参数在请求行中,没有请求体,POST请求请求参数在请求体中
- GET请求请求参数大小有限制,POST没有
3.2.2 实例演示
把 代码\http
拷贝到IDEA的工作目录中,比如D:\workspace\web
目录,
使用IDEA打开
打开后,可以点击项目中的html\19-表单验证.html
,使用浏览器打开,通过修改页面中form表单的method属性来测试GET请求和POST请求的参数携带方式。
小结:
-
请求数据中包含三部分内容,分别是请求行、请求头和请求体
-
POST请求数据在请求体中,GET请求数据在请求行上
3.3 响应数据格式
3.3.1 格式介绍
响应数据总共分为三部分内容,分别是响应行、响应头、响应体
-
响应行:响应数据的第一行,响应行包含三块内容,分别是 HTTP/1.1[HTTP协议及版本] 200[响应状态码] ok[状态码的描述]
-
响应头:第二行开始,格式为key:value形式
响应头中会包含若干个属性,常见的HTTP响应头有:
Content-Type:表示该响应内容的类型,例如text/html,image/jpeg; Content-Length:表示该响应内容的长度(字节数); Content-Encoding:表示该响应压缩算法,例如gzip; Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒
-
响应体: 最后一部分。存放响应数据
上图中…这部分内容就是响应体,它和响应头之间有一个空行隔开。
3.3.2 响应状态码
参考: 资料/1.HTTP/《响应状态码.md》
关于响应状态码,我们先主要认识三个状态码,其余的等后期用到了再去掌握:
- 200 ok 客户端请求成功
- 404 Not Found 请求资源不存在
- 500 Internal Server Error 服务端发生不可预期的错误
小结
-
响应数据中包含三部分内容,分别是响应行、响应头和响应体
-
掌握200,404,500这三个响应状态码所代表含义,分布是成功、所访问资源不存在和服务的错误
4 常见异常
访问Servlet浏览器展示500异常
- 现象:访问Servlet浏览器展示500异常
- 原因:在pom.xml中导入javax.servlet-api没有指定scope或者scope的取值不是provided,例如:
<!--添加servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
- 解决:导入javax.servlet-api指定scope
<!--添加servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
启动tomcat运行项目报错,启动失败了
- 现象:启动tomcat运行项目报错,启动失败了
- 原因:原因比较多,重点需要大家掌握怎么看异常信息,此处发现是Servlet访问路径写错了,检查@WebServlet()注解中定义的Servlet访问路径。
![image-20220210155605640](../assets/images/image-20220210155605640.png)
其他的异常信息(ExecutionException、LifecycleException)不用看,都是垃圾
- 解决:修改SservletDemo01的访问路径,把缺失的/写上去。路径要符合精确匹配、目录匹配、扩展名匹配。
![image-20220210155628738](../assets/images/image-20220210155628738.png)
启动tomcat运行项目报错,启动失败了
- 现象:启动tomcat运行项目报错,启动失败了
- 原因:还是按照上面的方式找异常信息,发现是两个Servlet有相同的访问路径。
- 解决:修改其中一个Servlet的访问路径,保证Servlet访问路径不同就行了。一个访问路径只能对应一个Servlet。
day09-Request&Response
1、request
1.1、request的继承体系
1. 注意
RequestFacade是HttpServletRequest接口的实现类,由tomcat去实现。
请求的信息都封装在这个RequestFacade实现类中。
1.2、Request 获取请求数据
1.2.1、请求行
String getMethod():获取请求方式: GET
String getContextPath():获取虚拟目录(项目访问路径): /day09-request-response
StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/day09-requesresponse/requestDemo01
String getRequestURI():获取URI(统一资源标识符): /day09-request-response/requestDemo1
String getQueryString():获取请求参数(GET方式): username=zhangsan&password=123
- 注意
- String getQueryString() 是针对get请求,只有get请求的请求参数在请求行中,post 请求的参数在请求体重
1.2.2、请求头
String getHeader(String name):根据请求头名称,获取值
1.2.3、请求体【post】
ServletInputStream getInputStream():获取字节输入流
BufferedReader getReader():获取字符输入流
- 注意
- 此处获取的是请求体。只针对post请求。因为get请求没有请求体。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求方法
String method = req.getMethod();
System.out.println("请求的方法是:"+ method);
// 获取请求全路径
StringBuffer requestURL = req.getRequestURL();
System.out.println("请求的全路径是:" + requestURL);
// 请求的简单路径是
String requestURI = req.getRequestURI();
System.out.println("请求的简单路径是:" + requestURI);
// 请求的项目虚拟路径是
String contextPath = req.getContextPath();
System.out.println("请求的项目虚拟路径是" + contextPath);
// get的请求参数是
String queryString = req.getQueryString();
System.out.println("get的请求参数是:" + queryString);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求头,
String header = request.getHeader("user-agent");
System.out.println("您的浏览器版本是:" + header);
// 获取请求体,针对post请求
BufferedReader reader = request.getReader();
String s = reader.readLine();
System.out.println("请求体:"+ s);
}
1.3、通用方式获取请求参数
请求参数 : username=admin&password=123&hobby=dance&hobby=rap&hobby=basketball
后台接收到参数
1. 使用 & 进行切割
username=admin password=123 hobby=dance hobby=rap hobby=basketball
2. 使用 = 进行切割
username admin
password 123
hobby dance
hobby rap
hobby basketball
----------------------------------------以上做法显然很麻烦----------------------------------------------
- tomcat 提供三个获取参数的方法
Map<String, String[ ]> getParameterMap():获取所有参数Map集合
String getParameter(String name):根据名称获取参数值(单个值)
String[ ] getParameterValues(String name) :根据名称获取参数值(数组)
示例
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1 Map<String, String[ ]> getParameterMap():获取所有参数Map集合
Map<String, String[]> map = request.getParameterMap();
Set<String> key = map.keySet();
for (String keys : key) {
String[] values = map.get(keys);
System.out.println(keys + "...." + Arrays.toString(values));
}
System.out.println("----------------------------------");
//2 String getParameter(String name):根据名称获取参数值(单个值)
System.out.println("----------------------------------");
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username = " + username);
System.out.println("password = " + password);
//3 String[ ] getParameterValues(String name) :根据名称获取参数值(数组)
String[] hobbies = request.getParameterValues("hobby");
System.out.println("Arrays.toString(hobbies) = " + Arrays.toString(hobbies));
}
1.4、中文乱码
-
Request 请求参数中文乱码处理-POST请求
-
乱码的原因
-
-
浏览器采用 utf-8 进行编码 tomcat服务器 采用 ISO-8859-1 进行解码 【驴唇不对马嘴】
-
-
解决
-
/** * 处理请求中文乱码问题-POST方式 * request.setCharacterEncoding("utf-8"); */
-
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置输入流中的编码 request.setCharacterEncoding("utf-8"); //String getParameter(String name):根据名称获取参数值(单个值) String username = request.getParameter("username"); System.out.println("username = " + username); }
-
-
-
Request 请求参数中文乱码处理-GET请求
-
进行反向推导
-
1. 浏览器发起请求 username = "张三" 浏览器采用 utf-8 进行编码 username = %E5%BC%A0%E4%B8%89 tomcat服务器 采用 ISO-8859-1 进行解码 username = å¼ ä¸ 2. 反向推导,2种方式 2.1、使用URLEncoder、URLDecoder String username = request.getParameter("username"); // 采用 ISO-8859-1 进行编码 String encode = URLEncoder.encode(username, "ISO-8859-1"); // %E5%BC%A0%E4%B8%89 // 采用 utf-8 进行解码 username = URLDecoder.decode(encode, "utf-8"); System.out.println("username = " + username); // 张三 2.2、可以使用String类的 getBytes() 进行推导 String username = request.getParameter("username"); // 采用 ISO-8859-1 进行编码, 得到字节数组 byte[] bytes = username.getBytes("ISO-8859-1"); // 采用 utf-8 进行解码 username = new String(bytes,"utf-8"); System.out.println("username = " + username); // 张三
-
-
-
如果使用的是tomcat8及以后版本的tomcat,GET请求中文不会乱码。
- 配置方式解决tomcat7 GET请求中文乱码问题【推荐使用】
1.5、请求转发
- 请求转发( forward ):一种在服务器内部的资源跳转方式
-
实现方式:request.getRequestDispatcher(“资源B路径”).forward(req,resp);
-
请求转发资源间共享数据:使用Request对象
void setAttribute(String name, Object value):存储数据到 request域中
Object getAttribute(String name):根据 key,获取值
void removeAttribute(String name):根据 key,删除该键值对
-
请求转发特点:
浏览器地址栏路径不发生变化
只能转发到当前服务器的内部资源
一次请求,可以在转发的资源间使用request共享数据
示例:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("RequestDemo06...");
//需求:浏览器访问requestDemo06,处理完请求之后转发的requestDemo07接着处理
// 设置共享数据
request.setAttribute("msg","没钱了");
//1 获取转发器,转发到目标 requestDemo07
RequestDispatcher msg = request.getRequestDispatcher("/requestDemo07");
//2 开始请求转发
msg.forward(request,response);
//优化代码:链式编程,简化书写
//request.getRequestDispatcher("/requestDemo07").forward(request,response);
}
2、response
- response是用来设置响应数据
2.1、response的继承体系
- HttpServletResponse接口的实现类ResponseFacade,是由 tomcat服务器自己去实现。
2.2、Response 设置响应数据功能介绍
1. 响应行
HTTP/1.1 200 OK
void setStatus(200)
2. 响应头
Content-Type:text/html
void setHeader(String name,String value) 键值对形式化
3. 响应体
PrintWriter.getWriter(); 获取字符输出流
ServletOutputStream getOutputStream() 获取字节输出流
2.3、Response 完成重定向
- 重定向(Redirect):一种资源跳转方式
- 实现方式:
response.setStatus(302);
response.setHeader("location", "资源B的路径");
简写
response.sendRedirect("资源B的路径");【推荐】
- 重定向特点:
- 浏览器地址栏路径发生变化
- 可以重定向到任意位置的资源(服务器内部、外部均可)
- 两次请求,不能在多个资源使用request共享数据
2.4、路径的问题
- 明确路径给谁使用?
- 浏览器使用:需要加虚拟目录(项目的访问路径)
- 服务端使用:不需要加虚拟目录
<a href = "/day09-request-response/responseDemo03" ></a> 加虚拟目录
<form action = "/day09-request-response/responseDemo03" ><form> 加虚拟目录
req.getRequestDispatcher("/responseDemo03") 不加虚拟目录
resp.sendRedirect("/day09-request-response/responseDemo03") 加虚拟目录
- 结论:目前除了请求转发不需要加虚拟目录,其他跳转都需要加虚拟目录
2.5、Response 响应字符数据
1. 通过Response对象获取字符输出流
PrintWriter pw = response.getWriter();
2. 写数据
pw.write(97);
pw.write 底层会把 97 转成字符 ----> a
pw.print(97);
pw.print 会把 97 直接转成字符串 ----> "97"
3. 链式编程【推荐】
response.getWriter().write(97);
- 注意
1. PrintWriter pw = response.getWriter();
pw 该流不需要关闭,随着响应的结束,response对象销毁,由服务器关闭
2. 中文乱码:原因通过response获取字符输出流,默认编码是 ISO-8859-1
response.setHeader("content-type","text/html;charset=utf-8");
或者
response.setContentType("text/html;charset=utf-8");【推荐】
2.6、Response 响应字节数据
1. 通过response对象获取字节输出流
ServletOutputStream outputStream = response.getOutputStream();
2. 写数据
outputStream.write(字节数据);
示例:响应给浏览器一张图片
//需求: 将D:/imgs中的某个图片读取到程序中,发送给客户端浏览器。
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1 使用字节输入流对象读取D:\imgs中的某个图片 FileInputStream
FileInputStream fis = new FileInputStream("C:\\Users\\26583\\Desktop\\imgs\\e.jpg");
//2 通过Response对象获取字节输出流对象 OutputStream
ServletOutputStream os = response.getOutputStream();
//3 两个流对接,一边读一边写,一次读写一个字节数组
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1){
// 写字节数据
os.write(bytes,0,len);
}
//4 释放资源'
fis.close();
}
- 使用 commons-io 简化字节的读写
导入坐标
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
使用
// 使用 commons-io 依赖,IOUtils工具类简化读写
IOUtils.copy(fis,os);
2.7、Response 响应字节数据-文件下载
- 做法:在响应数据之前告诉浏览器以附件的方式解析响应体中的内容即可
response.setHeader("content-disposition","attachment;filename=e.jpg");
示例
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 文件下载
response.setHeader("content-disposition","attachment;filename=e.jpg");
//需求: 将D:/imgs中的某个图片读取到程序中,发送给客户端浏览器。
//1 使用字节输入流对象读取D:\imgs中的某个图片 FileInputStream
FileInputStream fis = new FileInputStream("C:\\Users\\26583\\Desktop\\imgs\\e.jpg");
//2 通过Response对象获取字节输出流对象 OutputStream
ServletOutputStream os = response.getOutputStream();
//3 两个流对接,一边读一边写,一次读写一个字节数组
/* byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1){
// 写字节数据
os.write(bytes,0,len);
}*/
// 使用 commons-io 依赖,IOUtils工具类简化读写
IOUtils.copy(fis,os);
//4 释放资源'
fis.close();
}
案例
代码实现
1. 前端
<a href="/day09-request-response/responseDemo07?filename=a.jpg">美女1</a><br>
<a href="/day09-request-response/responseDemo07?filename=b.jpg">美女2</a><br>
<a href="/day09-request-response/responseDemo07?filename=c.jpg">美女3</a><br>
<a href="/day09-request-response/responseDemo07?filename=d.jpg">美女4</a><br>
<a href="/day09-request-response/responseDemo07?filename=e.jpg">美女5</a><br>
2. 后端
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 处理中文乱码
request.setCharacterEncoding("utf-8");
// 获取文件名
String filename = request.getParameter("filename");
// 使用FileInputStream读取文件
FileInputStream fis = new FileInputStream("C:\\Users\\26583\\Desktop\\imgs\\" + filename);
// 附件的方式解析响应
response.setHeader("content-disposition","attachment;filename=" + filename);
// 获取响应管道输出流
ServletOutputStream os = response.getOutputStream();
// 读写数据
IOUtils.copy(fis,os);
// 释放资源
fis.close();
}
- 如果文件名中含有中文,需要特殊处理 filename = URLEncoder.encode(filename,“utf-8”);
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取文件名
String filename = request.getParameter("filename");
// 使用FileInputStream读取文件
FileInputStream fis = new FileInputStream("C:\\Users\\26583\\Desktop\\imgs\\" + filename);
// 处理文件名为中文名,进行编码
filename = URLEncoder.encode(filename,"utf-8");
// 附件的方式解析响应
response.setHeader("content-disposition","attachment;filename=" + filename);
// 获取响应管道输出流
ServletOutputStream os = response.getOutputStream();
// 读写数据
IOUtils.copy(fis,os);
// 释放资源
fis.close();
}
day10-jsp
1、概念
1. Java Server Pages , Java服务端页面
2. 一种动态的网页技术,其中可以定义HTML,JS,CSS 等静态内容,还可以定义 Java代码等动态内容
3. JSP = HTML + JAVA
4. JSP的作用:简化开发,避免在Servlet 中直接输出HTML标签
5. JSP 的本质是 Servlet
2、jsp快速入门
- 导入依赖
<dependencies>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!--添加maven tomcat7 插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
</configuration>
</plugin>
</plugins>
</build>
- 创建 JSP文件,并简单编写输出语句
<%--
Created by IntelliJ IDEA.
User: 26583
Date: 2022/7/30
Time: 21:21
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>这是我的第一个jsp页面</h1>
<%
System.out.println("hello jsp~");
%>
</body>
</html>
3、jsp原理【重点】
- JSP 在被访问的时候,由 JSP容器 Tomcat 将其转为 Java 文件 (Servlet),再由 JSP 容器(Tomcat) 将其编译,最终对外提供服务的其实是字节码文件。
- JSP 本质上就是一个 Servlet
4、jsp脚本
- JSP 脚本用于在 JSP 页面内定义 JAVA 代码
1. <% %>
内容会直接放到 _jspService()方法中,属于局部代码
2. <%= %>
内容会放到 out.print()方法中,作为 out.print()的参数,输出到浏览器的页面上
3. <%! %>
内容会放到_jspService()方法之外,被类直接包含,属于成员代码
-
<% %>
<body> <% System.out.println("hello jsp~"); // 控制台输出 hello jsp~ int num = 10; String name = "张益达"; %> </body>
- 控制台输出
-
<%= %>
<body> <%="这个杀手不太冷"%> </body>
- 浏览器输出
-
<%! %>
<body>
<%!
private int id;
private String name;
private double price;
private boolean flag;
public void show(){
System.out.println("show.........");
}
%>
</body>
5、EL表达式【重点】
- JSP 缺点
- 书写麻烦:特别是复杂的页面
阅读麻烦
复杂度高:运行需要依赖于各种环境,JRE,JSP容器,JavaEE…
占内存和磁盘:JSP会自动生成.java和.class文件占磁盘,运行的是.class文件占内存
调试困难:出错后,需要找到自动生成的.java文件进行调试
不利于团队协作:前端人员不会 Java,后端人员不精 HTML
- 书写麻烦:特别是复杂的页面
1. EL (Expression Lanuage) 表达式语言,用于简化JSP页面的java代码
2. EL 表达式的主要作用,就是用来获取数据的。从域对象中获取数据,然后将数据展示在页面上。
3. EL 表达式的语法 ${expression} 例如:${brands} 就是获取域中存储的key 为 brands的数据
-
示例
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 演示从jsp的EL表达式 List brands = new ArrayList(); brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1)); brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0)); brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1)); // 存储到域中 request.setAttribute("brands",brands); // 转发到 hello2.jsp页面 request.getRequestDispatcher("/hello2.jsp").forward(request,response); }
<body> <%--使用EL表达式,从域对象中获取brands数据--%> ${brands} </body>
-
在Servlet中使用java代码获取数据存储到域对象中,转发到jsp页面使用EL表达式获取数据展示。
-
JavaWeb中的四大域对象:
-
page:当前页面有效
-
request:当前请求有效
-
session:当前会话有效
-
application:当前应用有效
-
-
el表达式获取数据,会依次从这4个域中寻找,直到找到为止
6、JSTL标签
- JSP标准标签库(Jsp Standarded Tag Library), 使用标签取代 JSP 页面上的java代码
6.1、快速入门
-
导入坐标
<dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
-
在JSP页面上引入JSTL标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-
使用: <c:if test=“表达式”>,相当于 if 判断
<c:if test="${brand[0].status==1} "> 启用 </c:if> <c:if test="${brand[0].status!=1}"> 禁用 </c:if>
6.2 if 标签
<c:if>
:相当于 if 判断
- 属性:test,用于定义条件表达式
<c:if test="${flag == 1}">
男
</c:if>
<c:if test="${flag == 2}">
女
</c:if>
代码演示:
-
定义一个
servlet
,在该servlet
中向 request 域对象中添加 键是status
,值为1
的数据@WebServlet("/demo2") public class ServletDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 存储数据到request域中 request.setAttribute("status",1); //2. 转发到 jstl-if.jsp 数据request.getRequestDispatcher("/jstl-if.jsp").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
定义
jstl-if.jsp
页面,在该页面使用<c:if>
标签<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> </head> <body> <%-- c:if:来完成逻辑判断,替换java if else --%> <c:if test="${status ==1}"> 启用 </c:if> <c:if test="${status ==0}"> 禁用 </c:if> </body> </html>
注意: 在该页面已经要引入 JSTL核心标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
6.3 forEach 标签
<c:forEach>
:相当于 for 循环。java中有增强for循环和普通for循环,JSTL 中的 <c:forEach>
也有两种用法
6.3.1 用法一
类似于 Java 中的增强for循环。涉及到的 <c:forEach>
中的属性如下
-
items:被遍历的容器
-
var:遍历产生的临时变量
-
varStatus:遍历状态对象
如下代码,是从域对象中获取名为 brands 数据,该数据是一个集合;遍历遍历,并给该集合中的每一个元素起名为 brand
,是 Brand对象。在循环里面使用 EL表达式获取每一个Brand对象的属性值
<c:forEach items="${brands}" var="brand">
<tr align="center">
<td>${brand.id}</td>
<td>${brand.brandName}</td>
<td>${brand.companyName}</td>
<td>${brand.description}</td>
</tr>
</c:forEach>
6.3.2 用法二
类似于 Java 中的普通for循环。涉及到的 <c:forEach>
中的属性如下
-
begin:开始数
-
end:结束数
-
step:步长
实例代码:
从0循环到10,变量名是 i
,每次自增1
<c:forEach begin="0" end="10" step="1" var="i">
${i}
</c:forEach>
7、MVC和3层架构【重点】
7.1、MVC模式
MVCA是一种分层开发的模式
M : Model,业务模型,处理业务
V : View, 视图,界面展示
C : Controller, 控制器,处理请求,调用模型和视图
- MVC 分层开发的好处
- 职责单一,互不影响
- 有利于分工协作
- 有利于组件重用
7.2、3层架构
1. 数据访问层 : 对数据库的CURD的基本操作
2. 业务逻辑层 : 对业务逻辑进行封装,组合【数据访问层】中基本功能,处理复杂的业务逻辑
3. 表现层 : 接收请求,封装数据,调用【业务逻辑层】,响应数据。
day11、会话跟踪技术
概念
1. 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到浏览器断开连接,会话结束。在一次会话中,可以包含多次请求和响应。
2. 会话跟踪 : 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一个浏览器,以便在同一次会话的多次请求间共享数据。
3. 客户端会话跟踪技术 : Cookie
服务端会话跟踪 : Session
1、Cookie 基本使用
-
Cookie : 客户端会话技术,将数据保存到客户端,以后每次请求都携带 Cookie数据进行访问
-
1. 发送 Cookie 创建 Cookie 对象,设置数据 Cookie cookie = new Cookie("key","value"); 发送 Cookie 到客户端 : 使用 response 对象 response.addCookie(cookie) 2. 获取 Cookie 获取客户端携带的所有 Cookie ,使用 request 对象获取 Cookie [] cookies = request.getCookies(); 遍历 cookies 数组,获取每一个 Cookie 对象 : for 循环 for(Cookie cookie : cookies){ 使用 Cookie 对象调用方法获取数据 cookie.getName(); cookie.getValue(); }
2、Cookie 原理
1. Cookie 是基于 HTTP 协议中请求头和响应头实现的
* 响应头 : set-cookie
* 请求头 : cookie
Cookie的实现原理:
当我们访问服务器端资源时,在服务端可以创建Cookie对象保存数据。
服务器通过set-cookie响应头把Cookie发送给浏览器保存起来。
当浏览器再次访问该项目资源时,浏览器会自动将属于该项目的Cookie通过cookie请求头发送给项目,
那么在项目中可以获取到之前的Cookie从而实现数据共享。
3、Cookie 使用细节
- cookie 的存活时间
1. 默认情况下,cookie 存储在浏览器内存中,当浏览器关闭,内存释放,Cookie 被销毁
setMaxAge(int seconds) : 设置Cookie 存活时间
正数 : 将 Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除
cookie.setMaxAge(60*60*24*7); 存储 1 周
负数 : 默认值,Cookie在当前浏览器内存中,当浏览器关闭,则 Cookie被销毁
cookie.setMaxAge(-1);
0 : 删除对应 Cookie 。 浏览器会接收到 Cookie , 但是下次不会请求
- Cookie 存储中文
Cookie 不能直接存储空格和中文
如需要存储,则需要进行转码:URL编码
获取Cookie值的时候进行URL解码
4、Session 基本使用
- 服务端会话跟踪技术:将数据保存到服务端
- JavaEE 提供 HttpSession接口,来实现一次会话的多次请求间数据共享功能
1. 获取Session 对象
HttpSession session = request.getSession();
2. Session对象功能
void setAttribute(String name,Object obj); // 存储数据到sessio域中
Object getAttribute(String name); // 根据key ,获取值
void removeAttribute(String name); // 根据key,删除该键值对
5、Session 原理
- Session是基于Cookie实现的
Session的实现原理:
当客户端浏览器访问服务器资源,在资源中通过request.getSession()方法获取到的Session有一个唯一id。
服务器会将session的唯一id保存到Cookie中发送给浏览器存起来。
当浏览器再次访问服务器资源时,会将唯一的sessionid携带过来,
服务器端再次通过request.getSession()底层会自动根据传递过来的的sessionid获取之前的session对象,
从而实现数据共享。
所以简单的说session是基于Cookie实现的。
6、Session 使用细节
- Session 钝化、活化:
1. 钝化:在服务器正常关闭后, Tomcat会自动将 Session数据写入硬盘的文件中
2. 活化:再次启动服务器后,从文件中加载数据到Session中
- Seesion 销毁
1. 默认情况下,无操作,30分钟自动销毁
<session-config>
<session-timeout>30</session-timeout>
</session-config>
2. 立即销毁
session.invalidate();
7、总结
1. 相同点:Cookie 和 Session 都是来完成一次会话内多次请求间数据共享的
2. 区别:
存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
安全性:Cookie 不安全,Session 安全
数据大小:Cookie 有大小限制(4kb),Session 无大小限制
数据类型:Cookie只能存储字符串,Session 可以存储任意类型数据
服务器性能:Cookie 不占服务器资源,Session 占用服务器资源
3. cookie和session的选择问题
cookie :
少量的,安全性要求不高,字符串类型的数据适合
session:
大量的,安全性相对要求较高的数据适合session
4. request 和 session的选择问题
一次请求间数据共享使用request
多次请求间数据共享使用session
8、登录和注册案例
8.1、登录
- 勾选记住用户名和密码,下次可以直接登录
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 处理中文乱码
request.setCharacterEncoding("utf-8");
// 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
// 转码
String usernameEncode = URLEncoder.encode(username, "utf-8");
String passwordEncode = URLEncoder.encode(password, "utf-8");
// 调用业务,查询用户
User user = userService.login(username,password);
// 判断用户是否存在
if (user != null){
// 把用户名和密码保存到cookie中,
Cookie usernameCookie = new Cookie("username",usernameEncode);
Cookie passwordCookie = new Cookie("password",passwordEncode);
// cookie的存活时间,设置 0 即只有这次请求响应回浏览器,下次不带cookie请求。
// 记住密码复选框,
String remember = request.getParameter("remember");
if ("yes".equals(remember)){// 用户勾选记住用户名和密码
// 设置cookie存活时间为1周
usernameCookie.setMaxAge(60*60*24*7);
passwordCookie.setMaxAge(60*60*24*7);
}else {// 用户没有勾选
// 设置cookie存活时间为 0
usernameCookie.setMaxAge(0);
passwordCookie.setMaxAge(0);
}
// 响应 cookie
response.addCookie(usernameCookie);
response.addCookie(passwordCookie);
// 保存用户
request.getSession().setAttribute("user",user);
// 用户名存在,重定向到查询所有商品的servlet 处理,
response.sendRedirect(request.getContextPath()+"/selectAllServlet");
}else {
// 用户名不存在
// 转发到login.jsp,封装错误信息返回
request.setAttribute("login_msg","用户名或密码错误!");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
<body>
<%
String username = null;
String password = null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())){
username = cookie.getValue();
// 进行解码
username = URLDecoder.decode(username,"utf-8");
}
if ("password".equals(cookie.getName())){
password = cookie.getValue();
// 进行解码
password = URLDecoder.decode(password,"utf-8");
}
}
%>
<div id="loginDiv" style="height: 350px">
<form action="/day11-brand-demo/loginServlet" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
<p>Username:<input id="username" name="username" type="text" value="<%=username!=null?username:""%>"></p>
<p>Password:<input id="password" name="password" type="password" value="<%=password!=null?password:""%>"></p>
<p>Remember:<input id="remember" name="remember" type="checkbox" value="yes" <%=username != null?"checked":""%>></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.jsp">没有账号?</a>
</div>
</form>
</div>
</body>
-
思路分析
-
用户进行的登录操作
-
登录成功
把用户名和密码存储到 cookie中
- 勾选复选框,记住用户名和密码
- cookie 保存时长设置 7天
- 没有勾选复选框
- cookie 保存时长设置 0 ,即不保存
- 勾选复选框,记住用户名和密码
-
-
8.2、注册
-
注册的步骤
- 先校验验证码
- 【校验验证码正确】获取到的用户名,先去数据库根据用户名查询,用户是否已经存在
- 获取到结果
- 用户已经存在
- 给出提示,不能注册
- 用户不存在
- 可进行注册
- 用户已经存在
-
生成随机验证码工具类 CheckCodeUtil
- 验证码的作用:
- 防止机器自动注册,攻击服务器
/** * 生成验证码工具类 */ public class CheckCodeUtil { public static final String VERIFY_CODES = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static Random random = new Random(); public static void main(String[] args) throws IOException { FileOutputStream fos=new FileOutputStream("d:/abc.jpg"); outputVerifyImage(100,50,fos,4); } /** * 输出随机验证码图片流,并返回验证码值(一般传入输出流,响应response页面端,Web项目用的较多) */ public static String outputVerifyImage(int width, int height, OutputStream os, int verifySize) throws IOException { //生成验证码内容:p5sv String verifyCode = generateVerifyCode(verifySize); //在内存中生成一张图片,将验证码内容写到图片中,并绘制干扰线 outputImage(width, height, os, verifyCode); return verifyCode; } /** * 使用系统默认字符源生成验证码 * * @param verifySize 验证码长度 * @return */ public static String generateVerifyCode(int verifySize) { return generateVerifyCode(verifySize, VERIFY_CODES); } /** * 使用指定源生成验证码 * * @param verifySize 验证码长度 * @param sources 验证码字符源 * @return */ public static String generateVerifyCode(int verifySize, String sources) { // 未设定展示源的字码,赋默认值大写字母+数字 if (sources == null || sources.length() == 0) { sources = VERIFY_CODES; } int codesLen = sources.length(); Random rand = new Random(System.currentTimeMillis()); StringBuilder verifyCode = new StringBuilder(verifySize); for (int i = 0; i < verifySize; i++) { verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1))); } return verifyCode.toString(); } /** * 生成随机验证码文件,并返回验证码值 (生成图片形式,用的较少) * * @param w * @param h * @param outputFile * @param verifySize * @return * @throws IOException */ public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException { String verifyCode = generateVerifyCode(verifySize); outputImage(w, h, outputFile, verifyCode); return verifyCode; } /** * 生成指定验证码图像文件 * * @param w * @param h * @param outputFile * @param code * @throws IOException */ public static void outputImage(int w, int h, File outputFile, String code) throws IOException { if (outputFile == null) { return; } File dir = outputFile.getParentFile(); //文件不存在 if (!dir.exists()) { //创建 dir.mkdirs(); } try { outputFile.createNewFile(); FileOutputStream fos = new FileOutputStream(outputFile); outputImage(w, h, fos, code); fos.close(); } catch (IOException e) { throw e; } } /** * 输出指定验证码图片流 * * @param w * @param h * @param os * @param code * @throws IOException */ public static void outputImage(int w, int h, OutputStream os, String code) throws IOException { int verifySize = code.length(); BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Random rand = new Random(); Graphics2D g2 = image.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 创建颜色集合,使用java.awt包下的类 Color[] colors = new Color[5]; Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.YELLOW}; float[] fractions = new float[colors.length]; for (int i = 0; i < colors.length; i++) { colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)]; fractions[i] = rand.nextFloat(); } Arrays.sort(fractions); // 设置边框色 g2.setColor(Color.GRAY); g2.fillRect(0, 0, w, h); Color c = getRandColor(200, 250); // 设置背景色 g2.setColor(c); g2.fillRect(0, 2, w, h - 4); // 绘制干扰线 Random random = new Random(); // 设置线条的颜色 g2.setColor(getRandColor(160, 200)); for (int i = 0; i < 20; i++) { int x = random.nextInt(w - 1); int y = random.nextInt(h - 1); int xl = random.nextInt(6) + 1; int yl = random.nextInt(12) + 1; g2.drawLine(x, y, x + xl + 40, y + yl + 20); } // 添加噪点 // 噪声率 float yawpRate = 0.05f; int area = (int) (yawpRate * w * h); for (int i = 0; i < area; i++) { int x = random.nextInt(w); int y = random.nextInt(h); // 获取随机颜色 int rgb = getRandomIntColor(); image.setRGB(x, y, rgb); } // 添加图片扭曲 shear(g2, w, h, c); g2.setColor(getRandColor(100, 160)); int fontSize = h - 4; Font font = new Font("Algerian", Font.ITALIC, fontSize); g2.setFont(font); char[] chars = code.toCharArray(); for (int i = 0; i < verifySize; i++) { AffineTransform affine = new AffineTransform(); affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2); g2.setTransform(affine); g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10); } g2.dispose(); ImageIO.write(image, "jpg", os); } /** * 随机颜色 * * @param fc * @param bc * @return */ private static Color getRandColor(int fc, int bc) { if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } private static int getRandomIntColor() { int[] rgb = getRandomRgb(); int color = 0; for (int c : rgb) { color = color << 8; color = color | c; } return color; } private static int[] getRandomRgb() { int[] rgb = new int[3]; for (int i = 0; i < 3; i++) { rgb[i] = random.nextInt(255); } return rgb; } private static void shear(Graphics g, int w1, int h1, Color color) { shearX(g, w1, h1, color); shearY(g, w1, h1, color); } private static void shearX(Graphics g, int w1, int h1, Color color) { int period = random.nextInt(2); boolean borderGap = true; int frames = 1; int phase = random.nextInt(2); for (int i = 0; i < h1; i++) { double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); g.copyArea(0, i, w1, 1, (int) d, 0); if (borderGap) { g.setColor(color); g.drawLine((int) d, i, 0, i); g.drawLine((int) d + w1, i, w1, i); } } } private static void shearY(Graphics g, int w1, int h1, Color color) { int period = random.nextInt(40) + 10; // 50; boolean borderGap = true; int frames = 20; int phase = 7; for (int i = 0; i < w1; i++) { double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); g.copyArea(i, 0, 1, h1, 0, (int) d); if (borderGap) { g.setColor(color); g.drawLine(i, (int) d, i, 0); g.drawLine(i, (int) d + h1, i, h1); } } } }
- 验证码的作用:
-
把随机生成的验证码存的 session域中
// 获取生成验证码的工具类
String code = CheckCodeUtil.outputVerifyImage(100,50,response.getOutputStream(), 4);
// 存入Session
HttpSession session = request.getSession();
session.setAttribute("checkCode",code);
- 前端 html 使用 标签进行展现验证码图片
<img src="/day11-brand-demo/checkCodeServlet" id="checkCodeImg" onclick="changeImage()">
- 由于 img 标签只能展示静态资源,当点击到该图片验证的时候就需要更换 它的 src 路径,
<script>
function changeImage() {
//修改image标签的src属性
document.querySelector("#checkCodeImg").src="/day11-brand-demo/checkCodeServlet?"+new Date().getTime()
}
</script>
- 解释
document.querySelector("#checkCodeImg").src="/day11-brand-demo/checkCodeServlet?"+new Date().getTime()
1. 由于浏览器认为获取的静态资源,会使用历史缓存,
src="/day11-brand-demo/checkCodeServlet?"+new Date().getTime()
new Date().getTime() 拼接上时间戳,浏览器解析会认为是动态变化的资源,会重新解析。没有实际含义。
day12、Filter&Listener&Ajax&axios&json
1、Filter
- 概念:Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
- 作用:把对资源的请求和响应拦截下来,从而实现一些特殊的功能。
- 过滤器一般完成一些通用的操作,比如:权限控制、统一编码处理、敏感字符处理等等…
1.1、Filter 执行流程
-
-
接收到请求-------------> 执行放行前的逻辑 ----> 放行 -----------> 访问资源 --------> 执行放行后的逻辑------------>响应请求
1.2、Filter 拦截路径配置
- Filter 可以根据需求,配置不同的拦截资源路径
- 拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截。
- 目录拦截:/user/:访问/user下的所有资源,都会被拦截, /:表示拦截访问的所有资源
- 后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
1.3、过滤器链
-
一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链
-
注解配置的Filter,优先级按照过滤器类名(字符串)的自然排序
14、拦截类型
dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD} :拦截类型
* DispatcherType.REQUEST:拦截浏览器发送的请求
* DispatcherType.FORWARD:拦截转发的请求
package javax.servlet;
public enum DispatcherType {
FORWARD,
INCLUDE,
REQUEST,
ASYNC,
ERROR;
private DispatcherType() {
}
}
2、Listener
-
概念:Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
-
监听器可以监听就是在application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件
-
Listener分类:JavaWeb中提供了8个监听器
-
-
ServletContextListener 使用
-
定义类,实现ServletContextListener接口
public class ServletContextListenerDemo implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // 初始化操作,比如一些资源的加载 System.out.println("contextInitialized...."); } @Override public void contextDestroyed(ServletContextEvent sce) { // 销毁 } }
-
在类上添加@WebListener 注解
@WebListener public class ServletContextListenerDemo implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // 初始化操作,比如一些资源的加载 System.out.println("contextInitialized...."); } @Override public void contextDestroyed(ServletContextEvent sce) { // 销毁 } }
-
3、Ajax
-
Ajax 的全称是 Asynchronous Javascript And XML(异步 JavaScript 和 XML)
- 通俗的理解:在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式,就是Ajax。
-
Ajax的典型应用场景
- **数据分页显示:**当点击页码值的时候,通过 ajax 的形式,根据页码值动态刷新表格的数据
3.1、XMLHttpRequest【了解】
- XMLHttpRequest(简称 xhr)是浏览器提供的 Javascript 对象,通过它,可以请求服务器上的数据资源。
- 使用XMLHttpRequest发起GET请求
- 创建 XMLHttpRequest对象
- 调用 XMLHttpRequest.open() 函数
- 调用 XMLHttpRequest.send() 函数
- 监听XMLHttpRequest.onreadystatechange 事件
// 1. 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open 函数,指定 请求方式 与 URL地址
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
// 3. 调用 send 函数,发起 Ajax 请求
xhr.send()
// 4. 监听 onreadystatechange 事件
xhr.onreadystatechange = function() {
// 4.1 监听 xhr 对象的请求状态 readyState ;与服务器响应的状态 status
if (xhr.readyState === 4 && xhr.status === 200) {
// 4.2 打印服务器响应回来的数据
console.log(xhr.responseText)
}
}
3.2、jQuery中的Ajax
- jQuery 中发起 Ajax 请求最常用的三个方法如下:
- $.get()
- $.post()
- $.ajax()
3.2.1、**$.get()**函数的语法
$.get(url, [data], [callback]);
参数名 | 参数类型 | 是否必选 | 说明 |
---|---|---|---|
url | string | 是 | 要请求的资源地址 |
data | object | 否 | 请求资源期间要携带的参数 |
callback | function | 否 | 请求成功时的回调函数 |
-
**$.get()**发起不带参数的请求
-
使用 $.get() 函数发起不带参数的请求时,直接提供请求的 URL 地址和请求成功之后的回调函数
-
$.get('http://www.liulongbin.top:3006/api/getbooks', function(res) { console.log(res) // 这里的 res 是服务器返回的数据 })
-
-
**$.get()**发起带参数的请求
-
$.get('http://www.liulongbin.top:3006/api/getbooks', { id: 1 }, function(res) { console.log(res) })
-
3.2.2、$.post()函数的语法
$.post(url, [data], [callback])
参数名 | 参数类型 | 是否必选 | 说明 |
---|---|---|---|
url | string | 是 | 提交数据的地址 |
data | object | 否 | 要提交的数据 |
callback | function | 否 | 数据提交成功时的回调函数 |
$.post(
'http://www.liulongbin.top:3006/api/addbook', // 请求的URL地址
{ bookname: '水浒传', author: '施耐庵', publisher: '上海图书出版社' }, // 提交的数据
function(res) { // 回调函数
console.log(res)
}
)
3.2.3、$.ajax()函数的语法
$.ajax({
type: '', // 请求的方式,例如 GET 或 POST
url: '', // 请求的 URL 地址
data: { },// 这次请求要携带的数据
success: function(res) { } // 请求成功之后的回调函数
})
- 使用$.ajax()发起GET请求
$.ajax({
type: 'GET', // 请求的方式
url: 'http://www.liulongbin.top:3006/api/getbooks', // 请求的 URL 地址
data: { id: 1 },// 这次请求要携带的数据
success: function(res) { // 请求成功之后的回调函数
console.log(res)
}
})
- 使用 $.ajax() 发起 POST 请求
$.ajax({
type: 'POST', // 请求的方式
url: 'http://www.liulongbin.top:3006/api/addbook', // 请求的 URL 地址
data: { // 要提交给服务器的数据
bookname: '水浒传',
author: '施耐庵',
publisher: '上海图书出版社'
},
success: function(res) { // 请求成功之后的回调函数
console.log(res)
}
})
4、axios
- Axios 是专注于网络数据请求的库
- 相比于原生的 XMLHttpRequest 对象,axios 简单易用。
- 相比于 jQuery,axios 更加轻量化,只专注于网络数据请求。
- 使用前,需要导入axios的js
4.1、axios发起GET请求
axios.get('url', { params: { /*参数*/ } }).then(callback)
// 请求的 URL 地址
var url = 'http://www.liulongbin.top:3006/api/get'
// 请求的参数对象
var paramsObj = { name: 'zs', age: 20 }
// 调用 axios.get() 发起 GET 请求
axios.get(url, { params: paramsObj }).then(function(res) {
// res.data 是服务器返回的数据
var result = res.data
console.log(res)
})
4.2、axios发起POST请求
axios.post('url', { /*参数*/ }).then(callback)
// 请求的 URL 地址
var url = 'http://www.liulongbin.top:3006/api/post'
// 要提交到服务器的数据
var dataObj = { location: '北京', address: '顺义' }
// 调用 axios.post() 发起 POST 请求
axios.post(url, dataObj).then(function(res) {
// res.data 是服务器返回的数据
var result = res.data
console.log(result)
})
4.3、直接使用axios发起请求
axios({
method: '请求类型',
url: '请求的URL地址',
data: { /* POST数据 */ },
params: { /* GET参数 */ }
}) .then(callback)
4.4、直接使用axios发起GET请求
axios({
method: 'GET',
url: 'http://www.liulongbin.top:3006/api/get',
params: { // GET 参数要通过 params 属性提供
name: 'zs',
age: 20
}
}).then(function(res) {
console.log(res.data)
})
4.5、直接使用axios发起POST请求
axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/post',
data: { // POST 数据要通过 data 属性提供
bookname: '程序员的自我修养',
price: 666
}
}).then(function(res) {
console.log(res.data)
})
5、json
- 概念:javascript object notation javascript对象表示法
- 由于其语法简单,层次结构鲜明,现多用于数据载体,在网络中进行数据传输
5.1、JSON 基础语法
let 变量名 = '{"key1":value1,"key2" : value2,... }';
let 变量名= '[value1, value2, value3, value4,…]';
- JSON字符串转为JS对象:
var jsObject = JSON.parse(jsonStr);
- JS对象转为JSON字符串:
let jsonObject = JSON.stringify(jsObject);
- Axios中,JSON字符串和JS对象自动进行转换
5.2、Java对象转为JSON字符串
- 响应数据:
1. 导入坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
2. 创建ObjectMapper对象
ObjectMapper mapper = new ObjectMapper();
3. Java对象转JSON字符串
String jsonStr = mapper.writeValueAsString(obj);
4. 响应数据
response.getWriter().write(jsonStr);
------------------------------------------------------------
对第 3 和 第 4 步进行简化
Java对象转JSON并通过writer字符流输出 mapper.writeValue(writer,obj);
mapper.writeValue(response.getWriter(),obj);
5.3、json字符串转java对象
- JSON字符串转Java**对象
类名 对象名 = mapper.readValue (jsonStr, 类名.class);
String json = request.getReader().readLine();
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(json,User.class);
- 将reader字符流中的JSON字符串转Java对象
类名 对象名 = mapper.readValue (reader, 类名.class);
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(request.getReader(),User.class);
注意:请求体中的json数据使用request.getParameter()等方法无法获取
-
使用axios发起请求
<button id="send">发送请求</button> <script src="assets/js/axios-0.18.0.js"></script> <script > document.querySelector("#send").onclick = function (){ axios({ method:"POST", url:"jacksonServlet", data:{"name":"张益达","age":18,"address":"北京"}, }).then(res=>{ console.log(res); }); } </script>
-
servlet响应请求
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 响应数据的格式 response.setContentType("application/json;charset=utf-8"); // 创建 ObjectMapper 对象 ObjectMapper objectMapper = new ObjectMapper(); // JSON字符串转JAVA对象 User user2 = objectMapper.readValue(request.getReader(),User.class); User user = new User(); user.setName("张益达"); user.setAddress("北京"); user.setAge(18); // 响应客户端 objectMapper.writeValue(response.getWriter(),user); }
day13、vue&element-ui
1、vue概念
-
Vue 是一套前端框架,免除原生JavaScript中的DOM操作,简化书写。
-
基于MVVM(Model-View-ViewModel)思想,实现数据的双向绑定,将编程的关注点放在数据上
2、vue快速入门
<div id="app">
<!--3 将模型数据绑定到视图中--><!--{{变量名}}叫做插值表达式 -->
<p> {{message}} </p>
</div>
<!--1 引入vue.js文件-->
<script src="assets/js/vue.js"></script>
<!--2 创建vue对象,定义模型数据-->
<script>
new Vue({
el:"#app",//表示Vue接管的区域,只有在这个区域内才可以使用模型数据
data(){
return{
message:"hello vue", //定义模型数据
}
}
});
</script>
3、vue常用指令
- 指令:HTML 标签上带有 v- 前缀的特殊属性,不同指令具有不同含义
指令 | 作用 |
---|---|
v-bind | 为HTML标签绑定属性值,如设置 href , css样式等 |
v-model | 在表单元素上创建双向数据绑定 |
v-on | 为HTML标签绑定事件 |
v-if | 条件性的渲染某元素,判定为true时渲染,否则不渲染 |
v-else | |
v-else-if | |
v-show | 根据条件展示某元素,区别在于切换的是display属性的值 |
v-for | 列表渲染,遍历容器的元素或者对象的属性 |
3.1、 v-bind
- 为HTML标签绑定属性值,如设置 href , css样式等
<div id="app">
<!--3 把模型数据绑定到视图上-->
<!--v-bind:给html标签属性绑定模型数据
语法:v-bind
简写: :
-->
<a v-bind:href="url">点击跳转到网页</a><br>
<!--简写:-->
<a :href="url">点击跳转到网页</a><br>
<input v-model="url"/>
</div>
<!--1 引入vue.js-->
<script src="assets/js/vue.js"></script>
<!--2 创建vue对象,定义模型数据-->
<script>
new Vue({
el:"#app",//接管的区域
data:{
//定义模型数据url
url:"www.baidu.com",
}
});
</script>
- v-bind 的语法糖 :
3.2、v-model
- v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据
<!--3 将模型数据和视图进行绑定-->
<div id="app">
<!--展示模型数据-->
<p>输入的用户名是:{{user.username}}</p>
<p>输入的年 龄是:{{user.age}}</p>
<p>选择的性 别是:{{user.gender}}</p>
<p>选择的爱 好是:{{user.hobby}}</p>
<hr>
<form action="">
用户名:<input type="text" v-model="user.username"><br>
年龄:<input type="text" v-model="user.age"><br>
性别:<input type="radio" value="man" v-model="user.gender">男
<input type="radio" value="woman" v-model="user.gender">女<br>
爱好:<input type="checkbox" value="sing" v-model="user.hobby">唱
<input type="checkbox" value="dance" v-model="user.hobby">跳
<input type="checkbox" value="rap" v-model="user.hobby">rap
<input type="checkbox" value="basketball" v-model="user.hobby">篮球<br>
<input type="button" id="btn" value="保存信息">
</form>
</div>
<!--1 引入js文件-->
<script src="assets/js/vue.js"></script>
<!--2 定义Vue对象,初始化模型数据-->
<script>
new Vue({
el:"#app", //定义Vue控制的区域
data:{
user:{
username:"张三",
age:20,
//定义性别模型数据,让男默认选中。
gender: "man",
//定义爱好模型数据,保存选中的爱好信息
hobby:["sing","basketball"],
}
}
});
</script>
3.3、v-on
- 主要用于为html标签绑定事件
<input type="button" value="一个按钮" v-on:click="show()">
或者
<input type="button" value="一个按钮" @click="show()">
new Vue({el: "#app",
methods: {
show(){
alert("我被点了");
}}
});
- v-on绑定表单向服务器提价表单数据
<form action="">
用户名:<input type="text" v-model="user.username"><br>
年龄:<input type="text" v-model="user.age"><br>
性别:<input type="radio" value="man" v-model="user.gender">男
<input type="radio" value="woman" v-model="user.gender">女<br>
爱好:<input type="checkbox" value="sing" v-model="user.hobby">唱
<input type="checkbox" value="dance" v-model="user.hobby">跳
<input type="checkbox" value="rap" v-model="user.hobby">rap
<input type="checkbox" value="basketball" v-model="user.hobby">篮球<br>
<!--
需求:给按钮绑定单击事件,在绑定的函数中提交表单数据
-->
<input type="button" @click="onsubmit()" value="新增">
<!--简写-->
</form>
<script>
new Vue({
el:"#app", //定义Vue控制的区域
data:{
user:{
username:"张三",
age:20,
gender:"man", //定义性别模型数据,让男默认选中。
hobby:[] //定义爱好模型数据,保存选中的爱好信息
}
},
methods:{
onsubmit(){
let _this = this;
axios({
method:"POST",
url:"servletdemo1",
data:_this.user,
}).then();
}
}
});
</script>
3.4、v-if 和 v-show
指令 | 作用 |
---|---|
v-if | 条件性的渲染某元素,判定为true时渲染,否则不渲染 |
v-else-if | |
v-else | |
v-show | 根据条件展示某元素,区别在于切换的是display属性的值 |
<div id="app">
<!-- 需求1:模型数据count=1展示div1,count=2展示div2,count=3展示div3,否则展示div4? -->
<div v-if="count==1">div1</div>
<div v-else-if="count==2">div2</div>
<div v-else-if="count==3">div3</div>
<div v-else>div4</div>
<!--需求2:如果count>=0就展示div-show,否则隐藏div-show-->
<div v-show="count==5">div-show</div>
<!--使用输入框双向绑定count,并通过输入框修改count值。-->
<input v-model="count">
</div>
<!--1 引入vue.js文件-->
<script src="./assets/js/vue.js"></script>
<!--2 创建vue对象,定义模型数据-->
<script>
new Vue({
el:"#app", //表示vue接管的区域,只有在这个区域内才可以使用表达式获取模型数据
//方式1:
data:{ //data中定义模型数据
count:1
}
});
</script>
3.5、v-for
指令 | 作用 |
---|---|
v-for | 列表渲染,遍历容器的元素或者对象的属性 |
1. 没有加索引
<div v-for="addr in addrs"> {{addr}}</div>
2. 加上索引
<div v-for="(addr,index) in addrs"> {{index}} -- {{addr}}</div>
new Vue({
el: "#app",
data:{
addrs:["北京","上海","西安"]
}
});
4、vue生命周期
- 生命周期的八个阶段:每触发一个生命周期事件,会自动执行一个生命周期方法(钩子函数)
new Vue({
beforeCreate(){}, // 创建前
created(){}, // 创建后
beforeMount(){}, // 载入前
mounted(){}, // 挂载完成
beforeUpdate(){}, // 更新前
updated(){}, // 更新后
beforeDestory(){}, // 销毁前
destoryed(){}, // 销毁后
});
- 实际开发中 mounted(){}, // 挂载完成 ,使用的最多。
- mounted:挂载完成,Vue初始化成功,HTML页面渲染成功。
- 发送异步请求,加载数据
5、Element-ui的快速入门
-
引入Element 的css、js文件 和 Vue.js
<script src="assets/js/vue.js"></script> <script src="assets/element-ui/lib/index.js"></script> <link rel="stylesheet" href="assets/element-ui/lib/theme-chalk/index.css">
-
创建vue实例
new Vue({ el: "#app", //表示vue接管的区域,只有在这个区域内才可以使用表达式获取模型数据 data() { return { imgs:["imgs/a.jpg","imgs/b.jpg","imgs/c.jpg","imgs/d.jpg","imgs/e.jpg"] } }, methods: { } });
-
去element官网借鉴
<div id="app"> <!--按钮--> <el-row> <el-button>默认按钮</el-button> <el-button type="primary">主要按钮</el-button> <el-button type="success">成功按钮</el-button> <el-button type="info">信息按钮</el-button> <el-button type="warning">警告按钮</el-button> <el-button type="danger">危险按钮</el-button> </el-row> </div>
day14、brand-demo综合案例
1. 使用 vue2 版本
2. 使用 Elemetn-ui2版本
3. 使用maven+mysql+mybatis
1、分页查询
- mysql分页查询使用的关键字是 limit,这个是mysql的方言,其他数据库不能工用。
SELECT 字段列表 FROM 表名 LIMIT 起始索引 , 每页显示条数;
起始索引 = (当前索引 - 1) * 每页显示条数
----------------------------------------
其他数据库分页查询使用的关键字
Oracle : rownum
SQL Server : top
- 分页查询-PageHelper分页插件的使用
-
导入插件依赖
<!--pagehelper分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
-
mybatsi-config.xml中配置拦截器插件
<!--分页插件--> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
-
在业务逻辑处理中使用
@Override public PageInfo<Brand> selectByPage(int currentPage, int pageSize) { // 获取sqlSession SqlSession sqlSession = factory.openSession(); // 获取mapper接口代理对象 BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class); // 设置分页参数 currentPage 当前页码, pageSize 每页条数 PageHelper.startPage(currentPage,pageSize); // 执行查询 List<Brand> list = brandMapper.selectAll(); // 封装分页结果 PageInfo<Brand> pageInfo = new PageInfo<Brand>(list); // 释放资源 sqlSession.close(); // 返回结果 return pageInfo; }
- 有了这个插件后,会自动根据不同数据库,进行拼接不同分页关键字
2、多条件查询
- 使用动态 SQL进行替代
<select id="selectAll" resultMap="brandResultMap">
select * from tb_brand
<where>
<if test="status != null and status != -1">
status = #{status}
</if>
<if test="brandName != null and brandName != ''">
and brand_name like concat('%',#{brandName},'%')
</if>
<if test="companyName != null and companyName != ''">
and company_name like concat('%',#{companyName},'%')
</if>
</where>
</select>
- 思考
1. 条件查询也需要进行分页展示
2. 单纯的分页查询不需要带任何的条件,就是 where 后面不拼接任何动态sql
3. 即可以把条件查询与分页查询的功能 合并,使用同一个 selectAll 方法。
3、批量删除
- 批量删除
- 单个删除 ------> 调用批量删除的方法 只不过数组只存储了 一个 id 值
4、servlet优化
4.1 、 问题导入
Web 层的 Servlet 个数太多了,不利于管理和编写
我们发现每一个功能都需要定义一个 servlet
,一个模块需要实现增删改查功能,就需要4个 servlet
,模块一多就会造成servlet
泛滥。此时我们就想 servlet
能不能像 service
一样,一个模块只定义一个 servlet
,而每一个功能只需要在该 servlet
中定义对应的方法。例如下面代码:
@WebServlet("/brand/*")
public class BrandServlet {
//查询所有
public void selectByPage(...) {}
//添加数据
public void add(...) {}
//修改数据
public void update(...) {}
//删除删除
public void deleteByIds(...) {}
}
而我们知道发送请求 servlet
,tomcat
会自动的调用 service()
方法,之前我们在自定义的 servlet
中重写 doGet()
方法和 doPost()
方法,当我们访问该 servlet
时,HttpServlet在 service()
会根据请求方式将请求分发给 doGet()
或者 doPost()
方法,如下图
那么我们也可以仿照这样请求分发的思想,在 service()
方法中根据具体的操作调用对应的方法,如:查询所有就调用 selectAll()
方法,添加企业信息就调用 add()
方法。
为了做到通用,我们定义一个通用的 servlet
类,在定义其他的 servlet
是不需要继承 HttpServlet
,而继承我们定义的 BaseServlet
,在 BaseServlet
中调用具体 servlet
(如BrandServlet
)中的对应方法。
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//进行请求的分发
}
}
BrandServlet
定义就需要修改为如下:
@WebServlet("/brand/*")
public class BrandServlet extends BaseServlet {
//用户实现分页查询
public void selectByPage(...) {}
//添加企业信息
public void add(...) {}
//修改企业信息
public void update(...) {}
//删除企业信息
public void deleteByIds(...) {}
}
那么如何在 BaseServlet
中调用对应的方法呢?比如查询所有就调用 selectByPage()
方法。
可以规定在发送请求时,请求资源的二级路径(/brandServlet/selectByPage)和需要调用的方法名相同,如:
查询所有数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/
selectByPage
添加数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/add
修改数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/update
删除数据的路径以后就需要写成: http://localhost:8080/brand-case/brandServlet/deleteById
这样的话,在 BaseServlet
中就需要获取到资源的二级路径作为方法名,然后调用该方法
public class BaseServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取请求路径
String uri = req.getRequestURI(); // 例如路径为:/brand-case/brand/selectAll
//2. 获取最后一段路径,方法名
int index = uri.lastIndexOf('/');
String methodName = uri.substring(index + 1); // 获取到资源的二级路径 selectAll
//2. 执行方法
//2.1 获取BrandServlet /UserServlet 字节码对象 Class
//System.out.println(this);
Class<? extends BaseServlet> cls = this.getClass();
//2.2 获取方法 Method对象
try {
Method method = cls.getMethod(methodName,???);
//4,调用该方法
method.invoke(this,???);
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过上面代码发现根据方法名获取对应方法的 Method
对象时需要指定方法参数的字节码对象。解决这个问题,可以将方法的参数类型规定死,而方法中可能需要用到 request
对象和 response
对象,所以指定方法的参数为 HttpServletRequest
和 HttpServletResponse
,那么 BrandServlet
代码就可以改进为:
@WebServlet("/brand/*")
public class BrandServlet extends BaseServlet {
//用户实现分页查询
public void selectByPage(HttpServletRequest req, HttpServletResponse resp) {}
//添加企业信息
public void add(HttpServletRequest req, HttpServletResponse resp) {}
//修改企业信息
public void update(HttpServletRequest req, HttpServletResponse resp) {}
//删除企业信息
public void deleteByIds(HttpServletRequest req, HttpServletResponse resp) {}
}
BaseServlet代码可以改进为:
public class BaseServlet extends HttpServlet {
//根据请求的最后一段路径来进行方法分发
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取请求路径
String uri = req.getRequestURI(); // 例如路径为:/brand-case/brand/selectAll
//2. 获取最后一段路径,方法名
int index = uri.lastIndexOf('/');
String methodName = uri.substring(index + 1); // 获取到资源的二级路径 selectAll
//2. 执行方法
//2.1 获取BrandServlet /UserServlet 字节码对象 Class
//System.out.println(this);
Class<? extends BaseServlet> cls = this.getClass();
//2.2 获取方法 Method对象
try {
Method method = cls.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
//2.3 执行方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
4.2、 后端优化
定义了 BaseServlet
后,针对品牌模块我们定义一个 BrandServlet
的 Servlet,并使其继承 BaseServlet
。在BrandServlet
中定义 以下功能的方法:
查询所有
功能:方法名声明为selectAll
,并将之前的SelectAllServlet
中的逻辑代码拷贝到该方法中添加数据
功能:方法名声明为add
,并将之前的AddServlet
中的逻辑代码拷贝到该方法中
具体代码如下:
/**
* @Author zhouxiangyang
* @Date 2021/12/24 16:57
*/
@WebServlet("/brand/*")
public class BrandServlet extends BaseServlet {
private BrandService brandService=new BrandServiceImpl();
private ObjectMapper objectMapper=new ObjectMapper();
/**
* 处理分页查询的请求
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void selectByPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json;charset=utf-8");
//1 接收请求参数,当前页面和每页条数
String currentPage = request.getParameter("currentPage");
String pageSize = request.getParameter("pageSize");
//获取搜索条件,封装成Brand对象
Brand brand = objectMapper.readValue(request.getReader(), Brand.class);
//2 调用service层方法,分页查询,得到结果PageInfo
PageInfo<Brand> pageInfo = brandService.selectByPage(
Integer.parseInt(currentPage),
Integer.parseInt(pageSize),brand);
//3 将结果转换成json响应给客户端
/* String json = objectMapper.writeValueAsString(pageInfo);
response.getWriter().write(json);*/
//简化
objectMapper.writeValue(response.getWriter(),pageInfo);
}
/**
* 处理新增品牌的请求
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1 获取请求参数,转换成Brand对象
Brand brand = objectMapper.readValue(request.getReader(), Brand.class);
//2 调用service层方法,保存品牌信息
brandService.add(brand);
//3 响应一个成功的标识
response.getWriter().write("success");
}
/**
* 处理批量删除的请求
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void deleteByIds(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1 接收请求参数,也就是被删除的品牌id数组们,json数据,转换成数组int[]对象
int[] ids = objectMapper.readValue(request.getReader(), int[].class);
//2 调用service层方法,批量删除
brandService.deleteByIds(ids);
//3 响应成功的标识
response.getWriter().write("success");
}
/**
* 处理修改品牌的请求
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1 获取请求参数,转换成Brand对象
Brand brand = objectMapper.readValue(request.getReader(), Brand.class);
//2 调用service层方法,修改品牌信息
System.out.println(brand);
//3 响应一个成功的标识
response.getWriter().write("success");
}
/**
* 处理根据id删除品牌的请求
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void deleteById(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1 获取请求参数,要删除的id
String id = request.getParameter("id");
//2 调用service层方法,删除品牌信息
System.out.println("id="+id);
//3 响应一个成功的标识
response.getWriter().write("success");
}
}
4.3、 前端优化
页面中之前发送的请求的路径都需要进行修改,selectByPage
函数中发送异步请求的 url
应该改为 http://localhost:8080/brand-case/brand/selectByPage
。具体代码如下:
//发送请求,获取分页数据
selectByPage(){
axios.post("brand/selectByPage?currentPage="+this.currentPage+"&pageSize="+this.pageSize,this.brand).then(resp=>{
//2 处理响应结果
console.log(resp.data); //pageInfo对象
//2.1 在table表格中展示当前页数据,本质是给tableData赋值
this.tableData=resp.data.list;
//2.2 展示分页条
this.total=resp.data.total;
})
}
addBrand()
函数中发送异步请求的 url
应该改为 http://localhost:8080/brand-case/brand/add
。具体代码如下:
// 添加数据
addBrand() {
console.log(this.addFormData);
//1 发送异步请求,将表单模型数据发送给服务器
axios.post("brand/add",this.addFormData).then(resp=>{
//2 处理响应结果
//判断结果是否是success
if(resp.data=="success"){
//添加成功
//2.1 关闭新增对话框
this.dialogVisible=false;
//2.2 重新分页查询
this.selectByPage();
//2.3 弹出消息提示
this.$message({
message: '恭喜你,添加成功',
type: 'success'
});
}
});
}
deleteByIds()
函数中发送异步请求的 url
应该改为 http://localhost:8080/brand-case/brand/deleteByIds
。具体代码如下:
//批量删除的方法
deleteByIds(){
this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//点击确定按钮
//1 发送异步请求,匹配删除,将multipleSelection中保存的选中的id发送给服务器
axios.post("brand/deleteByIds",this.multipleSelection).then(resp=>{
//2 处理响应结果
if(resp.data=="success"){
//2.1 提示删除成功
this.$message({
message: '恭喜你,删除成功',
type: 'success'
});
//2.2 重新分页查询
this.selectByPage();
}
});
});
}
----------------榆木脑袋,炼气期三层--------------2024-03-27 19:12:41-----