Servlet
0 为什么还看Servlet
2020年了,为什么还是看Servlet???首先这是一个必经的阶段,当初开始学习Java Web的时候,这部分就是重点,第二就是在学习了一些更加高级的框架时,还是时不时会看到它的身影,像Spring等,在学习他的源码的时候就可以看到它维护的DispatcherServlet,所以不要再问为什么2020还看这么土的东西??
当然还有一个问题就是要不要看JSP,这个个人认为简单了解即可,现在企业中的开发基本都是前后端分离的模式,就算是简单的搭建,Freemarker、Thymeleaf模板语言也是更加的方便,so 如果你有空,那么你可以去看看,但是不需要太过深究
1 基本概念
Servlet是一个接口,定义了servlet容器识别Java程序的规范
2 基础实现
实现Servlet接口
最基础的servlet实现,直接实现servlet接口,重写他的5个方法,同时需要在web.xml中配置这个servlet,servlet是根据web.xml中的配置找到对应url的处理者
public class HelloServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init ...");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service ...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy ...");
}
}
<?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_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.learn.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
继承HttpServlet
Servlet3.0
注意⚠️:Servlet3.0是从J2EE 6开始才支持的哦,主要是用于简化开发,不用再繁琐的xml配置了,所以可以直接不使用web.xml
使用方法很简单,直接在servlet上吗使用@WebServlet注解即刻,通过查看源码可以知道,原来配置在web.xml中的配置全部都可以挪到注解的值中
@WebServlet(name = "hello3", urlPatterns = {"/hello3"})
public class HelloServlet3 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("servlet 3.0 ...");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";
String[] value() default {};
String[] urlPatterns() default {};
int loadOnStartup() default -1;
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
3 深入学习
生命周期
Servlet中有五个方法,其中有3个和生命周期有关
- init 初始化:Servlet被创建的时候执行,只执行一次
- service 服务:Servlet被访问时执行,每次访问都会执行
- destroy 摧毁:当容器正常关闭时执行,只执行一次
Servlet默认情况下,是在初次被访问时创建,也可以通过配置load-on-startup(默认情况为负数,开启则配置为0或正整数)来使其随服务器的启动而创建,创建时会执行init方法,由此也可以看出Servlet是一个单例对象,所以在多个用户访问的时候会存在线程安全问题,所以尽量不要在Servlet中使用成员变量或尽量不要修改Servlet的成员变量。
destroy方法是只有在容器正常关闭时才会去执行,注意是正常关闭,且它是先于容器关闭而执行,所以一般用它来释放资源。
继承体系
- GenericServlet实现了Servlet接口,是对于Servlet接口基础的封装抽象类,继承者必须需要重写service方法,同时可以根据自身需要重写其他4个方法
HttpServlet继承自GenericServlet,是在http协议的基础上对Servlet进行进一步封装,其中常用的doGet和doPost根据http method的不同按需使用,简化开发
@WebServlet("/http") public class HelloHttpServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("do get ..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("do post ..."); } }
mapping的通配符
- 一个serlvet可以配置多个映射
规则:
- /xxx
- /xxx/xxx
- xxx.xxx
- 其中( )是代表任意,注意( ). xxx 这种写法不能和 / 一起用
- 访问的优先级是越匹配越优先
- 只有(/)的匹配称为缺省匹配,其他都找不到了就匹配这种
4 Request
Request对象的本质
Servlet在tomcat中是通过反射机制创建的对象,传递来的Request对象实际上是实现了HttpRequestServlet的RequestFacade,此对象由tomcat提供(门面模式)
常用的API
获取请求行
假设一个请求的请求行是 GET /hello/abc?name=123 HTTP/1.1
- request.getMethod 获取GET
- request.getContextPath 获取/hello
- request.getServletPath 获取/abc
- request.getQueryString 获取name=123
- request.getRequestURI 获取/hello/abc
- request.getRequestURL 获取http://localhost:8080/hello/abc
- request.getProtocal 获取 HTTP/1.1
- request.getRemoteAddr 获取客户机的ip地址
获取请求头
request.getHeader(key) 通过请求头名称获取请求头信息
防盗链
- 所谓防盗链是指防止其他web站点页面通过连接本站点的页面来访问本站点内容,这样对于本站点来说侵犯了本站点的版权
request.getHeader("referer")可以获取来源,当值为null代表浏览器地址栏直接输入访问,通过对于域名的匹配可以达到防盗链的效果
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("do get ..."); System.out.println(req.getHeader("referer")); String referer = req.getHeader("referer"); if (referer == null) { System.out.println("来自浏览器的地址直接访问"); }else if (referer.contains("localhost:8080")) { System.out.println("本地访问"); }else { System.out.println("不知道什么鬼地方访问的"); } }
- request.getHeaderNames 获取所有请求头的名称
获取请求体
- request.getReader 获取请求体(字符流)
- request.getInputStream 获取请求体(字节流)用于文件上传
获取请求参数
request.getRequestParam(name) 根据名字获取参数
- request.getParameterValues(name) 根据名字获取参数数组
- request.getParameterNames 获取所有请求参数的名字
request.getParamterMap 获取所有请求参数 k-v,封进一个map
- 中文乱码是因为页面传输的流编码格式经过tomcat的接受转换不一致导致的,页面传上来是utf-8,tomcat内部是iso编码,再输出到控制台utf-8就乱了,所以需要在读取参数前先转换一下request的编码,直接使用req.setCharacterEncoding("utf-8");即可
请求转发
- request..getRequestDispatcher("目标路径").forward(req, resp);
- 特点 1、服务器重定向浏览器无感 2、一次请求 3、只能重定向到内部资源 4、访问的http method = 转发的 http method
域对象
- 范围就是一次请求,服务器内请求转发的时候可以传递数据
- request.setAttribute("key", value) 向域对象里面存值
- request.getAttribute("key") 从域对象里面取值,取不到就是null
- request.removeAttribute("key") 从域对象里面移除key对应的值
获取ServletContext对象
- request.getServletContext
5 Response
常用API
设置响应头
- response.setHeader(key, value)
设置响应体
获取输出流
- 字节流 response.getWriter
- 字符流 response.getOutputStream
- 使用输出流输出数据
重定向
- 设置 http code 302,提示浏览器进行客户端重定向
- 设置重定向地址
- 特点:1、在客户端浏览器进行 2、实际发起了两次请求 3、可以访问任意资源
实战-验证码
其实就是利用绘图工具绘制一张二维码的图片,再通过response的字节输出流,输出图片对象
@WebServlet("/verify")
public class VerifyCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedImage bufferedImage = new BufferedImage(100, 50, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bufferedImage.getGraphics();
graphics.setColor(Color.PINK);
graphics.fillRect(0,0,100, 50);
graphics.setColor(Color.BLUE);
graphics.drawRect(0,0,100-1,50-1);
// 这里可以自定义验证码内容
String i = String.valueOf(new Random().nextInt(100));
graphics.drawString(i, 50, 25);
ImageIO.write(bufferedImage, "jpg", resp.getOutputStream());
}
}
6 ServletContext
域对象
ServletContext域对象是在容器启动时便创建,对于所有项目中的Servlet共享,无论是request对象获取的servlet context还是GenericServlet抽象父类提供的方法,获取的都是同一个对象,域对象存取数据方法和request对象一样
- servletContext.setAttribute("key", value) 向域对象里面存值
- servletContext.getAttribute("key") 从域对象里面取值,取不到就是null
- servletContext.removeAttribute("key") 从域对象里面移除key对应的值
获取MIME-TYPE
- servletContext.getMimeType(文件绝对路径)
- Mime-type的格式:大类型/小类型
- 作用:浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理URL,因此Web服务器在响应头中添加正确的MIME类型非常重要。如果配置不正确,浏览器可能会曲解文件内容,网站将无法正常工作,并且下载的文件也会被错误处理。
@WebServlet("/context")
public class ContextServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 这两个获取的是同一个
ServletContext servletContext = req.getServletContext();
// ServletContext servletContext1 = getServletContext();
File file = new File(servletContext.getRealPath("/hehe.html"));
String mimeType = servletContext.getMimeType(file.getName()); // 输出:text/html
System.out.println(mimeType);
}
}
获取文件正式路径
- servletContext.getRealPath(项目文件相对路径)
- 其实就是tomcat的虚拟目录地址+相对地址
实战-文件下载
其实步骤很简单
1、告诉浏览器要下载一个文件 resp.setHeader("content-disposition", "attachment;filename=你好.jpg");
2、把要下载的文件加载进入resp的字节输出流中
@WebServlet("/download")
public class DownloadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("utf-8");
String fileName = req.getParameter("fileName");
String realPath = getServletContext().getRealPath(fileName);
resp.setHeader("content-disposition", "attachment;filename=你好.jpg");
ImageIO.write(ImageIO.read(new File(realPath)), "jpg", resp.getOutputStream());
}
}
7 ServletConfig
运行servlet时可能会要一些辅助的信息,ServletConfig提供了这个空间去存这些信息
对于传统的xml方式的配置,在servlet标签中添加init-param标签即可
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.learn.servlet.HelloServlet</servlet-class>
<init-param>
<param-name>hello</param-name>
<param-value>this is hello value</param-value>
</init-param>
</servlet>
对于更加简单方便的注解,使用方式也是更加简单
@WebServlet(urlPatterns = "/config", initParams = {@WebInitParam(name = "abc", value = "123")})
注意⚠️:servlet config的配置仅仅在配置的servlet中有效哦
servlet获取配置的方法和servlet context类似
@WebServlet(urlPatterns = "/config", initParams = {@WebInitParam(name = "abc", value = "123")})
public class ConfigServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
String abc = servletConfig.getInitParameter("abc");
System.out.println(abc);
}
}
8 会话技术
一次会话中包含着多次请求和响应,所谓一次会话就是浏览器第一次和服务端发请求,此时会话建立,知道有一方关闭,此次会话才停止。会话技术就是在这次会话内共享数据,方式主要有二 1、cookie 2、session
9 cookie
基本操作
新建cookie对象
Cookie cookie = new Cookie("abc", "123");
向客户端设置cookie
resp.addCookie(cookie);
获取客户端cookie
Cookie[] cookies = req.getCookies();
cookie的本质
本质就是利用了http header的add-cookie和cookie,服务端设置cookie本质就是在header中加上add-cookie,服务端取出cookie也就是解析header中cookie的值
多个cookie
允许同时存在多个cookie,只要多次addCookie即可
Cookie的有效时间
- cookie.setMaxAge(seconds) 存放秒值
- seconds为正数代表存活的时间,负数为默认即关闭会话就清楚,0代表立即清除
Cookie共享问题
- 默认情况下cookie是不共享的
- cookie.setPath 可以设置项目内的cookie共享,如setPath("/abc"),那么uri中/abc下的都可以共享
- cookie.setDomain 可以设置域名共享,如设置了 .baidu.com 那么,xxx.baidu.com都可以共享这个cookie
Cookie特殊符号问题
- 有个特别的地方,cookie不支持特殊符号,所以使用时最好先对其进行转码 URLEncoder.encode,取值时别忘了解码URLDecoder.decode
实战-记住上次登录时间
实现思路比较简单,程序开始直接获取cookie,并检索获取到的cookie中是否存在我们设置过的cookie,有代表曾经登录过,没有代表初次登录
@WebServlet("/ct")
public class CookieTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie item: cookies) {
if (item.getName().equals("loginTime")) {
// 取出上次的时间
String lastLoginTime = URLDecoder.decode(item.getValue(), "utf-8");
// 存入当前的时间
item.setMaxAge(60 * 60 * 24 * 30);
item.setValue(URLEncoder.encode(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")\
.format(new Date()),"utf-8"));
resp.addCookie(item);
resp.getWriter().println("上次登录时间:" + lastLoginTime);
return;
}
}
}
Cookie cookie = new Cookie("loginTime",
URLEncoder.encode(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date()),"utf-8"));
cookie.setMaxAge(60 * 60 * 24 * 30);
resp.addCookie(cookie);
resp.getWriter().println("初次登录,欢迎您");
}
}
10 Session
基本操作
和cookie类似
- 获取session对象:request.getSession
- session域对象使用:session.setAttribute | session.getAttribute | session.removeAttribute
session实现原理
session实现本质是通过cookie来实现的,可以看到会话建立完成后,服务器会在内存中创建session对象,并会在响应中添加name为JSESSIONID的cookie,代表本次会话的对象标识。在同一次会话过程中,浏览器访问就会带上这个JSESSIONID进行请求,服务端接受之后通过对比ID与内存中的对象,判断本次会话是否和之前的相同。
session存活时间
使用tomcat容器,session的默认存活时间时30分钟,默认的配置是在tomcat/conf/web.xml中进行配置,用户可以修改其中的session-config手动对其修改
客户端关闭后,下次请求的session是否还相同
默认情况下,客户端关闭,由于cookie的特性,cookie会随着浏览器关闭而清除,所以不相同;但是我们了解了session的基本原理之后,可以通过手动设置JSESSIONID cookie的存活时间,达到缓存cookie的效果,那么下次在打开浏览器的请求就可以保持同一个session
服务端关闭后,下次请求的session是否还相同
不做任何处理的情况下,服务端关闭,内存中的session对象自然就不存在,所以不可能相同。要保持session,需要进行session的钝化,即在服务端关闭之前持久化存储session对象信息,并在服务端重启时进行session活化,即将钝化的session重新加载进入内存。
我们常用的tomcat容器为我们提供了这一个功能
session和cookie的对比
- session是服务端会话技术,cookie是客户端会话技术
- session中的值类型可以任意,大小可以任意;cookie只能存字符值,大小受到浏览器的限制
- session由于存放在服务端内存中相对安全;cookie直接由客户端浏览器进行管理不太安全
11 Filter
基本使用
用法十分简单,通过实现Filter接口,重写三个方法即可,其中主要的过滤方法就是doFilter
@WebFilter(urlPatterns = "/hello3")
public class FilterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 随服务器启动而创建
System.out.println("init ... ");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter ...");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
// 随服务器关闭而销毁
System.out.println("destroy ...");
}
}
生命周期
- init 随服务器启动而创建
- destroy 随服务器关闭而销毁
多个Filter
可以对于同一个访问地址配置多个Filter,构成所谓的过滤器链,在访问到达servlet的具体方法前,先依次由外而内的通过filter的过滤(filterChain.doFilter之前的语句),结束servlet的service方法后,再由内而外的执行一次filter过滤(filterChain.doFilter之后的语句)
拦截顺序
分为两种情况:
- xml配置,则根据配置顺序的不同,配置在上面的先执行,再一次向下
- 注解配置,根据Filter类的类名的首字母排序按顺序执行(有点坑)
FilterConfig
作用同ServletConfig,用法更是一样,不再过多赘述,都是在xml中配置init-param或者在注解中配置,并只能在对应的filter中才能获取。
Dispatcher
默认情况下,过滤器只过滤请求,如果要过滤转发或其他的一些情况,也想拦截时需要配置这个参数
- REUQEST 默认,代表过滤请求
- FORWARD 代表过滤转发
- INCLUDE 代表如果页面被include标签引用,会进行过滤(用的少)
- ERROR 代表出现全局错误需要跳转至错误页时会进行拦截(用的少)
12 JSP & Listener等等。。。
关于JSP&Listener或者一些细枝末节我不做过多的探究,因为这部分对于后续的学习没有更多的帮助,因为确实用的实在太少了,后续研究框架的源码也很少会看到他们,所以算了算了,哈哈哈哈主要还是懒
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。