乐趣区

关于java:知识点梳理2020还看Servlet

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 个和生命周期无关

  1. init 初始化:Servlet 被创立的时候执行,只执行一次
  2. service 服务:Servlet 被拜访时执行,每次拜访都会执行
  3. 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 的通配符

  1. 一个 serlvet 能够配置多个映射
  2. 规定:

    1. /xxx
    2. /xxx/xxx
    3. xxx.xxx
    4. 其中()是代表任意,留神(). xxx 这种写法不能和 / 一起用
    5. 拜访的优先级是越匹配越优先
    6. 只有(/)的匹配称为缺省匹配,其余都找不到了就匹配这种

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 的比照

  1. session 是服务端会话技术,cookie 是客户端会话技术
  2. session 中的值类型能够任意,大小能够任意;cookie 只能存字符值,大小受到浏览器的限度
  3. 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 之后的语句)

拦挡程序

分为两种状况:

  1. xml 配置,则依据配置程序的不同,配置在下面的先执行,再一次向下
  2. 注解配置,依据 Filter 类的类名的首字母排序按程序执行(有点坑)

FilterConfig

作用同 ServletConfig,用法更是一样,不再过多赘述,都是在 xml 中配置 init-param 或者在注解中配置,并只能在对应的 filter 中能力获取。

Dispatcher

默认状况下,过滤器只过滤申请,如果要过滤转发或其余的一些状况,也想拦挡时须要配置这个参数

  • REUQEST 默认,代表过滤申请
  • FORWARD 代表过滤转发
  • INCLUDE 代表如果页面被 include 标签援用,会进行过滤(用的少)
  • ERROR 代表呈现全局谬误须要跳转至谬误页时会进行拦挡(用的少)

12 JSP & Listener 等等。。。

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

退出移动版