为您推荐:
精华内容
最热下载
问答
  • 5星
    3.04MB qq_34715954 2021-06-05 13:33:35
  • 5星
    7.17MB helongqiang 2021-06-24 00:31:58
  • 5星
    6.59MB helongqiang 2021-06-23 22:24:41
  • 5星
    12.14MB helongqiang 2021-08-12 20:41:19
  • 5星
    101.9MB helongqiang 2021-06-18 20:55:28
  • 5星
    4.48MB helongqiang 2021-07-16 21:42:42
  • 5星
    16.2MB weixin_45750628 2021-01-14 22:14:27
  • 5星
    23.3MB u014388322 2021-07-05 16:38:23
  • 5星
    15.07MB z18223345669 2021-01-11 19:37:48
  • 5星
    14.48MB tangyang8942 2021-06-26 20:54:35
  • Servlet1.Servlet简介1.1 Servlet在web应用中的位置2.Servlet 执行过程3.Servlet接口实现类4.Servlet的一些细节4.1 访问web服务器中的资源4.2 相关格式4.3 映射关系4.4 其它5.Servlet 生命周期6. Servlet 常见对象...

    1.Servlet简介

    动态web资源:数据是时刻变化的,不同的用户、不同的时间看到的数据都有可能完全不相同。

    Servlet合成词。

    Servlet = Server + applet。运行在服务器上面的一个小程序。

    A servlet is a small Java program that runs within a Web server.
    servlet接口里面定义了三个生命周期函数。init、service、destroy

    这三个函数分别会在servlet的生命周期各个阶段被执行到。

    读书、工作、结婚。

    servlet在生命周期的创建阶段会调用init方法

    servlet在整个生命周期服务阶段会调用service方法

    如果servlet死亡,那么会调用destroy方法。

    可以在这些方法里面做一些特定的事情。
    在这里插入图片描述

    1.1 Servlet在web应用中的位置

    在这里插入图片描述

    2.Servlet 执行过程

    1. 客户端发出请求http://localhost:8080/servlet/first
    2. 服务器对应的应根据web.xml文件的配置,找到子元素的值“/first”的**元素**
    3. 读取元素的子元素的值,由此确定Servlet的名字为”first”
    4. 找到值为HelloServlet的元素
    5. 读取元素的子元素的值,由此确定Servlet的类名为cn.cskaoyan.HelloServlet
    6. 到Tomcat安装目录/webapps/Demo1/WEB-INF/classes/cn/cskaoyan目录下查找到HelloServlet.class文件
    7. 初始化,并执行这个类的 service方法

    Tip: Servlet的运行细节

    Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:

    1. Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
    2. 装载并创建该Servlet的一个实例对象
    3. 调用Servlet实例对象的init()方法
    4. tomcat创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去
    5. WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法
      第一步:
      在这里插入图片描述
      第二步:在这里插入图片描述
      第三步:在这里插入图片描述
      第四步:
      在这里插入图片描述

    第五步:
    在这里插入图片描述

    第六步:
    在这里插入图片描述
    第七步:
    在这里插入图片描述
    第八步:在这里插入图片描述

    3.Servlet接口实现类

    • Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServletHttpServlet
    • HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
    • HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式。
      [1] 如为GET请求,则调用HttpServlet的doGet方法
      [2] 如为Post请求,则调用doPost方法

    4.Servlet的一些细节

    4.1 访问web服务器中的资源

    如果servlet的url-pattern存在着相互覆盖的情况,那么最终tomcat会调用哪个servlet来处理该请求呢?

    • 由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<.servlet>元素和<.servlet-mapping>元素完成。
    • <.servlet>元素用于注册Servlet,它包含有两个主要的子元素:<.servlet-name>和<.servlet-class>,分别用于设置Servlet的注册名称Servlet的完整类名
    • 一个<.servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<.servlet-name>和<.url-pattern>,分别用于指定Servlet的注册名称Servlet的对外访问路径
      eg:
    <web-app>
    	<servlet>
    		<servlet-name>AnyName</servlet-name>
    		<servlet-class>HelloServlet</servlet-class>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>AnyName</servlet-name>
    		<url-pattern>/demo/hello.html</url-pattern>
    	</servlet-mapping>
    </web-app>
    

    4.2 相关格式

    同一个Servlet可以被映射到多个URL上,即多个<.servlet-mapping>元素的<.servlet-name>子元素的设置值可以是同一个Servlet的注册名。
    在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式: /xxxxxxxx /abc/efg/dddd
    *.后缀。
    格式1:

    <servlet-mapping>
    	<servlet-name>
    		AnyName
    	</servlet-name>
    	<url-pattern>
    		*.do
    	</url-pattern>
    </servlet-mapping>
    

    格式2:

    <servlet-mapping>
    	<servlet-name>
    		AnyName
    	</servlet-name>
    	<url-pattern>
    		/action/*
    	</url-pattern>
    </servlet-mapping>
    

    4.3 映射关系

    • Servlet1 映射到 /abc/*
    • Servlet2 映射到 /*
    • Servlet3 映射到 /abc
    • Servlet4 映射到 *.do

    问题

    在这里插入图片描述
    在这里插入图片描述
    Servlet1.java

    public class Servlet1 extends GenericServlet {
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            //如果你想往浏览器的页面输出内容,可以使用该API
            servletResponse.getWriter().println("/abc/*");
        }
    }
    
    

    Servlet2.java

    public class Servlet2 extends GenericServlet {
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            //如果你想往浏览器的页面输出内容,可以使用该API
            servletResponse.getWriter().println("/*");
        }
    }
    
    

    Servlet3.java

    public class Servlet3 extends GenericServlet {
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            //如果你想往浏览器的页面输出内容,可以使用该API
            servletResponse.getWriter().println("/abc");
        }
    }
    
    

    Servlet4.java

    public class Servlet4 extends GenericServlet {
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            //如果你想往浏览器的页面输出内容,可以使用该API
            servletResponse.getWriter().println("*.do");
        }
    }
    

    在这里插入图片描述

    4.4 其它

    • 如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet
    • 凡是在web.xml文件中找不到匹配的<.servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,即缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
    • 在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。
    • 当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。 (服务器中的html文件数据的读取由缺省servlet完成)

    5.Servlet 生命周期

    • Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度
    • 针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象驻留在内存中,为后续的其它请求服务,直至web容器退出(或应用停止),servlet实例对象才会销毁。
    • 在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
    • 如果在<.servlet>元素中配置了一个<.load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
      eg:
    <servlet>
    		<servlet-name>invoker</servlet-name>
    		<servlet-class>
    			org.apache.catalina.servlets.InvokerServlet
    		</servlet-class>
    		<load-on-startup>2</load-on-startup>
    	</servlet>
    

    用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。

    提高Servlet容器运行性能:(Servlet规范为Servlet规定了不同的初始化情形),如下:

    • 如果有些Servlet专门负责在web应用启动阶段为web应用完成一些初始化操作,则可以让它们在web应用启动时就被初始化。
    • 对于大多数Servlet,只需当客户端首次请求访问时才被初始化。
    • 假设所有的Servlet都在web应用启动时被初始化,那么会大大增加Servlet容器启动web应用的负担,而且Servlet容器有可能加载一些永远不会被客户访问的Servlet,白白浪费容器的资源。

    6. Servlet 常见对象

    6.1 ServletConfig对象

    • 在Servlet的配置文件中,可以使用一个或多个<.init-param>标签为某个servlet配置一些初始化参数。
    • 当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。

    实际的Servlet开发中,可以直接通过getServletConfig()的到ServletConfig对象。

    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    
    • WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用
    • ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
    • 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context对象。

    6.2 ServletConfig对象

    • 获取WEB应用的初始化参数。(多个serlvet获取相同参数)
    • 多个Servlet通过ServletContext对象实现数据共享。
    • 实现Servlet的转发(request)。

    统计特定网页被客户端访问的次数:

    public class Counter {
    	private int count;
    	public Counter(int count){
    		this.count = count;
    	}
    	public Counter(){
    		this(0);
    	}
    	public int getCount() {
    		return count;
    	}
    	public void setCount(int count) {
    		this.count = count;
    	}
    	public void add(int step){
    		count+=step;
    	}
    }
    ----------------------------------------------
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		ServletContext context = getServletContext();
    		Counter counter = (Counter) context.getAttribute("counter");
    		if(counter==null){
    			counter = new Counter(1);
    			context.setAttribute("counter", counter);
    		}
     
    		response.getOutputStream().write(("count:"+counter.getCount()).getBytes());
    		counter.add(1);
    	}
    

    资源文件通常有两种方式:

    • 对于简单的资源文件,即包含key=value的形式,我们一般采用properties,这些文件的扩展名一般为*.properties。
    • 对于较复杂的资源文件,采用XML格式。
    • 通常资源文件放在src目录或者WEB-INF目录下。
    • 在web工程中,要获得某个文件的路径,我们一般都采取相对于web工程“/”的相对路径。
    • 在src下放置properites配置文件,利用
      Context.getRealpath(“’)
      context.getResourceAsStream("/WEB-INF/classes/db.properties")来读取。
    • 另外一种方式是采用ServeltContext的getRealPath()方法来获取某个文件的绝对路径。

    1、加载src下的属性资源文件
    InputStream in= ServletDemo.getClass().getClassLoader().getResourceAsStream(“db.properties”);
    2、加载与servlet同包中的资源属性文件
    InputStream in=ServletDemo.getClass().getClassLoader().getResourceAsStream(“cn/cskaoyan/db.properties”);

    展开全文
    gy99csdn 2021-02-25 15:23:22
  • servlet和jsp没什么不同只不过是jsp写在页面servlet写在后台 主要记住要在web.xml中配置servlet <servlet> <servlet-name> 自定义名 <servlet-name> <servlet-class> 具体类路径 <...

    servlet和jsp没什么不同只不过是jsp写在页面servlet写在后台
    主要记住要在web.xml中配置servlet

    <servlet>
    <servlet-name>
    自定义名
    <servlet-name>
    <servlet-class>
    具体类路径
    <servlet-class>
    <servlet-mapping>
    <servlet-name>
    自定义名字
    <servlet-name>
    <url-patten>
    具体类名
    </url-patten>
    </servlet-mapping>
    </servlet>
    
    

    创建servlet步骤
    创建一个类
    重构doget或dopost方法
    配置web.xml

    展开全文
    qq_47324233 2021-04-29 22:31:38
  • Servlet的调用图 前面我们已经学过了Servlet的生命周期了,我们根据Servlet的生命周期画出Servlet的调用图加深理解 Servlet的细节 一个已经注册的Servlet可以被多次映射 同一个Servlet可以被映射到多个URL上。 <...

    Servlet的调用图

    前面我们已经学过了Servlet的生命周期了,我们根据Servlet的生命周期画出Servlet的调用图加深理解
    在这里插入图片描述

    Servlet的细节

    一个已经注册的Servlet可以被多次映射
    同一个Servlet可以被映射到多个URL上。

            <servlet>
                <servlet-name>Demo1</servlet-name>
                <servlet-class>zhongfucheng.web.Demo1</servlet-class>
            </servlet>
            <servlet-mapping>
                <servlet-name>Demo1</servlet-name>
                <url-pattern>/Demo1</url-pattern>
            </servlet-mapping>
            <servlet-mapping>
                <servlet-name>Demo1</servlet-name>
                <url-pattern>/ouzicheng</url-pattern>
            </servlet-mapping>
    

    无论我访问的是http://localhost:8080/Demo1还是http://localhost:8080/ouzicheng。我访问的都是Demo1。
    在这里插入图片描述
    在这里插入图片描述

    Servlet映射的URL可以使用通配符

    通配符有两种格式:

    1. *.扩展名
    2. 正斜杠(/)开头并以“/*”结尾。

    匹配所有
    在这里插入图片描述
    匹配扩展名为.jsp的
    在这里插入图片描述
    如果.扩展名和正斜杠(/)开头并以“/”结尾两种通配符同时出现,匹配的是哪一个呢?

    1. 看谁的匹配度高,谁就被选择
    2. *.扩展名的优先级最低

    Servlet映射的URL可以使用通配符和Servlet可以被映射到多个URL上的作用:

    1. 隐藏网站是用什么编程语言写的【.php,.net,.asp实际上访问的都是同一个资源】
    2. 用特定的后缀声明版权【公司缩写】
            <servlet>
                <servlet-name>Demo1</servlet-name>
                <servlet-class>zhongfucheng.web.Demo1</servlet-class>
            </servlet>
            <servlet-mapping>
                <servlet-name>Demo1</servlet-name>
                <url-pattern>*.jsp</url-pattern>
            </servlet-mapping>
            <servlet-mapping>
                <servlet-name>Demo1</servlet-name>
                <url-pattern>*.net</url-pattern>
            </servlet-mapping>
            <servlet-mapping>
                <servlet-name>Demo1</servlet-name>
                <url-pattern>*.asp</url-pattern>
            </servlet-mapping>
            <servlet-mapping>
                <servlet-name>Demo1</servlet-name>
                <url-pattern>*.php</url-pattern>
            </servlet-mapping
    

    Servlet是单例的

    为什么Servlet是单例的

    浏览器多次对Servlet的请求,一般情况下,服务器只创建一个Servlet对象,也就是说,Servlet对象一旦创建了,就会驻留在内存中,为后续的请求做服务,直到服务器关闭。

    每次访问请求对象和响应对象都是新的
    对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。

    线程安全问题

    当多个用户访问Servlet的时候,服务器会为每个用户创建一个线程。当多个用户并发访问Servlet共享资源的时候就会出现线程安全问题。

    原则:

    1. 如果一个变量需要多个用户共享,则应当在访问该变量的时候,加同步机制synchronized (对象){}
    2. 如果一个变量不需要共享,则直接在 doGet() 或者 doPost()定义.这样不会存在线程安全问题

    load-on-startup

    如果在元素中配置了一个元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
    在这里插入图片描述
    在这里插入图片描述
    作用:

    1. 为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据
    2. 完成一些定时的任务【定时写日志,定时备份数据】

    在web访问任何资源都是在访问Servlet

    当你启动Tomcat,你在网址上输入http://localhost:8080。为什么会出现Tomcat小猫的页面?

    这是由缺省Servlet为你服务的!

    • 我们先看一下web.xml文件中的配置,web.xml文件配置了一个缺省Servlet
        <servlet>
            <servlet-name>default</servlet-name>
            <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
            <init-param>
                <param-name>debug</param-name>
                <param-value>0</param-value>
            </init-param>
            <init-param>
                <param-name>listings</param-name>
                <param-value>false</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
    
        <servlet-mapping>
            <servlet-name>default</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    • 什么叫做缺省Servlet?凡是在web.xml文件中找不到匹配的元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求
    • 既然我说了在web访问任何资源都是在访问Servlet,那么我访问静态资源【本地图片,本地HTML文件】也是在访问这个缺省Servlet【DefaultServlet】
    • 证实一下:当我没有手工配置缺省Servlet的时候,访问本地图片是可以访问得到的
      在这里插入图片描述
    • 现在我自己配置一个缺省Servlet,Demo1就是我手工配置的缺省Servlet,覆盖掉web.xml配置的缺省Servlet
        <servlet>
            <servlet-name>Demo1</servlet-name>
            <servlet-class>zhongfucheng.web.Demo1</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>Demo1</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    

    下面我继续访问一下刚才的图片,此时输出的是Demo1这个Servlet写上的内容了
    在这里插入图片描述

    • 总结:无论在web中访问什么资源【包括JSP】,都是在访问Servlet。没有手工配置缺省Servlet的时候,你访问静态图片,静态网页,缺省Servlet会在你web站点中寻找该图片或网页,如果有就返回给浏览器,没有就报404错误

    ServletConfig对象

    ServletConfig对象有什么用?

    通过此对象可以读取web.xml中配置的初始化参数。

    现在问题来了,为什么我们要把参数信息放到web.xml文件中呢?我们可以直接在程序中都可以定义参数信息,搞到web.xml文件中又有什么好处呢?

    好处就是:能够让你的程序更加灵活【更换需求,更改配置文件web.xml即可,程序代码不用改】

    获取web.xml文件配置的参数信息

    • 为Demo1这个Servlet配置一个参数,参数名是name,值是wqd
        <servlet>
            <servlet-name>Demo1</servlet-name>
            <servlet-class>wqd.web.Demo1</servlet-class>
            <init-param>
                <param-name>name</param-name>
                <param-value>wqd</param-value>
            </init-param>
        </servlet>
        <servlet-mapping>
            <servlet-name>Demo1</servlet-name>
            <url-pattern>/Demo1</url-pattern>
        </servlet-mapping>
    

    在Servlet中获取ServletConfig对象,通过ServletConfig对象获取在web.xml文件配置的参数

    在这里插入图片描述

    ServletContext对象

    什么是ServletContext对象?

    当Tomcat启动的时候,就会创建一个ServletContext对象。它代表着当前web站点

    ServletContext有什么用?

    1. ServletContext既然代表着当前web站点,那么所有Servlet都共享着一个ServletContext对象,所以Servlet之间可以通过ServletContext实现通讯。
    2. ServletConfig获取的是配置的是单个Servlet的参数信息,ServletContext可以获取的是配置整个web站点的参数信息
    3. 利用ServletContext读取web站点的资源文件
    4. 实现Servlet的转发【用ServletContext转发不多,主要用request转发】

    Servlet之间实现通讯

    ServletContext对象可以被称之为域对象

    到这里可能有一个疑问,域对象是什么呢?其实域对象可以简单理解成一个容器【类似于Map集合】

    实现Servlet之间通讯就要用到ServletContext的setAttribute(String name,Object obj)方法,
    第一个参数是关键字,第二个参数是你要存储的对象

    • 这是Demo2的代码
            //获取到ServletContext对象
            ServletContext servletContext = this.getServletContext();
    
            String value = "zhongfucheng";
    
            //MyName作为关键字,value作为值存进   域对象【类型于Map集合】
            servletContext.setAttribute("MyName", value);
    
    • 这是Demo3的代码
            //获取ServletContext对象
            ServletContext servletContext = this.getServletContext();
    
            //通过关键字获取存储在域对象的值
            String value = (String) servletContext.getAttribute("MyName");
    
            System.out.println(value);
    
    • 访问Demo3可以获取Demo2存储的信息,从而实现多个Servlet之间通讯
      在这里插入图片描述

    获取web站点配置的信息

    如果我想要让所有的Servlet都能够获取到连接数据库的信息,不可能在web.xml文件中每个Servlet中都配置一下,这样代码量太大了!并且会显得非常啰嗦冗余。

    • web.xml文件支持对整个站点进行配置参数信息【所有Servlet都可以取到该参数信息】
        <context-param>
            <param-name>name</param-name>
            <param-value>zhongfucheng</param-value>
        </context-param>
    
    • Demo4代码
            //获取到ServletContext对象
            ServletContext servletContext = this.getServletContext();
    
            //通过名称获取值
            String value = servletContext.getInitParameter("name");
            System.out.println(value);
    

    在这里插入图片描述

    • 试一下Demo3是否能拿到,相同的代码
            //获取到ServletContext对象
            ServletContext servletContext = this.getServletContext();
    
            //通过名称获取值
            String value = servletContext.getInitParameter("name");
            System.out.println(value);
    

    在这里插入图片描述

    读取资源文件

    第一种方式:

    • 现在我要通过Servlet111读取1.png图片
      在这里插入图片描述

    • 按我们以前的方式,代码应该是这样的。

            FileInputStream fileInputStream = new FileInputStream("1.png");
            System.out.println(fileInputStream);
      
    • 当我们访问的时候,却出错了!说找不到1.png文件
      在这里插入图片描述

    • 这是为什么呢?我们以前读取文件的时候,如果程序和文件在同一包名,可以直接通过文件名称获取得到的!,原因很简单,以前我们写的程序都是通过JVM来运行的,而现在,我们是通过Tomcat来运行的

    • 根据web的目录规范,Servlet编译后的class文件是存放在WEB-INFclasses文件夹中的
      在这里插入图片描述

    • 看到这里,我们知道了要进入classes目录中读取文件,所以我们将代码改成以下方式

            FileInputStream fileInputStream = new FileInputStream("D:\\zhongfucheng\\web\\WEB-INF\\classes\\zhongfucheng\\web\\1.png");
            System.out.println(fileInputStream);
      
    • 再去读取时,就发现可以获取到文件了。

    • 但是现在问题又来了,我读取文件的时候都要写上绝对路径,这样太不灵活了。试想一下,如果我将该读取文件的模块移到其他的web站点上,我的代码就又要修改了【因为web站点的名字不一样】。

    • 我们通过ServletContext读取就可以避免修改代码的情况,因为ServletContext对象是根据当前web站点而生成的

    • 代码如下所示:

      
            //获取到ServletContext对象
            ServletContext servletContext = this.getServletContext();
      
            //调用ServletContext方法获取到读取文件的流
            InputStream inputStream = servletContext.getResourceAsStream("/WEB-INF/classes/zhongfucheng/web/1.png");
          ```
      
      
    • 在这里插入图片描述

    第二种方式:

    • 如果我的文件放在web目录下,那么就简单得多了!,直接通过文件名称就能获取
      在这里插入图片描述

    • 代码如下所示

            //获取到ServletContext对象
            ServletContext servletContext = this.getServletContext();
      
            //调用ServletContext方法获取到读取文件的流
            InputStream inputStream = servletContext.getResourceAsStream("2.png");
      
    • 在这里插入图片描述

    第三种方式:

    通过类装载器读取资源文件。

    • 我的文件放在了src目录下【也叫做类目录】
      在这里插入图片描述

    • 代码如下所示

            //获取到类装载器
            ClassLoader classLoader = Servlet111.class.getClassLoader();
      
            //通过类装载器获取到读取文件流
            InputStream inputStream = classLoader.getResourceAsStream("3.png");
      
    • 在这里插入图片描述

    • 我的文件放在了src目录下的包下
      在这里插入图片描述

    • 代码如下,添加包名路径即可。

            //获取到类装载器
            ClassLoader classLoader = Servlet111.class.getClassLoader();
      
            //通过类装载器获取到读取文件流
            InputStream inputStream = classLoader.getResourceAsStream("/zhongfucheng/web/1.png");
      

    原则:如果文件太大,就不能用类装载器的方式去读取,会导致内存溢出

    展开全文
    qq_43842093 2021-10-22 23:03:12
  • 声明:本文基于Servlet3.1规范翻译整理,如有谬误,烦请指正。 JSR 340,原文请查阅 https://www.jcp.org/en/jsr/summary?id=Servlet+3.1 本文中的示例代码 可以 从...

    声明:本文基于Servlet3.1规范翻译整理,如有谬误,烦请指正。
    JSR 340,原文请查阅 https://www.jcp.org/en/jsr/summary?id=Servlet+3.1
    本文中的示例代码 可以 从https://gitee.com/Ouroboros_K/springboot2-demo.git项目中的servlet-service模块中获得

    1 Servlet 概括

    • Servlet 是基于Java技术的Web组件
    • Servlet 用于生成动态内容。
      • 动态: 数据动态
    • Servlet 是一组Java规范,以接口的形式对外提供,各个Servlet提供商按照规范提供实现
    • Servlet 是在Servlet容器中的,例如 Tomcat, Apache等 Web服务器都扩展了Servlet组件,因此这些Web 服务器也可以称为Servlet容器或Servlet引擎
    • Servlet与平台无关(基于Java)

    1.1 Servlet容器概括

    • Servlet容器主要提供基于请求/响应发送模式的网络服务,处理基于MIME的请求并格式化基于MINE的响应。
      • MIME: http协议中规定的数据类型,不同的MIME类型使用不同的方式解析并处理
    • Servlet容器都支持基于HTTP协议的请求/响应模式(HTTP/1.0 和 HTTP/1.1版本)。
    • Servlet容器一般是Web Server的一部分,浏览器发送请求到Web服务器,Web服务器将请求交由Servlet组件处理,Servlet在处理完请求后将格式化后的响应返回给Web服务器,Web服务器再将响应返回给浏览器
      • 在这里插入图片描述

    1.2 Servlet与Java版本

    这里使用Tomcat官网提供的图表展示。
    注意Servlet3.1 需要Java 7 及以上版本 和 Tomcat 8 及以上版本支持
    在这里插入图片描述

    2 Servlet 接口

    Servlet接口位于javax.servlet 包下,是Java Servlet API的核心抽象。其定义了 3 个生命周期方法 和 2 个普通方法 共5个方法。

    • init(servletConfig)
      • 生命周期-初始化方法
      • 该方法接收一个ServletConfig参数,支持Servlet在初始化时根据参数进行配置
        • ServltConfig中主要内容为ServletContext和initParamters
    • service(ServletRequest,ServletResponse)
      • 生命周期-运行时方法
      • 核心方法,该方法用于处理ServletRequest并将处理结果写入到ServletResponse中
    • destory()
      • 生命周期-销毁方法
    • getServletConfig():ServletConfig
      • 获取初始化时的配置信息
    • getServletInfo:String
      • 获取Servlet的信息,该方法在GenericServlet中被实现,返回空字符串。该方法目前似乎没有有效价值
    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by FernFlower decompiler)
    //
    
    package javax.servlet;
    
    import java.io.IOException;
    
    public interface Servlet {
        void init(ServletConfig var1) throws ServletException;
    
        ServletConfig getServletConfig();
    
        void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    
        String getServletInfo();
    
        void destroy();
    }
    

    2.1 Servlet的当前基础实现

    Servlet源生实现当前主要有GenericServlet抽象类 和 HttpServlet抽象类,通常情况下,开发者只需要继承HttpServlet类去实现自己的Servlet即可。

    2.1.1 GenericServlet

    GenericServlet抽象类实现了Servlet接口,主要完成了以下任务:

    • init() 中的 ServletConfig 赋给一个成员变量,可以由 getServletConfig 获得;
    • 为 Servlet 所有方法提供默认实现;
    • 可以直接调用 ServletConfig 中的方法;
    • 新提供了获取初始化参数的延展方法和日志记录方法

    基础结构如下

    public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
        private static final long serialVersionUID = -8592279577370996712L;
        private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
        private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
        private transient ServletConfig config;
    
        public GenericServlet() {
        }
    
        public void destroy() {}
    
      	//新提供获取键值对初始化参数的方法
        public String getInitParameter(String name) {...}
    	
    	//新提供获取所有初始化参数方法
        public Enumeration<String> getInitParameterNames() {...}
    	
        public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
        }
    	
        //新提供两个日志记录方法,日志格式为servletName + msg
        public void log(String msg) {...}
    	
        public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    
    }
    

    继承GenericServlet需要实现service方法。

    2.1.2 HttpServlet

    HttpServlet抽象类进一步封装了GenericServlet,在其基础上扩展了Http的内容。主要完成了以下内容:

    • 提供了service方法的默认实现,其中将请求和响应进一步由ServletRequestServletResponse转为HttpServletRequestHttpServletResponse
    • 提供了基于HttpServletRequestHttpServletResponseservice方法,其中根据不同的请求方式调用不同的方法
    • 提供了基于Http请求动词的 doGetdoPostdoDelete等7种方法
      • doGet 处理 HTTP GET 请求
      • doPost 处理 HTTP POST 请求
      • doPut 处理 HTTP PUT 请求
      • doDelete 处理 HTTP DELETE 请求
      • doHead 处理 HTTP HEAD 请求
        • 返回由doGet方法产生的header信息,也就是类似get方法,但不做任何处理,将直接返回一个NobodyResponse
      • doOptions 处理 HTTP OPTIONS 请求
        • 返回当前Servlet支持的HTTP方法
      • doTrace 处理 HTTP TRACE 请求
        • 返回请求中的所有头部信息

    在HTTP1.0环境下进行开发时,只需要重写doGet和doPost方法即可,当需要支持HTTP1.1版本时,还需要重写doPut和doDelete方法。

    3 Servlet生命周期

    Servlet具有严格定义的生命周期,其生命周期交由Servlet容器管理,开发人员无需关心其生命周期的管理,只需要定义好其生命周期的主要实现即可。总体上,Servlet生命周期分为初始化、处理请求和销毁3个阶段。也可以细分为 加载、初始化、处理请求、销毁四个阶段。

    3.1 加载和初始化

    Servlet容器负责加载和实例化Servlet,当Servlet容器启动时,根据 load-on-start元素规则 加载并初始化Servlet。将首先扫描所有Servlet的非抽象实现类的类名,并通过反射实例化类,此为加载过程,实例化完成之后调用init()方法完成初始化过程。

    3.1.1 init()

    init() 方法用于Servlet的初始化,开发人员通常需要实现该方法并完成初始化工作。注意此时Servlet类已完成实例化。如下是一个Servlet的Demo实现

    /**
     * <p>
     *     Demo Servlet
     * </p>
     *
     * @author Kern
     */
    @Log4j2
    public class DemoServlet extends HttpServlet {
    
        public DemoServlet() {
            log.info("DemoServlet is Instantiated");
        }
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            super.init(config);
            log.info("Servlet is initializing");
            //获取配置
            ServletConfig servletConfig = getServletConfig();
            //获取初始化参数
            String demoParameter = servletConfig.getInitParameter("demoParameter");
            log.info("Servlet Name: {}", getServletName());
            log.info("Servlet type: {}", getClass().getName());
            log.info("初始化参数demoParameter: {}", demoParameter);
            //TODO OTHER THINGS
        }
    
    }
    

    3.1.2 初始化配置

    Servlet在初始化时主要有如下三个配置

    • 加载方式和优先级顺序 load-on-start
    • 初始化参数配置 init-param
    • 路径寻址配置 servlet-mapping

    3.1.2.1 load-on-start

    load_on_start 元素为一个整数值, 定义了Servlet的加载和初始化优先顺序。
    load_on_start 规则:

    • 该元素值为正整数时,将在Servlet容器启动时随之加载和初始化,数值越小优先级越高
    • 该元素值为空或者负数时,将在Servlet被使用时才进行加载和初始化。

    配置方式:

    • Servlet3.0 之前的版本Servlet配置在web.xml文件中,配置方式如下:
    <web-app>
        <!--配置Servlet -->
        <servlet>
            <servlet-name>app</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    </web-app>
    
    • Servlet 3.0 之后,随着ServletContainerInitializer 接口的出现,Servlet支持在启动时动态加载Servlet,因而不仅同样支持web.xml中的配置方式,也支持Java编码的配置方法。如下例所示:
    public class KernServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher", new DemoServlet());
            dynamic.setLoadOnStartup(1);
            //TODO OTHER THINGS
        }
    }
    

    3.1.2.2 init-aram 初始化参数

    Servlet在初始化时支持以键值对的方式传递初始化参数,初始化参数需要配合特定的Servlet实现,是一个灵活的配置方式。例如在Spring MVC的前端总控制器 DispatcherServlet 的初始化中,需要在初始化参数中指定Spring 上下文配置文件的路径地址。
    配置方式,xml配置和java编码配置的原因同上文所述,是由版本支持导致的不同方式。
    xml配置方式:

    <web-app>
        <!--配置Servlet -->
        <servlet>
            <servlet-name>app</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
            <!--初始化参数中配置上下文文件地址 -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:app-context.xml</param-value>
            </init-param>
        </servlet>
    </web-app>
    

    Java编码方式:

    public class KernServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher", new DemoServlet());
            dynamic.setLoadOnStartup(1);
            dynamic.setInitParameter("contextConfigLocation", "classpath:app-context.xml");
            //TODO OTHER THINGS
        }
    }
    

    3.1.2.3 servlet-mapping 资源映射地址

    Servlet在初始化时,需要指定其寻址方式以便于Servlet容器决定调用哪个Servlet以处理请求。
    同样展示两种配置方式
    xml配置方式:

    <web-app>
        <!--配置Servlet -->
        <servlet>
            <servlet-name>app</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
            <!--初始化参数中配置上下文文件地址 -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:app-context.xml</param-value>
            </init-param>
        </servlet>
        <!--配置Servlet资源映射 -->
        <servlet-mapping>
            <servlet-name>app</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

    Java编码方式

    public class KernServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcher", new DemoServlet());
            dynamic.setLoadOnStartup(1);
            dynamic.setInitParameter("contextConfigLocation", "classpath:app-context.xml");
            dynamic.addMapping("/");
            //TODO OTHER THINGS
        }
    }
    

    如3.1所示的配置完成后,及具备了一个Servlet初始化的所有必要条件,此外,开发人员可以在init()方法中根据传入的ServletConfig参数对象获取初始化参数并完成Servlet的初始化实现。

    3.2 处理请求

    关于多线程和异步处理的内容,不多做讨论,除非你真正需要自己编写一个用于生产的Servlet,才可能涉及到该部分内容。本文仅作Servlet3.1的功能理解和部分组件配置了解用,因此不多做讨论,如需了解以上内容,请查阅文首提供的文档。

    • Servlet 完成初始化后,Servlet 容器就可以使用它处理客户端请求了。
    • Servlet 处理请求对应的service()方法,接收requestresponse两个请求参数。在 HTTP 请求的场景下,容器提供的请求和响应对象具体类型分别是 HttpServletRequest

    HttpServletResponse

    • 客户端请求由ServletRequest 类型的request 对象表示。
    • 客户端响应由ServletResponse 类型的 response 对象表示。
    • sevice方法不是线程安全的,开发人员需要针对性地完成请求处理的线程安全处理。

    3.3 销毁

    当Servlet容器确定一个Servlet需要进行销毁时,将调用destory方法,同时需要确保该Servlet正在执行的所有service方法线程完成或超过限制时间。一旦调用destory方法,Servlet实例将交由JVM的垃圾回收器回收,因此一个Servlet在Web服务运行期间将只能被初始化和销毁一次。目前不支持在运行时动态注册Servlet。

    4 Servlet 主要对象

    4.1 ServletContext

    ServletContext(Servlet 上下文)接口定义了 servlet 运行在的 Web 应用的视图。容器供应商负责提供 Servlet容器的 ServletContext 接口的实现。Servlet 可以使用 ServletContext 对象记录事件,获取 URL 引用的资源,存取当前上下文的其他 Servlet 可以访问的属性。

    在这里插入图片描述

    简而言之,ServletContext是当前应用中Servlet的视图抽象,其持有注册的Servlet、Listener、Filter以及其他上下文内容,包括初始化参数,运行时属性等,Servlet容器和Servlet通过其完成转发通信等工作。例如tomcat的ServletContext实现,如下所示

    • 注:仅展示部分代码
    
    public class ApplicationContext implements ServletContext {
        protected Map<String, Object> attributes = new ConcurrentHashMap();
        private final Map<String, String> readOnlyAttributes = new ConcurrentHashMap();
        private final StandardContext context;
        private static final List<String> emptyString;
        private static final List<Servlet> emptyServlet;
        private final Map<String, String> parameters = new ConcurrentHashMap();
    }
    public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
        private String[] applicationListeners = new String[0];
        private List<Object> applicationEventListenersList = new CopyOnWriteArrayList();
        private Map<ServletContainerInitializer, Set<Class<?>>> initializers = new LinkedHashMap();
        private ApplicationParameter[] applicationParameters = new ApplicationParameter[0];
        private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap();
        private Map<String, FilterDef> filterDefs = new HashMap();
        private final Map<String, String> parameters = new ConcurrentHashMap();
        private Map<String, String> servletMappings = new HashMap();
        private Set<Servlet> createdServlets;
    }
    

    Servlet 可以通过getServletConfig().getServletContext()方法获取上下文。

    public void test(Servlet servlet) {
    	ServletContext context = servlet.getServletConfig().getServletContext();
    }
    

    在一个Web应用中,所有Servlet拥有共同的ServletContext 上下文环境。ServletContext支持以Java编码的形式添加Servlet、Listener(包括上下文监听器、事件监听等所有监听器)、Filter过滤器。如下所示

    /**
     * <p>
     *     Servlet容器初始化类
     * </p>
     * @see javax.servlet.ServletContainerInitializer
     * @author Kern
     */
    @Log4j2
    @HandlesTypes(DemoWebAppInitializer.class)
    public class DemoServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            ServletRegistration.Dynamic dynamic = servletContext.addServlet("demo", DemoServlet.class);
            int loadOnStartup = 1;
            String primaryMapping = "/";
            dynamic.setLoadOnStartup(loadOnStartup);
            dynamic.setInitParameter("demoParameter", "I'm init parameter");
            dynamic.addMapping(primaryMapping);
            log.info("Demo Servlet config complete! loadOnStartup:{}, url-pattern{}", loadOnStartup, primaryMapping);
            
            log.info("=== addListeners ===");
            servletContext.addListener(ContextListener.class);
            servletContext.addListener(RequestLoggingListener.class);
    
            log.info("=== addFilters ===");
            servletContext.addFilter("wrapperRequestFilter", WrapperRequestFilter.class)
                    .addMappingForServletNames(null, false, "ROOT");
            servletContext.addFilter("unifiedEncodingUTF8Filter", UnifiedEncodingUTF8Filter.class)
                    .addMappingForUrlPatterns(null, false, "/*");
        }
    }
    
    

    以上代码等效于

    <web-app>
    		<!--配置过滤器 -->
        <filter>
            <filter-name>wrapperRequestFilter</filter-name>
            <filter-class>
                cn.kerninventor.springboot2.servlet.service.filter.WrapperRequestFilter
            </filter-class>
        </filter>
        <filter-mapping>
            <filter-name>wrapperRequestFilter</filter-name>
          	<!--Servlet映射 -->
            <servlet-name>ROOT</servlet-name>
        </filter-mapping>
        <filter>
            <filter-name>unifiedEncodingUTF8Filter</filter-name>
            <filter-class>
                cn.kerninventor.springboot2.servlet.service.filter.UnifiedEncodingUTF8Filter
            </filter-class>
        </filter>
        <filter-mapping>
            <filter-name>unifiedEncodingUTF8Filter</filter-name>
          	<!--路径映射 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
      	<!--配置事件监听器 -->
        <listener>
            <listener-class>cn.kerninventor.springboot2.servlet.service.listener.RequestLoggingListener</listener-class>
        </listener>
        <listener>
            <listener-class>cn.kerninventor.springboot2.servlet.service.listener.ContextListener</listener-class>
        </listener>
        
        <!--配置Servlet -->
        <servlet>
            <servlet-name>ROOT</servlet-name>
            <servlet-class>cn.kerninventor.springboot2.servlet.service.servlet.DemoRootHttpServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <!--配置Servlet资源映射 -->
        <servlet-mapping>
            <servlet-name>ROOT</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

    4.2 Request

    请求对象封装了客户端请求的所有信息。在 HTTP 协议中,这些信息是从客户端发送到服务器请求的 HTTP
    头部和消息体。主要包括ServletRequest接口和HttpServletRequest接口,HttpServletRequest扩展了ServletRequest接口以提供Http Servlet的请求信息
    请求的主要信息如下:

    • Http协议参数 Parameter
      • 以键值对的形式存储,键与值是一对多的关系,例如调用getParameterMap()方法,将返回一个Map<String, String[]>类型的Map集合 。
    • 文件上传 Part
      • content-type 为multipart/form-data时,将以文件形式处理,通过Part关键字相关方法获取,返回一个Part对象,该对象提供getInputStream()方法来访问文件输入流
    • 属性 Attribut
      • 属性是与请求相关联的对象。用于在Servlet容器和Servlet之间传递信息,也可以在Servlet之间传递信息
    • 头部 Header
      • Http请求的头部信息
    • 请求路径 Path
      • Context Path:Servlet相对于Web服务器中的相对路径

      • Servlet Path: Servlet的映射地址

      • PathInfo:请求路径的一部分

      • RequestURI: 等于 contextPath + servletPath + pathInfo

        • 官方提供的示例
        • 在这里插入图片描述
      • 其他路径方法

        • getRealPath:获取该请求在服务器文件系统中的真实寻址路径,得到的地址字符串对应本地文件系统的一个文件
        • getPathTranslated:用于推断请求的pathInfo的实际请求路径
    • Cookies信息
      • cookies将以键值对的形式存储
    • SSL属性
      • 如果请求以HTTPS等安全协议发送过,将在请求信息中包含该部分内容。开发人员可以通过isSecure()方法查阅该请求是否经过安全协议
    • 国际化 Locale
      • 客户可以选择希望Web服务器用什么语言来响应。该信息在请求的头部属性 Accept-Language中设置
        • getLocale
        • getLocales
    • 请求数据编码 Charset
      • 编码格式通过getCharacterEncoding方法获取,如果客户端没有在Content-Type中存储编码格式的话,将返回null,此时可以在读取请求信息前使用s``etCharacterEncoding方法统一编码格式

    以下是我的一个Demo测试返回的一些request对象的方法,供参考

    @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //        super.doPost(req, resp);
            log.info("{} 接收到POST请求", getServletName());
            log.info("=== remote information ===");
            log.info("remoteAddr    : {}", req.getRemoteAddr());
            log.info("remoteHost    : {}", req.getRemoteHost());
            log.info("remotePort    : {}", req.getRemotePort());
            log.info("remoteUser    : {}", req.getRemoteUser());
    
            log.info("=== path information ===");
            log.info("requestMethod : {}", req.getMethod());
            log.info("contentType   : {}", req.getContentType());
            log.info("contextPath   : {}", req.getContextPath());
            log.info("servletPath   : {}", req.getServletPath());
            log.info("pathInfo      : {}", req.getPathInfo());
            log.info("requestURI    : {}", req.getRequestURI());
            log.info("pathTranslated: {}", req.getPathTranslated());
            log.info("realPath      : {}", getServletContext().getRealPath("/"));
            log.info("=== cookies information ===");
            Cookie[] cookies = req.getCookies();
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    log.info("cookie-{} : {}", cookie.getName(), cookie.getValue());
                }
            }
            log.info("=== locale information ===");
            Enumeration<Locale> localeEnumeration = req.getLocales();
            if (localeEnumeration != null) {
                while (localeEnumeration.hasMoreElements()) {
                    log.info("locale : {}", localeEnumeration.nextElement().toString());
                }
            }
            log.info("=== parameters information ===");
            Map<String, String[]> parameters = req.getParameterMap();
            if (parameters != null) {
                for (Map.Entry<String, String[]> stringEntry : parameters.entrySet()) {
                    log.info("parameter: key={}, value={}", stringEntry.getKey(), ToStringUtils.toString(stringEntry.getValue(), ","));
                }
            }
            log.info("=== body information ===");
            BufferedReader reader = req.getReader();
            StringBuilder bodyStringBuilder = new StringBuilder();
            String lineStr = "";
            while ((lineStr = reader.readLine()) != null) {
                bodyStringBuilder.append(lineStr);
            }
            log.info(bodyStringBuilder);
            try {
                Person person = new JsonMapper().readValue(bodyStringBuilder.toString(), Person.class);
                log.info(person.toString());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
    
            resp.setCharacterEncoding("UTF-8");
            ServletOutputStream outputStream = resp.getOutputStream();
            outputStream.print("Hello Servlet");
            outputStream.flush();
        }
    

    psotman测试请求
    image.png

    结果如下
    在这里插入图片描述

    4.3 Response

    响应(response)对象封装了从服务器返回到客户端的所有信息。在 HTTP 协议中,从服务器传输到客户端
    的信息通过 HTTP 头信息或响应的消息体。主要包括ServletResponse接口和HttpServletResponse接口,同样,HttpServletResponse扩展了ServletResponse接口以提供Http Servlet的请求信息。
    response主要包含以下内容

    • 缓存 buffer
      • 支持Servlet设置和修改响应的缓冲区域
    • 头部 header
      • 包含响应的头部信息,同时Servlet允许添加和修改头部信息
    • 国际化 Locale
      • 存放在响应的头部属性Content-Language中,同时也支持配置统一的默认国际化属性。此外还提供Locale的查询和设置方法
    • 请求数据编码 Charset
      • 存放在响应的头部信息Content-Type中,例如application/json; charset=utf-8,提供setCharacterEncodinggetCharacterEncoding。需要注意的是,请求和响应的编码格式一般需要保持一致。
    • 其他简便方法
      • sendRedirect,重定向到另外一个网络地址
      • sendError,直接返回错误信息

    4.4 Session

    Servlet规范定义了一个 HttpSession 接口用于弥补HTTP协议的无状态特性。
    开发人员可以使用HttpServletRequest#getSession(); 方法直接获取,将返回一个HttpSession对象。
    使用Session池与Response对象的Cookie值相结合,可以实现用户会话的保持,以及较为复杂的单点登录等。当然相关的替代方案也比较多。这里不多讨论

    5 Servlet主要自定义组件

    Servlet针对其整个生命周期,甚至包括Servlet容器及上下文的整个生命周期,请求/响应对象的生命周期都提供了一些基础的自定义组件,主要包括过滤器和监听器,有针对性的在生命周期的各个阶段提供额外功能。

    • Listener 针对容器的启动和运行期间的事件进行监听,在 EventListener 接口的基础上,Servlet 针对生命周期的不同元素及其不同阶段扩展了多种实现。
    • Filter 在Servlet的路径映射规则基础上添加过滤器,过滤器主要用于请求和响应的包装,可以映射到指定请求路径或指定Servlet(Servlet本身具有路径映射)上。

    5.1 各组件的加载和执行顺序

    首先以Tomcat容器实现描述一下Servlet容器的主要组件加载顺序

    Servlet Container 加载顺序

    1. 加载上下文参数
    2. 实例化Listener
    3. 实例化Filter并调用Filter#init(config:FilterConfig)初始化Filter
    4. 实例化Servlet并调用Servlet#init(config:ServletConfig)初始化Servlet
    5. 懒加载(load-on-start== null || load-on-start < 0)的Servlet将在第一次使用时被实例化并初始化

    在这里插入图片描述

    截取部分代码,通过源代码的注释可以很清楚看到加载顺序

    	@Override
        protected synchronized void startInternal() throws LifecycleException {
            try {
                // Set up the context init params
                mergeParameters();
                // Call ServletContainerInitializers
                for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                    initializers.entrySet()) {
                    try {
                        entry.getKey().onStartup(entry.getValue(),getServletContext());
                    } catch (ServletException e) {
                        log.error(sm.getString("standardContext.sciFail"), e);
                        ok = false;
                        break;
                    }
                }
                // Configure and call application event listeners
                if (ok) {
                    if (!listenerStart()) {
                        log.error(sm.getString("standardContext.listenerFail"));
                        ok = false;
                    }
                }
                // Configure and call application filters
                if (ok) {
                    if (!filterStart()) {
                        log.error(sm.getString("standardContext.filterFail"));
                        ok = false;
                    }
                }
                // Load and initialize all "load on startup" servlets
                if (ok) {
                    if (!loadOnStartup(findChildren())){
                        log.error(sm.getString("standardContext.servletFail"));
                        ok = false;
                    }
                }
                // Start ContainerBackgroundProcessor thread
                super.threadStart();
            } finally {
                // Unbinding thread
                unbindThread(oldCCL);
            }
        }
    

    其次是一次请求各个组件的调用顺序

    1. 请求相关的事件监听器
    2. 符合映射规则的过滤器
    3. 符合映射规则的Servlet

    在这里插入图片描述

    5.2 Filter

    过滤器技术基于代码重用,在Servlet规范中,Filter被定义为javax.servlet.Filter接口,其核心功能是包装请求和响应,此外也可以在Filter中修改和调整资源的请求,对请求进行拦截,数据加密,日志记录等。
    **
    应用场景

    • 验证过滤器
    • 日志记录和审计过滤器
    • 图像转换过滤器
    • 数据压缩过滤器
    • 加密过滤器
    • 词法(Tokenizing)过滤器
    • 触发资源访问事件过滤器
    • 转换 XML 内容的 XSL/T 过滤器
    • MIME-类型链过滤器
    • 缓存过滤器

    5.2.1 Filter接口

    /**
     * @since Servlet 2.3
     */
    public interface Filter {
    
        default public void init(FilterConfig filterConfig) throws ServletException {}
    	
    
        public void doFilter(ServletRequest request, ServletResponse response,
                             FilterChain chain)
                throws IOException, ServletException;
        
        default public void destroy() {}
    }
    
    

    Filter接口主要包含三个生命周期方法,初始化,过滤处理和销毁。
    三个方法的主要职责如下

    • init 进行初始化,将传入一个FilterConfig参数,该参数主要包括其初始化参数、映射路径或映射Servlet等内容
    • doFilter 过滤请求,过滤器的核心功能,主要功能在其中实现。
      • 需要注意的是,根据Servlet规范,一般情况下,doFilter方法的执行结尾应当调用 chain.doFilter(request,response) 将请求传递给下一个过滤器。
    • destory 销毁方法,应当在销毁时释放必要的资源

    5.2.2 Filter配置示例

    Servlet3.0后除了常规的web.xml配置外,还支持Java方式配置。主要的配置方式本文4.1 ServletContext 章节已有展示。下面简要提炼
    主要配置内容

    • FilterName: Filter的唯一标识符
    • FilterClass:Filter实现类的类名,需要实现javax.servlet.Filter接口
    • mapping:映射规则,可以映射到一个路径中,也可以映射到Servlet(实际上也是间接映射到路径)
    • dispatcherTypes:调度者类型,该例说明比较复杂,建议查阅Servlet规范文档,此处仅作简要说明。该值为枚举值,查阅 javax.servlet.DispatcherType
      • REQUEST 请求从客户端发起时,调用该Filter,这是默认的值(dispatcherTypes为空时默认为REQUEST)
      • FORWARD 请求经由 request.getRequestDispatcher().forward() 转发时,调用该Filter
        • 关于forward方法本身存在规约,即调用者对响应做出的任何操作都将被忽略,既完全由被调用者处理响应,调用者本身只是相当于转发的中介,如果被调用者发现响应已经提交,需要抛出异常。
      • INCLUDE 请求经由 request.getRequestDispatcher().include() 转发时,调用该Filter
        • 关于include方法本身存在规约,即调用者与被调用者存在包含关系,被调用者可以在调用者的资源基础上进行添加。当然也有诸多限制(例如不能处理响应头部信息)。具体查阅Servlet规约文档第9.3节
      • ASYNC 请求经由 request.startAsync().dispatch() 异步转发时,调用该Filter
      • ERROR 当调用者的调用发生异常时,将调用该Filter
    • init-param:初始化参数,该参数将封装 FilterConfig 在 init 方法中作为参数获得

    注意,Filter映射规则需要符合两个条件,即 请求路径 + DispatcherTypes

    Filter实现类

    /**
     * <p>
     *     UnifiedEncodingUTF8Filter
     *	   统一请求与响应的编码格式
     * </p>
     * @see javax.servlet.Filter
     * @author Kern
     */
    @Log4j2
    public class UnifiedEncodingUTF8Filter implements javax.servlet.Filter {
    
        public UnifiedEncodingUTF8Filter() {
            log.info(this.getClass().getName() + " constructed");
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info(this.getClass().getName() + " initializing");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("UnifiedEncodingUTF8Filter handle");
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
        }
    }
    

    5.2.2.1 Java编码方式示例

    java编码格式通过 SerlvletContext#addFilter() 方法完成配置
    ServletContainerInitializer实现(仅展示部分代码)

    /**
     * <p>
     *     Servlet容器初始化类
     * </p>
     * @see javax.servlet.ServletContainerInitializer
     * @author Kern
     */
    @Log4j2
    @HandlesTypes(DemoWebAppInitializer.class)
    public class DemoServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
    		//注册Servlet
            ServletRegistration.Dynamic dynamic = servletContext.addServlet("ROOT", DemoRootHttpServlet.class);
            int loadOnStartup = 1;
            String primaryMapping = "/";
            dynamic.setLoadOnStartup(loadOnStartup);
            dynamic.setInitParameter("demoParameter", "I'm init parameter");
            dynamic.addMapping(primaryMapping);
            log.info("Demo Servlet config complete! loadOnStartup:{}, url-pattern{}", loadOnStartup, primaryMapping);
    		//注册Filter
            log.info("=== addFilters ===");
            servletContext.addFilter("unifiedEncodingUTF8Filter", UnifiedEncodingUTF8Filter.class)
                    .addMappingForUrlPatterns(null, false, "/*");
        }
    }
    
    

    5.2.2.2 XML配置方式示例

    xml配置示例等效于Java配置实例

    <web-app>
      	<!--配置Filter -->
        <filter>
            <filter-name>unifiedEncodingUTF8Filter</filter-name>
            <filter-class>
                cn.kerninventor.springboot2.servlet.service.filter.UnifiedEncodingUTF8Filter
            </filter-class>
        </filter>
      	<!--配置Filter映射 -->
        <filter-mapping>
            <filter-name>unifiedEncodingUTF8Filter</filter-name>
            <url-pattern>/*</url-pattern>
          	<!--该配置等效于上面的url-pattern -->
          	<!-- <servlet-name>ROOT</servlet-name> -->
        </filter-mapping>
        <!--配置Servlet -->
        <servlet>
            <servlet-name>ROOT</servlet-name>
            <servlet-class>cn.kerninventor.springboot2.servlet.service.servlet.DemoRootHttpServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <!--配置Servlet资源映射 -->
        <servlet-mapping>
            <servlet-name>ROOT</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

    5.2.3 路径映射规则,Filter与Servlet的绑定

    Filter的路径映射可以映射到 一个指定路径中, 也可以直接映射到一个Servlet,
    在这里插入图片描述

    5.2.4 FilterChain的构建\执行原理简析

    以Tomcat Servlet容器实现为例。容器根据Filter配置和定义加载并实例化完Filter之后,所有Filter将被存放在一个FilterMap数组中,存放在标准容器上下文环境中。当发生一次请求时,调用org.apache.catalina.core.ApplicationFilterFactory#createFilterChain()
    方法。组装过滤器链。

    public class ApplicationFilterFactory {
        //截取部分代码
    	public static ApplicationFilterChain createFilterChain(ServletRequest request,
                Wrapper wrapper, Servlet servlet) {
            // Add filters that match on servlet name second
            for (FilterMap filterMap : filterMaps) {
                if (!matchDispatcher(filterMap, dispatcher)) {
                    continue;
                }
                if (!matchFiltersServlet(filterMap, servletName))
                    continue;
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                        context.findFilterConfig(filterMap.getFilterName());
                if (filterConfig == null) {
                    // FIXME - log configuration problem
                    continue;
                }
                filterChain.addFilter(filterConfig);
            }
    
            // Return the completed filter chain
            return filterChain;
        }
    }
    public class ApplicationFilterChain implements FilterChain {
    	void addFilter(ApplicationFilterConfig filterConfig) {
            // Prevent the same filter being added multiple times
            for(ApplicationFilterConfig filter:filters)
                if(filter==filterConfig)
                    return;
            if (n == filters.length) {
                //这里时New的FilteConfig
                ApplicationFilterConfig[] newFilters =
                    new ApplicationFilterConfig[n + INCREMENT];
                System.arraycopy(filters, 0, newFilters, 0, n);
                filters = newFilters;
            }
            filters[n++] = filterConfig;
        }
    }
    

    组装完过滤链后,将执行过滤链, 通过移动 pos 下标,结合规约中定义的 Filter调用chain.doFilter(req, resp) 完成链的顺序调用,将链执行完毕后,将调用目标Servlet处理请求。

    public class ApplicationFilterChain implements FilterChain {
    	@Override
        public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {	
            if( Globals.IS_SECURITY_ENABLED ) {
               	//加密请求下的操作,最终也是调用internalDoFilter
            } else {
                internalDoFilter(request,response);
            }
        }
    
        private void internalDoFilter(ServletRequest request,
                                      ServletResponse response)
            throws IOException, ServletException {
    
            // Call the next filter if there is one
            if (pos < n) {
                //pos++ ,移动指针
                ApplicationFilterConfig filterConfig = filters[pos++];
                try {
                    //这里实际上也是利用反射实例化一个新的Filter实例
                    //this.filter = (Filter) context.getInstanceManager().newInstance(filterClass);
                    Filter filter = filterConfig.getFilter();
    				//忽略大部分代码,最终执行
                    filter.doFilter(request, response, this);
            }
    
            // We fell off the end of the chain -- call the servlet instance
    		// 此处忽略大部分代码
            servlet.service(request, response);
        }
    }
    

    可以简单总结流程如下图
    在这里插入图片描述
    整个过程中,无论是Filter的配置和Filter实例都是新创建的对象,对于多线程高并发环境也提供了支持。

    5.2.5 Filter和DispatcherType

    由于分派机制与Filter有较多的关联,因而在本章内我整理了部分Dispatcher机制,以图表的形式展示不同分派类型的执行流程。

    5.2.5.1 RequestDispatcher接口

    RequestDispatcher接口是Servlet封装的分派接口,主要API有forward转发和include包含两个,转发和包含其本意上都是描述调用者与被调用者之间响应资源的关系。
    forward调用时,调用者仅作为请求的转发者,被调用者将通过 response.flushBuffer() 清除调用者对响应的所有修改。
    include调用时,调用者作为响应处理的主体,负责响应头部的设置和内容的修改,被调用者在前者的基础上,可以获得前者所有添加在request中的信息,同时在调用者的基础上添加响应内容,此外被调用者还有一个重要的职责是提交响应。
    Servlet借由RequestDispatcher接口分派规约,结合3.0版本提出的异步上下文AsyncContext以及错误处理机制,整合了REQUEST\FORWARD\INCLUDE\ASYNC\ERROR 五种分派机制。下面对五种分派机制进行图解。

    如下图解中没有显示路径映射规则,需要注意Filter映射规则 = 路径映射 + DispatcherType映射
    ASYNC 异步暂时不做讨论

    5.2.5.2 Request 请求

    由客户端发起的请求其分派类型为REQUEST,是默认的请求分派类型。在此过程中,配置DispatcherTypes 中含有 REQUEST 的Filter将拦截请求并调用过滤链。
    在这里插入图片描述

    5.2.5.3 Forward 分派

    服务端从请求中获得分派对象并调用 forward 方法指定到对应路径的 Servlet进行分派处理
    req.getRequestDispatcher("/").forward(req,resp)
    在此过程中,配置DispatcherTypes 中含有 FORWARD 的Filter将拦截请求并调用过滤链。
    在这里插入图片描述

    5.2.5.4 Include 包含分派

    服务端从请求中获得分派对象并调用 include 方法指定到对应路径的 Servlet进行分派处理
    req.getRequestDispatcher("/").include(req,resp)
    在此过程中,配置DispatcherTypes 中含有 INCLUDE 的Filter将拦截请求并调用过滤链。
    在这里插入图片描述

    5.2.5.5 Error 错误处理

    服务端在整个运行阶段,如果发生异常,将根据规约封装异常信息到request中,同时调用错误处理机制,Servlet容器厂商提供默认的错误处理机制,同时支持自定义的错误处理(映射为文件或Servlet路径)。
    在进入错误处理机制前,配置DispatcherTypes 中含有 ERROR 的Filter将拦截请求并调用过滤链。

    自定义错误处理配置

    <web-app>
      	 <!--配置Filter -->
        <filter>
            <filter-name>errorFilter</filter-name>
            <filter-class>
                cn.kerninventor.springboot2.servlet.service.filter.ErrorFilter
            </filter-class>
        </filter>
        <!--配置Filter映射 -->
        <filter-mapping>
            <filter-name>errorFilter</filter-name>
             <servlet-name>ERROR-HANDLER</servlet-name>
            <dispatcher>ERROR</dispatcher>
        </filter-mapping>
      
      	<!--配置Servlet -->
        <servlet>
            <servlet-name>ERROR-HANDLER</servlet-name>
            <servlet-class>cn.kerninventor.springboot2.servlet.service.servlet.DemoErrorHandlerServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    		<!--配置Servlet映射 -->
        <servlet-mapping>
            <servlet-name>ERROR-HANDLER</servlet-name>
            <url-pattern>/error/</url-pattern>
        </servlet-mapping>
    		<!--根据响应编码配置错误处理,指定到页面 -->
        <error-page>
            <error-code>404</error-code>
            <location>/WEB-INF/404.jsp</location>
        </error-page>  
    		<!--根据异常类型配置错误处理,指定到路径 -->
        <error-page>
              <exception-type>java.lang.Throwable</exception-type>
              <location>/error/</location>
        </error-page>
    </web-app>
    

    处理流程
    在这里插入图片描述

    5.3 Listener

    应用事件监听器是实现一个或多个 Servlet 事件监听器接口的类。它们是在部署 Web 应用时,实例化并注册到 Web 容器中。它们由开发人员在 WAR 包中提供。
    Servlet 事件监听器支持在 ServletContext、HttpSession 和 ServletRequest 状态改变时进行事件通知。Servlet上下文监听器是用来管理应用的资源或 JVM 级别持有的状态。

    应用场景

    • 监听Servlet启动
    • 请求监听
    • 日志记录
    • 会话监听

    5.3.1 Listener接口

    基于Java的事件监听接口

    package java.util;
    
    /**
     * A tagging interface that all event listener interfaces must extend.
     * @since JDK1.1
     */
    public interface EventListener {
    }
    

    根据事件类型的不同,Servlet定义如下接口

    事件主体事件类型描述监听器接口
    ServletContext生命周期Servlet 上下文刚刚创建,并可用于服务它的第一个请求,或者Servlet 上下文即将关闭javax.servlet. ServletContextListener
    更改属性在 Servlet 上下文的属性已添加,删除、或替换。javax.servlet.
    ServletContextAttributeListener
    HttpSession生命周期会话已创建、销毁或超时。javax.servlet.http.
    HttpSessionListener
    更改属性已经在 HttpSession 上添加、移除、或替换属性。javax.servlet.http.HttpSessionAttributeListener
    改变IDHttpSession 的 ID 将被改变javax.servlet.httpHttpSessionIdListener
    会话迁移HttpSession 已被激活或钝化。javax.servlet.http.
    HttpSessionActivationListener
    对象绑定对象已经从 HttpSession 绑定或解除绑定javax.servlet.http.
    HttpSessionBindingListener
    ServletRequest生命周期一个 servlet 请求已经开始由 Web 组件处理。javax.servlet.
    ServletRequestListener
    更改属性已经在 ServletRequest 上添加、移除、或替换属
    性。javax.servlet.
    ServletRequestAttributeListener
    异步事件超时、连接终止或完成异步处理javax.servlet.AsyncListener

    5.3.2 Listener配置示例

    本例中以上下文监听器为例,实现简单记录一下上下文的启动和销毁

    /**
     * <p>
     *     DemoServletContextListener
     * </p>
     *
     * @author Kern
     */
    @Log4j2
    public class ContextListener implements ServletContextListener {
        public ContextListener() {
            log.info(this.getClass().getName() + " constructed");
        }
        @Override
        public void contextInitialized(ServletContextEvent sce) {
           log.info("ServletContext Initialized");
        }
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            log.info("ServletContext destroyed!");
        }
    }
    

    5.3.2.1 Java编程配置

    /**
     * <p>
     *     Servlet容器初始化类
     * </p>
     * @see javax.servlet.ServletContainerInitializer
     * @author Kern
     */
    @Log4j2
    @HandlesTypes(DemoWebAppInitializer.class)
    public class DemoServletContainerInitializer implements ServletContainerInitializer {
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            log.info("=== addListeners ===");
            servletContext.addListener(ContextListener.class);
        }
    }
    
    

    5.3.2.2 XML配置示例

    <web-app>
        <listener>
            <listener-class>cn.kerninventor.springboot2.servlet.service.listener.ContextListener</listener-class>
        </listener>
    </web-app>
    

    6 Servlet 3.0+的主要新特性

    Servlet3.0 主要在支持Java编程配置和注解编程方面做了非常大的努力,支持在Web应用启动时动态加载Servlet上下文。可惜不能再运行时进行组件的运行时加载和卸载。
    下面简述一下两大特性

    6.1 ServletContainerInitializer

    接口如下

    /**
     * @see javax.servlet.annotation.HandlesTypes
     *
     * @since Servlet 3.0
     */
    public interface ServletContainerInitializer {
        public void onStartup(Set<Class<?>> c, ServletContext ctx)
            throws ServletException; 
    }
    

    6.1.1 加载规则

    容器在启动时,将扫描 /META-INF/services 目录中 javax.servlet.ServletContainerInitializer 文件

    • 文件名为接口名
    • 文件内容为接口实现

    在这里插入图片描述

    ServletContainerInitializer 实现类必须有一个无参的构造器,
    经由反射实例化后,将执行 onStratup 方法完成初始化
    主要参数如下

    • Set<Class<?>> 在实现类中标注 @HandlesTypes 指定class类型。 容器将扫码所有指定class类型的类,并作为参数传入。可以简单理解 ServletContainerInitializer 的实现作为主要加载类,使用 @HandlesTypes 标注其他辅助加载类。可以参考Springboot的相关实现
    • ServletContext 上下文参数

    Spring 的 实现,实际上SpringMVC自3.2版本开始就使用此机制加载DispatcherServlet

    
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
    	@Override
    	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
    			throws ServletException {
    
    		List<WebApplicationInitializer> initializers = new LinkedList<>();
    
    		if (webAppInitializerClasses != null) {
    			for (Class<?> waiClass : webAppInitializerClasses) {
    				// Be defensive: Some servlet containers provide us with invalid classes,
    				// no matter what @HandlesTypes says...
    				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
    						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
    					try {
    						initializers.add((WebApplicationInitializer)
    								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
    					}
    					catch (Throwable ex) {
    						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
    					}
    				}
    			}
    		}
    
    		if (initializers.isEmpty()) {
    			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
    			return;
    		}
    
    		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
    		AnnotationAwareOrderComparator.sort(initializers);
    		for (WebApplicationInitializer initializer : initializers) {
    			initializer.onStartup(servletContext);
    		}
    	}
    }
    
    /**
     * Base class for {@link org.springframework.web.WebApplicationInitializer}
     * @since 3.2
     */
    public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
      //实现省略   
    }
    

    **
    以下是我测试的Demo实现,主要完成了Servlet注册 Filter注册 Listener注册等工作

    /**
     * <p>
     *     Servlet容器初始化类
     * </p>
     * @see javax.servlet.ServletContainerInitializer
     * @author Kern
     */
    @Log4j2
    @HandlesTypes(DemoWebAppInitializer.class)
    public class DemoServletContainerInitializer implements ServletContainerInitializer {
    
        @Override
        public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
            
            ServletRegistration.Dynamic dynamic = servletContext.addServlet("ROOT", DemoRootHttpServlet.class);
            int loadOnStartup = 1;
            String primaryMapping = "/";
            dynamic.setLoadOnStartup(loadOnStartup);
            dynamic.setInitParameter("demoParameter", "I'm init parameter");
            dynamic.addMapping(primaryMapping);
            log.info("Demo Servlet config complete! loadOnStartup:{}, url-pattern{}", loadOnStartup, primaryMapping);
    
    
            ServletRegistration.Dynamic lazyLoadingDynamic = servletContext.addServlet("LAZY", DemoLazyLoadingRootHttpServlet.class);
            lazyLoadingDynamic.setLoadOnStartup(-1);
            lazyLoadingDynamic.setInitParameter("demoParameter", "I'm init parameter");
            lazyLoadingDynamic.addMapping("/lazy/");
            log.info("DemoLazyLoading Servlet config complete!");
    
            if (set != null) {
                Iterator<Class<?>> webAppInitializerIterator = set.iterator();
                List<DemoWebAppInitializer> initializers = new ArrayList<>(set.size());
                while (webAppInitializerIterator.hasNext()) {
                    Class<?> webAppInitializerClass = webAppInitializerIterator.next();
                    if (webAppInitializerClass != null &&
                            !webAppInitializerClass.isInterface() &&
                            DemoWebAppInitializer.class.isAssignableFrom(webAppInitializerClass)) {
                        try {
                            DemoWebAppInitializer demoWebAppInitializer = (DemoWebAppInitializer) webAppInitializerClass.getConstructor().newInstance();
                            initializers.add(demoWebAppInitializer);
                        } catch (Throwable e) {
                            throw new RuntimeException("WebAppInitializer failed to initialize" + webAppInitializerClass.getName());
                        }
                    }
                }
    
                if (initializers.size() > 0) {
                    log.info("Initialize DemoWebAppInitializer!");
                    DemoWebContext demoWebContext = new DemoWebContext();
                    initializers.forEach(e -> e.onStartup(demoWebContext));
                }
            }
    
            log.info("=== addListeners ===");
            servletContext.addListener(ContextListener.class);
            servletContext.addListener(RequestLoggingListener.class);
    
            log.info("=== addFilters ===");
            servletContext.addFilter("wrapperRequestFilter", WrapperRequestFilter.class)
                    .addMappingForServletNames(null, false, "ROOT");
            servletContext.addFilter("unifiedEncodingUTF8Filter", UnifiedEncodingUTF8Filter.class)
                    .addMappingForUrlPatterns(null, false, "/*");
    //        servletContext.addFilter("errorFilter", ErrorFilter.class)
    //                .addMappingForServletNames(EnumSet.of(DispatcherType.ERROR), false, "ERROR-HANDLER");
            //TODO OTHER THINGS
        }
    }
    
    

    6.2 注解编程

    除了ServletContainerInitializer 接口之外,Servlet 也提供了非常多的注解以供快速注册Servlet、Listener和Filter等。用户可以不适用任何xml配置和Java配置编程,仅使用注解完成大部分配置工作。注解编程通过 metadata-complete属性开启或关闭。

    6.2.1 注解说明

    注解功能
    @WebServlet注册Servlet
    @WebFilter注册Filter
    @WebListener注册Listener

    6.2.2 metadata-complete属性

    类型:Boolean
    释义:

    • true
      • 不扫描应用的类的注解
      • 不读取jar文件中的web-fragment.xml
    • false 默认值
      • 扫描应用的类的注解
      • 读取jar文件中的web-fragment.xml

    配置方式

    <web-app metadata-complete="true">
    </web-app>
    

    6.2.3 可插拔式配置文件 web-fragment.xml

    包括上述的注解和 web-fragment.xml 文件,都可以通过 metadata-complete配置实现可插拔。
    与web.xml内容差异不大。以 标签包围
    仅对特化标签做说明

    • name 配置名称
    • ording 配置加载顺序,也可以在web.xml文件中通过 absolute-ording 标签强制排序,web.xml 必然是第一个加载的

    web-fragment.xml 示例

    <web-fragment> 
     <name>MyFragment1</name> 
     <ordering><after><name>MyFragment2</name></after></ordering> 
     ... 
    </web-fragment>
    

    web.xml 排序示例

    <web-app> 
    <absolute-ordering> 
     <name>MyFragment3</name> 
     <name>MyFragment2</name> 
     </absolute-ordering> 
     ... 
    </web-app>
    
    展开全文
    weixin_43740223 2021-04-24 14:19:49
  • weixin_45394002 2021-01-13 14:22:48
  • weixin_30253289 2021-02-12 14:22:32
  • faramita_of_mine 2021-11-26 23:10:25
  • weixin_45228360 2021-09-07 14:36:14
  • qq_40624026 2021-02-17 21:52:58
  • sinat_33087001 2021-07-20 11:00:32
  • weixin_48211214 2021-03-26 18:45:23
  • weixin_35974217 2021-03-12 20:09:26
  • weixin_32396347 2021-02-13 01:53:22
  • zzj_csdn 2021-11-09 23:38:38

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,113,944
精华内容 445,577
关键字:

sevlet