1. Servlet简介
1)它是sun公司开发动态web的一门技术。
2)sun在API中提供的一个servlet接口,因此想开发servlet程序,需要完成两个步骤:编写一个类,实现servlet接口;把开发好的Java类部署到web服务器中。
3)把实现了servlet接口的Java程序叫做Servlet,即本质上还是Java程序。
2. HelloServlet
1)构建一个普通Maven项目,删除里面的src目录,以后在这个项目里建module。这个空的工程叫主工程。主工程导入dependency。
2)如有必要(有时候子项目parent标签会被覆盖掉,导致dependency无法关联),手动在子项目中的pom.xml添加parent标签信息。关于Maven父子工程的理解:
//父项目中有子项目(模块)
<modules>
<module>servlet-01</module>
</modules>
//子项目
<parent>
<artifactId>javaweb-02-servlet</artifactId>
<groupId>com.yyz</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
子项目中:
父项目中的jar包子项目可以直接用
3) 复制最新的配置信息到web.xml
<?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"
metadata-complete="true">
</web-app>
4)在主项目pom.xml添加依赖,子项目也可以关联到
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
5)在main文件夹下添加java和resources文件夹
6)编写servlet程序,在子项目java文件夹下新建package(注意规范):
7)然后实现一个接口:
值得注意的是sun公司有两个默认的实现类,HttpServlet 和 GenericServlet
这里直接继承HttpServlet
注:command+下方向键查看源码,可得
实际代码:
import java.io.PrintWriter;
public class HelloServlet extends HttpServlet {
//由于get和post只是请求实现的不同方式,可以相互调用,业务逻辑都一样,写一个就行。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入了doGet方法");
//ServletOutputStream outputStream = resp.getOutputStream();
PrintWriter writer = resp.getWriter();//响应流
writer.println("hello servlet");
//super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doPost(req, resp);
}
}
7)编写servlet的映射:
why:写的是java程序,但是要通过浏览器访问,而浏览器需要连接web服务器,所以需要在web服务中注册我们写的servlet,还需要给他一个浏览器能够访问的路径。
方法:打开web.xml,添加如下代码
<!--注册servlet-->
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.yyz.servlet.HelloServlet</servlet-class>
</servlet>
<!--servlet请求(映射)路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
8)配置Tomcat
注意配置项目发布的路径:
9)启动
注:输入 http://localhost:8080/s1/hello可访问之前映射的路径。
3. Servlet原理
servlet是由web服务器调用,web服务器在收到浏览器请求之后,会:
关于映射路径:
一个请求可以指定一个映射路径
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
一个请求可以指定多个映射路径:类似上面的增加,或/*
<!--servlet默认请求,进servlet而不是jsp文件,尽量不要这么写-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!--可以自定义后缀实现请求-->
<!--url:localhost:8080/s1/xxxxx.yyz
注意:*前面不能加映射的路径-->
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>*.yyz</url-pattern>
</servlet-mapping>
优先级问题:指定了固有的映射路径优先级最高,如果找不到,就会走默认的处理请求。
<!--404-->
<!--注册servlet-->
<servlet>
<servlet-name>error</servlet-name>
<servlet-class>com.yyz.servlet.ErrorServlet</servlet-class>
</servlet>
<!--servlet请求(映射)路径-->
<servlet-mapping>
<servlet-name>error</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
4. ServletContext
web容器在启动的时候,会为每个web程序都创建一个对应的servletcontext对象,它代表了当前的web应用。如:
–>应用1:共享数据(后面用session,request做):我在一个servlet中保存的数据能在另一个servlet中拿到。它凌驾于众多servlet之上。
1)先新建一个包含context对象的类,用来放置数据。并且在web.xml中加入映射。
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//System.out.println("hello servlet");
//servlet上下文
ServletContext context =this.getServletContext();
String username = "公孙离";
//将一个数据保存在ServletContext中,以键值对的形式
context.setAttribute("username",username);//key-value
}
}
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.yyz.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
2)然后再建立一个读取context的类。同样在web.xml中加入映射。
public class GetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//数据
//和HelloServlet里的共用context
ServletContext context = this.getServletContext();
//object强转到string
String username = (String) context.getAttribute("username");
//响应
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
resp.getWriter().print("名字:"+username);
}
}
<servlet>
<servlet-name>getc</servlet-name>
<servlet-class>com.yyz.servlet.GetServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getc</servlet-name>
<url-pattern>/getc</url-pattern>
</servlet-mapping>
3)测试访问结果,先访问hello(存储context),再访问getc即可看到读取数据。
–>应用2:获取初始化参数(一般不用):
web.xml文件中
<!--配置一些web应用的初始化参数-->
<!--JDBC的mybatis框架-->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/mybatis</param-value>
</context-param>
使用
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//url是jdbc的key
String url = context.getInitParameter("url");//
resp.getWriter().println(url);
}
–>应用3:请求转发(后面用request做),可以转发url
当A不能直接获取C的信息,可以从B中转
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext context = this.getServletContext();
//请求转发,转发地址
//RequestDispatcher requestDispatcher = context.getRequestDispatcher("/gp");//转发的请求路径
//requestDispatcher.forward(req,resp);//转发(request进, response出)
context.getRequestDispatcher("/gp").forward(req,resp);
}
与请求转发对应的是重定向:
–>应用4:读取资源文件(后面用类加载,反射)
Properties: 在java目录下,resources(用这个)目录下新建properties,发现在target文件夹中都被打包到了同一个路径classes下,称为类路径。
思路:需要一个文件流(db.properties):
username=root
password=123456
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//读取properties文件信息,在target可以找到路径
InputStream is = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
Properties properties = new Properties();
properties.load(is);
//存在db.properties中的信息
String username = (String) properties.get("username");
String password = (String) properties.get("password");
resp.getWriter().print(username+":"+password);
}
5. HttpServletResponse
web服务器接收到客户端的http请求,针对这个请求,分别创建一个代表请求的HttpServletRequest对象,和代表响应的HttpServletResponse对象。
如果要获取客户端请求过来的参数,找HttpServletRequest
如果要给客户端响应信息,找HttpServletResponse
1)简单分类
负责向浏览器发送数据的方法:
//平常的流
public ServletOutputStream getOutputStream() throws IOException;
//一般中文用getwriter
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);
public void setDateHeader(String name, long date);
public void addDateHeader(String name, long date);
public void setHeader(String name, String value);
public void addHeader(String name, String value);
public void setIntHeader(String name, int value);
public void addIntHeader(String name, int value);
响应的状态码:200,3xx,4xx,5xx
2)常见应用
- 向浏览器输出消息(前面已经提过)
- 下载文件:
1)要获取下载的文件路径
2)下载的文件名是什么
3)设置:让浏览器能够支持下载需要的文件
4)获取下载文件的输入流
5)创建缓冲区(三部曲)
6)获取outputstream对象(三部曲)
7)将fileoutputstream流写到缓冲区(三部曲)
8)将缓冲区中的输出输出到客户端
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1)要获取下载的文件路径
//getRealPath的路径是get/Users/mac/Environment/tomcat-9.0.40/webapps/r/,所以用绝对路径
//String realPath = this.getServletContext().getRealPath("/2.png");
String realPath = "/Users/mac/IdeaProjects/Servlet/javaweb-03-servlet/response/target/classes/啦啦啦.png";
System.out.println("下载路径:"+realPath);
//2)下载的文件名是什么
//注意:在mac中就是/,但是在windows中,文件路径是\,\是转译符,要再加上一个\,\\才是\:
String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
//3)设置:让浏览器能够支持(Content-Disposition)下载需要的文件
//https://blog.csdn.net/weixin_33747129/article/details/93745005
//设置响应的消息头
resp.setContentType("text/html;charset=UTF-8");
//设置响应类型中包含文件附件。中文文件名URLEncoder.encode编码,否则可能乱码。
resp.setHeader("Content-Disposition", "attachment; " + "filename="+ URLEncoder.encode(fileName,"utf-8"));
//4)获取下载文件的输入流
FileInputStream in = new FileInputStream(realPath);
//5)创建缓冲区(三部曲)
int len = 0;
byte[] buffer = new byte[1024];
//6)获取OutputStream对象(三部曲)
ServletOutputStream out = resp.getOutputStream();
//7)将FileOutputStream流写到缓冲区(三部曲)
//8)将缓冲区中的输出输出到客户端
while((len=in.read(buffer))>0){
out.write(buffer,0,len);
}
//9)关闭流,保证安全
in.close();
out.close();
}
3)验证码功能
验证可有前端或后端实现。
后端实现需要用到java的图片类,生成一个图片。
4)重定向(重点)
一个web资源收到客户端A的请求,B会通知A客户端去访问另外一个web资源C。
常见场景:用户登录。
重定向和转发的区别:
相同点:都可以实现页面跳转
不同点:请求转发的时候url不会产生变化(307),但是重定向会发生变化(302)。
应用:
//后端
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入doGet");
//处理请求
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println(username+":"+password);
//重定向一定要注意路径问题,否则会出现404
resp.sendRedirect("/r/success.jsp");
}
<%--这里提交的路径,需要寻找到项目的类路径(/login,但前端不明白/),用${} --%>
<form action="${pageContext.request.contextPath}/login" method="get">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type ="submit">
</form>
6. HttpServletRequest
HttpServletRequest代表客户端的请求,用户通过http协议访问服务器,,http请求中的所有信息会被封装到HttpServletRequest,我们通过HttpServletRequest的方法获得客户端所有信息。
应用场景:获取前端传进的参数,请求转发
public String getParameter(String name);
public String[] getParameterValues(String name);
<div style="text-align: center">
<%--pageContext在导入的jsp包里--%>
<%--以post方式提交到login请求--%>
<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="a">a
<input type="checkbox" name="hobby" value="b">b
<input type="checkbox" name="hobby" value="c">c
<input type="checkbox" name="hobby" value="d">d
<br>
<input type="submit">
</form>
</div>
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
String[] hobby = req.getParameterValues("hobby");
System.out.println("=======================");
System.out.println(username);
System.out.println(password);
System.out.println(Arrays.toString(hobby));
System.out.println("=======================");
//通过请求转发
//因为/在request里会自动被识别成当前web应用目录,所以不用加 req.getContextPath()
req.getRequestDispatcher("/success.jsp").forward(req,resp);
System.out.println(req.getContextPath());
7. Cookie和Session
7.1 会话
会话:用户打开一个浏览器没惦记了很多超链接,访问多个web资源,关闭浏览器这个过程,称为会话。
有状态会话:
服务端给客户端一个信件,客户端访问服务端带上信件即可:cookie
服务器登记你来过了,下次你来的时候,我来匹配:session
7.2 保存会话的两种技术
cookie:客户端技术(响应,请求)
session:服务器技术(可以保存用户的会话信息),我们可以把信息或者数据放在session中。
常见场景:网站登录之后,下次不用再登陆了。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//服务器告诉你你来的时间,把这个时间封装成一个信件,你下次来,服务器就知道你来了。
//解决中文乱码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
PrintWriter out = resp.getWriter();
//Cookie,服务器从客户端获取。
Cookie[] cookies = req.getCookies();//这里返回数组,说明cookie可能存在多个
//判断cookie是否存在
if(cookies!=null){
out.write("上一次访问的时间是:");
for(int i = 0; i<cookies.length; i++){
Cookie cookie = cookies[i];
//获取cookie的名字
if(cookie.getName().equals("lastLoginTime")){
//获取cookie中的值
long lastLoginTime = Long.parseLong(cookie.getValue());
Date date = new Date(lastLoginTime);
out.write(date.toLocaleString());
}
}
}else{
out.write("这是您第一次访问本站");
}
//服务器给客户端响应一个cookie:
Cookie cookie = new Cookie("lastLoginTime",String.valueOf(System.currentTimeMillis()));
cookie.setMaxAge(24*60*60);//cookie有效期为一天
resp.addCookie(cookie);
}
7.3 Cookie
从请求中拿到cookie信息
服务器响应给客户端cookie
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//服务器告诉你你来的时间,把这个时间封装成一个信件,你下次来,服务器就知道你来了。
//解决中文乱码
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
PrintWriter out = resp.getWriter();
//第一步:服务器从客户端获取Cookie
Cookie[] cookies = req.getCookies();//这里返回数组,说明cookie可能存在多个
//判断cookie是否存在
if(cookies!=null){
out.write("上一次访问的时间是:");
for(int i = 0; i<cookies.length; i++){
Cookie cookie = cookies[i];
//第二步:获取cookie的名字(key)
if(cookie.getName().equals("lastLoginTime")){
//第三步:获取cookie中的值
long lastLoginTime = Long.parseLong(cookie.getValue());
Date date = new Date(lastLoginTime);
out.write(date.toLocaleString());
}
}
}else{
out.write("这是您第一次访问本站");
}
//第四步:服务器给客户端响应一个cookie:
Cookie cookie = new Cookie("lastLoginTime",String.valueOf(System.currentTimeMillis()));
//第五步:设置cookie的有效期
cookie.setMaxAge(24*60*60);//cookie有效期为一天
//第六步:响应给客户端一个cookie
resp.addCookie(cookie);
}
cookie一般会保存在用户目录下appdata的文件下。
一个cookie只能保存一个信息
一个web站点可以给浏览器发送多个cookie,每个站点最多存20个。
cookie大小限制为4kb
300个cookie浏览器上限
删除cookie:
1)不设置有效期,关闭浏览器,自动失效。
2)设置有效期时间为0。(新建另一个demo文件)
//解决乱码:编码
Cookie cookie = new Cookie("name",URLEncoder.encode("公孙离","utf-8"));
//对应的在取cookie时候,解码
URLDecoder.decode(cookie.getValue(),"utf-8");
7.4 Session(重点)
服务器会给每一个用户(浏览器)创建一个Session对象。
一个session独占一个浏览器,只要浏览器没有关闭,这个session就存在。
用户登录之后,整个网站它都可以访问。 它保存用户的信息,保存购物车的信息,等等。
session和cookie的区别:
cookie是把用户的数据写给用户的浏览器,浏览器保存(可有多个)。
session是把用户的数据写到用户独占的session中,服务器保存(保存重要信息,减少服务器资源的浪费)。
session对象由服务器创建。
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
resp.setCharacterEncoding("utf-8");
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");//响应回去是html页面
//得到session
HttpSession session = req.getSession();
//给session中存东西,session存(string, object),而cookie(string, string)
session.setAttribute("name",new Person("公孙离",15));
//获取session的id
String sessionID = session.getId();
//判断session是不是新创建的
if (session.isNew()){
resp.getWriter().write("session创建成功,ID:"+ sessionID);
} else{
resp.getWriter().write("session已经在服务器中存在了,ID:"+ sessionID);
}
//session创建的时候做了什么事
//Cookie cookie = new Cookie("JSESSIONID", sessionID);
//resp.addCookie(cookie);
}
//跨servlet获取信息
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决乱码问题
resp.setCharacterEncoding("utf-8");
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");//响应回去是html页面
//得到session
HttpSession session = req.getSession();
//给session中存东西,session存(string, object),而cookie(string, string)
Person person = (Person) session.getAttribute("name");
System.out.println(person);
}
删除session方法1:手动注销
//删除session
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到session
HttpSession session = req.getSession();
session.removeAttribute("name");//取消
session.invalidate();//注销,sessionId就没了,但会立刻生成一个新的
}
删除session方法2:在web.xml中配置(二者可同时使用)
<!--设置session失效时间-->
<servlet>
<servlet-name>SessionDemo03</servlet-name>
<servlet-class>com.yyz.servlet.SessionDemo03</servlet-class>
</servlet>
<servlet-mapping>
<!--15分钟后session自动失效-->
<servlet-name>SessionDemo03</servlet-name>
<url-pattern>/s3</url-pattern>
</servlet-mapping>
session使用场景:
1)保存一个用户的登陆信息
2)购物车信息
3)在整个网站中,经常会使用的数据,我们将它保存在session里。
cookie:
session: