知识点梳理:2020还看Servlet?

Servlet

0 为何还看Servlet

2020年了,为何仍是看Servlet???首先这是一个必经的阶段,当初开始学习Java Web的时候,这部分就是重点,第二就是在学习了一些更加高级的框架时,仍是时不时会看到它的身影,像Spring等,在学习他的源码的时候就能够看到它维护的DispatcherServlet,因此不要再问为何2020还看这么土的东西?? html

固然还有一个问题就是要不要看JSP,这个我的认为简单了解便可,如今企业中的开发基本都是先后端分离的模式,就算是简单的搭建,Freemarker、Thymeleaf模板语言也是更加的方便,so 若是你有空,那么你能够去看看,可是不须要太过深究java

1 基本概念

Servlet是一个接口,定义了servlet容器识别Java程序的规范web

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的成员变量tomcat

destroy方法是只有在容器正常关闭时才会去执行,注意是正常关闭,且它是先于容器关闭而执行,因此通常用它来释放资源。安全

继承体系

image-20200528224632146

  • 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);
    • 特色 一、服务器重定向浏览器无感 二、一次请求 三、只能重定向到内部资源 四、访问的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,提示浏览器进行客户端重定向
  • 设置重定向地址
  • 特色:一、在客户端浏览器进行 二、实际发起了两次请求 三、能够访问任意资源

实战-验证码

其实就是利用绘图工具绘制一张二维码的图片,再经过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的虚拟目录地址+相对地址

实战-文件下载

其实步骤很简单

一、告诉浏览器要下载一个文件 resp.setHeader("content-disposition", "attachment;filename=你好.jpg");

二、把要下载的文件加载进入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 会话技术

一次会话中包含着屡次请求和响应,所谓一次会话就是浏览器第一次和服务端发请求,此时会话创建,知道有一方关闭,这次会话才中止。会话技术就是在此次会话内共享数据,方式主要有二 一、cookie 二、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或者一些细枝末节我不作过多的探究,由于这部分对于后续的学习没有更多的帮助,由于确实用的实在太少了,后续研究框架的源码也不多会看到他们,因此算了算了,哈哈哈哈主要仍是懒

相关文章
相关标签/搜索