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)@Documentedpublic @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或者一些细枝末节我不做过多的探索,因为这部分对于后续的学习没有更多的帮忙,因为的确用的切实太少了,后续钻研框架的源码也很少会看到他们,所以算了算了,哈哈哈哈次要还是懒