Servlet--基础学习
1、 Servlet简介
Servlet(Server Applet)是 Java Servlet 的简称,称为小服务程序或服务连接器,是 Java 语言编写的服务器端程序,换句话说,Servlet 就是运行在服务器上的 Java 类。
处理请求和发送响应的过程是由一种叫做Servlet的程序来完成的,并且Servlet是为了解决实现动态页面而衍生的东西。
2、Servlet原理
- 浏览器向服务器发送Get或Post请求
- 服务器上的容器接收HTTP请求,从而产生两个对象:HttpServletRequest和HttpServletResponse
- 加载servlet,实例化Servlet后,执行init()方法
- 调用Servlet中的Service()方法
- service()方法根据请求类型调用doGet()或者doPos()方法
- doGet()或者doPost()处理后,由web容器调用返回给浏览器
- 执行destroy(),销毁线程
我们在编写Servlet的时候,我们一般采用的是继承HttpServlet类的方式,Servlet有着这样的关系(HelloServlet为自己创建的类):
Servlet的mapping映射
这是注册的servlet:
<!-- 注册servlet-->
<servlet>
<!-- 类名 -->
<servlet-name>hello</servlet-name>
<!-- 所在的包 -->
<servlet-class>com.sdablog.servlet.HelloServlet</servlet-class>
</servlet>
- 一个Servlet可以指定一个映射路径
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
- 一个Servlet可以指定多个映射路径
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
- 一个Servlet可以指定通用映射路径
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
- 一个Servlet可以指定后缀或前缀映射路径
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>*.sd</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/sd*</url-pattern>
</servlet-mapping>
指定后缀时,前面不能有斜杠
优先级问题
如果指定了固定的路径映射,它的优先级会高于通配的路径映射。
严格来讲会根据"精确优先"进行匹配:
- 路径模式包含的路径变量越少、通配符越少,SpringMVC认为它越精确。例如/hotels/{hotel}/*只包含一个路径变量和一个通配符,它会被认为比/hotels/{hotel}/**更精确,因为后者包含一个路径变量和两个通配符
- 如果两个路径模式包含的路径变量和通配符数量相同,长度更长的路径模式会被认为更精确。例如/foo/bar*和foo/*两个路径模式都只包含一个通配符,但/foo/bar/*更长,因此它会被认为更精确
- 当两个路径模式包含的路径变量和通配符总数相同时,通配符较少的路径模式会被认为更精确。例如/hotels/{hotel}和/hotels/*都只包含一个路径变量和通配符,但是前者只包含路径变量,没有包含通配符,因此他被认为更精确
此外,还有以下的匹配规则:- 路径模式几乎可以匹配任何请求地址,因此其它任何路径模式都比它更精确。例如.api/{a}/{b}/{c}虽然包含三个路径变量,但是依然比更精确。
乎可以匹配任何请求地址,因此其它任何路径模式都比它更精确。例如.api/{a}/{b}/{c}虽然包含三个路径变量,但是依然比/**更精确。 - /public/几乎可以匹配/public/路径下的任何请求地址,因此任何不包含通配符的路径模式都比它更精确。例如/public/{a}/{b}/{c}虽然包含了三个路径变量,但它依然比前者更精确。
- 路径模式几乎可以匹配任何请求地址,因此其它任何路径模式都比它更精确。例如.api/{a}/{b}/{c}虽然包含三个路径变量,但是依然比更精确。
3、HelloServlet
Servlet接口在Sun公司有两个默认的实现类:HttpServlet、GenericServlet
项目工程需要引入的jar包:
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
</dependencies>
-
构建一个普通的Maven项目,删掉里面的src目录,以后学习的就在这项目里建立Moudel;这个空的工程就是Maven主工程。
-
pom.xml中parent标签的使用
使用maven是为了更好的帮项目管理包依赖,maven的核心就是pom.xml。当我们需要引入一个jar包时,在pom文件中加上< dependency></ dependency>就可以从仓库中依赖到相应的jar包。但是子项目如果也需要这个jar包,如果又重新在子项目的pom文件增加,当jar版本发生改变,就需要修改父项目和子项目的jar包,这样就很麻烦,工作量也很大。此时就可以使用parent标签, 可以创建一个parent项目,打包类型为pom,parent项目中没有任何代码,只是管理多个项目之间公共的依赖。在子项目的pom文件定义< parent></ parent>,parent标签中写上parent项目的pom坐标就可以引用到common.jar了。父项目的java,子项目可以直接使用
父项目中会有
<groupId>com.sdablog</groupId>
<artifactId>javaweb-servlet</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>servlet-01</module>
</modules>
子项目中会有
<parent>
<groupId>com.sdablog</groupId>
<artifactId>javaweb-servlet</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
标签中写上springmvc.jar的坐标,不需要写版本号,可以依赖到这个jar包了。这样springmvc.jar的版本发生变化时只需要修改parent中的版本就可以了。
-
Maven环境优化
- 修改web.xml为最新
- 将maven的结构搭建完整
-
编写一个Servlet程序
- 编写一个普通类
- 实现Servlet接口,这里直接继承HttpServlet
- 编写一个普通类
public class HelloServlet extends HttpServlet {
//由于get或post只是请求实现的不同方式,可以相互调用,业务逻辑都一样;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter(); //响应流
writer.println("Hello,Servlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
- 编写Servlet的映射
为什么需要映射:我们写的是java程序,但是要通过浏览器访问,而浏览器需要连接Web服务器,所以我们需要在web服务器中注册我们写的Servlet,还需要给他一个浏览器访问的路径。
在web.xml文件中配置servlet映射标签
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 注册servlet-->
<servlet>
<!-- 类名 -->
<servlet-name>hello</servlet-name>
<!-- 所在的包 -->
<servlet-class>com.sdablog.HelloServlet.ServletHttp</servlet-class>
</servlet>
<!-- servlet的请求路径-->
<servlet-mapping>
<!-- 访问的网址 -->
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
- 配置Tomcat
注意:配置项目发布的路径就可以
- 启动测试
4、Servlet技术特点
Servlet技术带给程序员最大的优势是它可以处理客户端传来的HTTP请求,并返回一个响应
Servlet是一个Java类,Java语言能够实现的功能,Servlet基本上都可以实现(图形界面除外)。总的来说,Servlet技术具有以下特点
-
高效。在服务器上仅有一个Java虚拟机在运行,它的优势在于当多个来自客户端的请求进行访问时,Servlet为每个请求分配一个线程而不是进程。
-
方便。Servlet提供了大量的实用工具例程,例如处理很难完成的HTML表单数据、读取和设置HTTP头、处理Cookie和跟踪会话等。
-
跨平台。Servlet是用Java类编写的,它可以在不同的操作系统平台和不同的应用服务器平台下运行。
-
灵活性和可扩展性。采用Servlet开发的Web应用程序,由于Java类的继承性、构造函数等特点,使得其应用灵活,可随意扩展。
-
共享数据。Servlet之间通过共享数据可以很容易地实现数据库连接池。它能方便地实现管理用户请求,简化Session和获取前一页面信息的操作。而在CGI之间通信则很差。由于每个CGI程序的调用都开始一个新的进程,调用间通信通常要通过文件进行,因而相当缓慢。同一台服务器上的不同CGI程序之间的通信也相当麻烦。
-
安全。有些CGI版本有明显的安全弱点。即使是使用最新的标准和PERL等语言,系统也没有基本安全框架。而Java定义有完整的安全机制,包括SSL\CA认证、安全政策等规范。
5、ServletContext应用
当web应用启动的时候,自动创建ServletContext对象,它代表了当前的web应用。
由于一个web应用中的所有servlet共享同一个ServletContext对象,因此servlet对象之间可以通过servletontext对象来实现通讯。servletContext对象通常也被称为context域对象。
ServletContext对象相当于一个管理员,也可以说是一个“中介”,它可以保存一些东西,将各个servlet应用联系到一起。是一个公用的空间
servletcontext用法小结:
获取:this.getServletContext(建议用这个)、this.getServletConfig.getServletContext();
//添加属性
servletcontext.setAttribute(string,object);
//取出属性
servletcontext.getAttribute(“属性名”);
//删除
servletcontext.removeAttribute(“属性名”);
通过在ServletConfig中调用getServletContext方法,也可以获得ServletContext对象。
作用:
5.1、 共享数据
例:A这个Servlet中保存的数据,B这个Servlet中拿到;
存放的类:
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String username = "小明";
context.setAttribute("username", username); //将一个数据保存在username中,值为username
}
}
读取的类:
public class GetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//获取一个名为username的值
String username = (String) context.getAttribute("username");
//设置头文件
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
resp.getWriter().print("名字=" + username);
}
}
注:先存后取。
5.2、获取web应用的初始化参数
web.xml
<!-- 配置该项目所有的servlet均可以使用的参数 ,写在servlet域外面-->
<context-param>
<!-- 名称-->
<param-name>sex</param-name>
<!-- 内容-->
<param-value>famale</param-value>
</context-param>
在web应用中获取sex的内容:String sex=this.getServletContext().getInitParameter(“sex”);
5.3、请求转发
- 请求转发:你去一家餐厅吃饭,想吃什么菜点了服务员给你送来,你不用直接去端你的菜。
请求转发:
ServletContext context = this.getServletContext();
// 转发的请求路径
RequestDispatcher requestDispatcher = context.getRequestDispatcher("/getc");
// 调用forward实现请求转发
requestDispatcher.forward(req, resp);
this.getServletContext().getRequestDispatcher("/url").forward(request, response);
request:请求对象
response:响应对象
5.4、读取资源文件
读取资源文件要根据资源文件所在的位置分为两种情况:
- 读取资源文件(读web目录下和WEB-INF目录下的文件)
无论在java目录下还是resources目录下创建的文件,编译后都会被打包在:classes路径下,这个路径俗称为classpath
读取资源文件:
InputStream stream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
//创建属性对象
Properties properties = new Properties();
properties.load(stream);
//读取属性
String user = properties.getProperty("username");
String pwd = properties.getProperty("password");
resp.getWriter().print(user + ":" + pwd);
6、HttpServletResponse
web服务器接收到客服端的请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象
,代表响应的一个HttpServletReqsponse;
- 如果要获取客户端请求过来的参数:找HttpServletRequest
- 如果要给客户端响应一些信息:找HttpServletResponse
1. 简单分类
- 负责向浏览器发送数据的方法
public ServletOutputStream getOutputStream() throws IOException;
public PrintWriter getWriter() throws IOException;
- 负责向浏览器发送响应头的方法
//设置响应头编码
public void setCharacterEncoding(String charset);
//设置响应头的字符串长度
public void setContentLength(int len);
//设置长度
public void setContentLengthLong(long len);
//设置类型
public void setContentType(String type);
2. 常见应用
- 向浏览器输出消息
- 下载文件
- 要获取下载文件的路径
- 获取下载文件名
- 设置让浏览器能够支持下载我们需要的东西
- 获取下载文件的输入流
- 创建缓冲区
- 获取OutputStream对象
- 将FileOutputStream流写入buffer缓冲区
- 使用OutputStream将缓冲区中的数据输出到客户端
// 1. 要获取下载文件的路
String realPath = "F:\\java\\Maven\\javaweb-servlet\\response\\src\\main\\resources\\img.png";
System.out.println("下载文件的路径:"+realPath);
// 2. 获取下载文件名
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
// 3. 设置让浏览器能够支持下载我们需要的东西
resp.setHeader("Content-disposition", "attachment;filename=" + fileName);
// 4. 获取下载文件的输入流
FileInputStream in = new FileInputStream(realPath);
// 5. 创建缓冲区
int len = 0;
byte[] buffer = new byte[1024];
// 6. 获取OutputStream对象
ServletOutputStream out = resp.getOutputStream();
// 7. 将FileOutputStream流写入buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
while ((len = in.read(buffer))>0){
out.write(buffer,0,len);
}
in.close();
out.close();
扩展知识:
- Java substring():截取父字符串的某一部分
public String substring(int beginIndex):从位置 beginIndex 向后获取内容
public String substring(int beginIndex, int endIndex) :从 beginIndex 到 endIndex 区间获取内容
参数
-
beginIndex – 起始索引(包括), 索引从 0 开始。
-
endIndex – 结束索引(不包括)。
public static void main(String[] args) {
String str = "this is text";
//从0到5的位置获取内容
String sub = str.substring(0, 5);
System.out.println(sub); // is text
//从位置4开始向后获取内容
String str1 = str.substring(4);
System.out.println(str1); // is text
}
-
lastIndexOf 和 IndexOf 函数
1. lastIndexOf():在字符串中根据搜索条件来返回其在字符串中的位置,**空格也计数**,如果字符串中没有这样的字符,返回-1。
strObj.lastIndexOf(int ch) ,返回指定字符在此字符串中最后一次出现处的索引。
strObj.lastIndexOf(int ch , int fromIndex) ,返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。
参数:
strObj:必选项,String 对象或文字。
substring :必选项。要在 String 对象内查找的子字符串。
startindex :可选项。该整数值指出在 String 对象内进行查找的开始索引位置。假如省略,则查找从字符串的末尾开始。
String str = "01234567890123456789";
//查找字符串“01234567890123456789”中字符‘8’所在的位置(索引从0开始,从前往后搜索)
System.out.println(str.lastIndexOf('8')); //18
//查找字符串“01234567890123456789”中字符‘8’所在的位置,从索引为9的位置,即“0123456789”,从后往前搜索。
System.out.println(str.lastIndexOf('8', 9)); //8
2. IndexOf ():返回 String 对象内第一次出现子字符串的字符位置。这里和lastIndexOf 道理相反,就不写了。
3. 验证码功能
验证码怎么来的?
- 前端实现
- 后端实现,需要用到java的图片类,生成一个图片
public class ImageServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//如何让浏览器3秒刷新一次
resp.setHeader("refresh", "3");
//在内存中创建一个图片
BufferedImage image = new BufferedImage(200, 180, BufferedImage.TYPE_INT_RGB);
//得到图片
Graphics2D g = (Graphics2D) image.getGraphics();
//设置背景颜色
g.setColor(Color.white);
g.fillRect(0, 0, 200, 180);
//给图片写数据
g.setColor(Color.BLUE);
g.setFont(new Font(null, Font.BOLD, 20));
g.drawString(makeNum(), 0, 20);
//告诉浏览器这个请求用图片的方式打开
resp.setContentType("image/jpeg");
//网站存在缓存,不让浏览器缓存
resp.setDateHeader("expires", -1);
resp.setHeader("Cache-Control", "no-cache");
resp.setHeader("Pragma", "no-cache");
//把图片写给浏览器
ImageIO.write(image, "jpg", resp.getOutputStream());
}
//生成随机数
private String makeNum() {
Random random = new Random();
String num = random.nextInt(9999999) + "";
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 7 - num.length(); i++) {
sb.append("0");
}
num = sb.toString() + num;
System.out.println(num);
return num;
}
}
4. 实现重定向
重定向:你去吃自助餐,问服务员匹萨在哪,服务员告诉你在哪,然后你去端。
一个web资源收到客户端请求后,他会通知客户端去访问另一个web资源,这个过程叫重定向。
常见场景:
- 用户登录
public void sendRedirect(String location) throws IOException;
/*
解析:
//响应头部设置一个Location属性,值就是重定向路径
resp.setHeader("Location", "/re/img");
//设置状态码
resp.setStatus(302);
*/
//‘re’是当前项目路径
resp.sendRedirect("/re/img"); //重定向
面试题: 请你聊聊重定向和转发的区别?
相同点: 页面都会跳转
不同点:
- 请求转发的时候,url不会产生变化;
- 重定向的时候url会发生变化;
实践:
用重定向做一个用户登录逻辑。
前端登录页面:
<html>
<body>
<h2>Hello World!</h2>
<%--这里提交的路径,需要寻找到项目的路劲--%>
<%-- ${pageContext.request.contextPath} 代表当前的项目--%>
<form action="${pageContext.request.contextPath}/login" method="get">
USER:<input type="text" name="username"> <br>
Password:<input type="password" name="password"> <br>
<input type="submit" value="login">
</form>
</body>
</html>
后端处理数据函数:
public class RequestTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入这个请求");
//处理请求,获取客户端请求的数据
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println(username + " : " + password);
//重定向时候一定要注意路径问题,否则报404
resp.sendRedirect("/re/success.jsp");
}
这里补充一个基础的知识,以前没怎么注意。
java中不等于的逻辑运算,数字可用用 != 符号进行判断,但是要判断字符串的话,需要用equals()方法
public boolean equals(Object anObject)
- anObject – 与字符串进行比较的对象
- 如果给定对象与字符串相等,则返回 true;否则返回 false。
7、HttpServletRequest
HttpServletRequest代表客户端的请求,用户通过Http协议访问服务器,HTTP请求中的所有信息会被封装到HttpServletRequest,通过这个HttpServletRequest的方法,获得客户端的所有信息。
- 获得客户机信息:
方法 | 说明 |
---|---|
getRequestURL() | 返回客户端发出请求时的完整URL。 |
getRequestURI() | 返回请求行中的参数部分。 |
getQueryString () | 方法返回请求行中的参数部分(参数名+值) |
getRemoteHost() | 返回发出请求的客户机的完整主机名。 |
getRemoteAddr() | 返回发出请求的客户机的IP地址。 |
getPathInfo() | 返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以"/"开头。 |
getRemotePort() | 返回客户机所使用的网络端口号。 |
getLocalAddr() | 返回WEB服务器的IP地址。 |
getLocalName() | 返回WEB服务器的主机名。 |
- 获得客户机请求头
方法 | 说明 |
---|---|
getHeader(string name) | 以 String 的形式返回指定请求头的值。如果该请求不包含指定名称的头,则此方法返回 null。如果有多个具有相同名称的头,则此方法返回请求中的第一个头。头名称是不区分大小写的。可以将此方法与任何请求头一起使用 |
getHeaders(String name) | 以 String 对象的 Enumeration 的形式返回指定请求头的所有值 |
getHeaderNames() | 返回此请求包含的所有头名称的枚举。如果该请求没有头,则此方法返回一个空枚举 |
- 获得客户机请求参数
方法 | 说明 |
---|---|
getParameter(String name) | 根据name获取请求参数(常用) |
getParameterValues(String name) | 根据name获取请求参数列表(常用) |
getParameterMap() | 返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系。(编写框架时常用) |
7.1、 获取前端数据
通过getParameter和getParameterValues获取前端的数据,用一个登录demo例子,用请求转发的方式登录页面。
前端页面jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<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="hobbys" value="打球"> 打球
<input type="checkbox" name="hobbys" value="游泳"> 游泳
<input type="checkbox" name="hobbys" value="玩游戏"> 玩游戏
<input type="checkbox" name="hobbys" value="听音乐"> 听音乐
<input type="checkbox" name="hobbys" value="打代码"> 打代码
<input type="checkbox" name="hobbys" value="看电影"> 看电影
<br>
<input type="submit" value="登录">
</form>
</body>
</html>
获取前端数据:
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[] hobbys = req.getParameterValues("hobbys");
System.out.println(username+":"+password);
System.out.println(Arrays.toString(hobbys));
//通过请求转发,跳转到success页面
//请求转发的路径 / 代表当前的web应用
req.getRequestDispatcher("/success.jsp").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
注:这里有个坑,请求转发的符号“ / ”代表的是当前项目的路径,所以不用写当前项目的路径名,直接 / 就行。
小章总结:客户端将信息封装到request里,发送给服务器,让后服务器有各种方法函数将里面的信息取出。