tomcat的web三大组件&Sci&idea搭建web/maven的tomcat项目

发布于:2025-04-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

1. web项目的搭建

1. 创建web项目

在这里插入图片描述

2.修改web.xml版本

在这里插入图片描述

3.添加servlet、jsp依赖

image-20200430180453970

4.servlet示例(使用注解)

image-20200430180613640

5.配置tomcat

image-20200430180754362

image-20200430180933488

6.添加artifact

image-20200430181123706

在这里插入图片描述

7.部署

image-20200430181449134

image-20200430181541176

在这里插入图片描述

8.启动tomcat、访问

image-20200430181738261

image-20200430181815986

9.打war包

在这里插入图片描述

image-20200430182404092

image-20200430182442284

image-20200430182529149

image-20200430182603751

10.部署到tomcat

将war包复制到tomcat的webapps目录下,在tomcat的bin目录下双击startup.bat运行项目

  • 如果是linux双击startup.sh即可运行项目,

  • tomcat如果早就启动了,那么复制war包到webapps目录下后,war包会自动解压并运行

2.maven的项目搭建

1.创建项目

图解

image-20200911004339288

image-20200911004611337

image-20200911004854511

image-20200911005133875

2.tomcat启动方式

图解

image-20200911012718299

image-20200911012856673

image-20200911013012668

idea打war包

image-20200911005452759

image-20200911010206891

3.改为使用maven启动

上面使用tomcat启动时,pom.xml中什么都没有写,现在改成使用maven启动,pom的xml中如下配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zzhua</groupId>
    <artifactId>tomcat-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--一定要设置打包方式为war-->
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <!-- 配置tomcat的运行插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <!-- 配置端口 -->
                     <port>8080</port>
                    <!-- 配置urlencoding -->
                     <uriEncoding>UTF-8</uriEncoding>
                    <!-- 配置项目的访问路径 -->
                     <path>/zzhua</path>

                    <!-- 配置tomcat上传war包的路径 -->
                    <!--<url>http://127.0.0.1:8080/manager/text</url>-->
                    <!-- tomcat的登陆名 -->
                    <!--<username>admin</username>-->
                    <!-- tomcat的登陆密码-->
                    <!--<password>admin</password>-->
                    <!-- 设置打包的名称 -->
                    <!--<path>/zzhua</path>-->
                </configuration>
            </plugin>

            <!-- 配置jdk的编译版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <!-- 指定source和target的版本 -->
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
图解

image-20210307100832348

剩下的Facets和Artifacts这两个里面都是空的

image-20210307101001969

在这里插入图片描述

idea打war包

image-20210307101918425

5.三大组件

web.xml配置方式

<?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>
        <servlet-name>userServlet</servlet-name>
        <servlet-class>com.zzhua.servlet.UserServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>userServlet</servlet-name>
        <url-pattern>/user</url-pattern>
    </servlet-mapping>

    <!--配置自定义过滤器 Filter-->
    <filter>
        <filter-name>userFilter1</filter-name>
        <filter-class>com.zzhua.filter.UserFilter1</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>userFilter1</filter-name>
        <url-pattern>/user</url-pattern>
    </filter-mapping>

    <!--配置自定义过滤器 Filter-->
    <filter>
        <filter-name>userFilter2</filter-name>
        <filter-class>com.zzhua.filter.UserFilter2</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>userFilter2</filter-name>
        <url-pattern>/user</url-pattern>
    </filter-mapping>

    <!--配置监听器-->
    <listener>
        <listener-class>com.zzhua.listener.UserListener</listener-class>
    </listener>

</web-app>

1.Servlet

1.介绍

​ Servlet是用来处理请求的

HttpServlet继承GenericServlet,GenericServlet继承javax.servlet

2.Servlet接口
public interface Servlet {

    public void init(ServletConfig config) throws ServletException;  // 初始化方法
    
    // ...可以实现上面这个方法,拿到tomcat传过来的servletConfig并保存到本servlet对象中, 
    //    然后在下面这个方法中的实现中获取保存的servletConfig

    public ServletConfig getServletConfig(); // 获取servlet配置的方法

    // 处理请求的方法
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    public String getServletInfo();

    public void destroy(); // 销毁的方法
}
3.ServletConfig
/**
 * A servlet configuration object used by a servlet container to pass
 * information to a servlet during initialization.
 */
public interface ServletConfig {

    /**
     * Returns the name of this servlet instance. The name may be provided via
     * server administration, assigned in the web application deployment
     * descriptor, or for an unregistered (and thus unnamed) servlet instance it
     * will be the servlet's class name.
     *
     * @return the name of the servlet instance
     */
    public String getServletName(); //================= 可以获取到servlet的名字

    /**
     * Returns a reference to the {@link ServletContext} in which the caller is
     * executing.
     *
     * @return a {@link ServletContext} object, used by the caller to interact
     *         with its servlet container
     * @see ServletContext
     */
    public ServletContext getServletContext(); // ============= 可以获取到ServletContext

    /**
     * Returns a <code>String</code> containing the value of the named
     * initialization parameter, or <code>null</code> if the parameter does not
     * exist.
     *
     * @param name
     *            a <code>String</code> specifying the name of the
     *            initialization parameter
     * @return a <code>String</code> containing the value of the initialization
     *         parameter
     */
    public String getInitParameter(String name); // ============ 根据名字获取配置的初始化参数对应的值

    /**
     * Returns the names of the servlet's initialization parameters as an
     * <code>Enumeration</code> of <code>String</code> objects, or an empty
     * <code>Enumeration</code> if the servlet has no initialization parameters.
     *
     * @return an <code>Enumeration</code> of <code>String</code> objects
     *         containing the names of the servlet's initialization parameters
     */
    public Enumeration<String> getInitParameterNames(); // ============ 获取到所有配置的初始化参数的名字
}
4.使用
public class UserServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("userServlet...init初始化");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
                                                        throws ServletException, IOException {
        // resp.setContentType("text/html;charset=utf-8"); // 或者如下设置,否则会中文乱码
        resp.setHeader("content-type","text/plain;charset=UTF-8");
        resp.getWriter().write("Hello, 你好!");
    }
}

2.Filter

1.介绍

请求被Servlet处理之前,被Filter过滤器拦截,放行之后,请求才会被Servlet处理

如果配置了多个过滤器,那么将会按照顺序依次拦截

2.Filter接口
public interface Filter {

    // 初始化方法, 可以从filterConfig中获取ServletContext
    public void init(FilterConfig filterConfig) throws ServletException; 

    // 过滤的逻辑, 通过执行chain.do(request,response)才能放行请求
    public void doFilter(ServletRequest request, 
                         ServletResponse response,
                         FilterChain chain       )  throws IOException, ServletException;
    // 销毁的方法
    public void destroy(); 
}
3.FilterConfig
/**
 *
 * A filter configuration object used by a servlet container to pass information
 * to a filter during initialization.
 *
 * @see Filter
 * @since Servlet 2.3
 */
public interface FilterConfig {

    /**
     * Get the name of the filter.
     *
     * @return The filter-name of this filter as defined in the deployment
     *         descriptor.
     */
    public String getFilterName(); // ============= 获取到为该filter配置的名字

    /**
     * Returns a reference to the {@link ServletContext} in which the caller is
     * executing.
     *
     * @return {@link ServletContext} object, used by the caller to interact
     *         with its servlet container
     *
     * @see ServletContext
     */
    public ServletContext getServletContext(); // ========== 获取到servletContext

    /**
     * Returns a <code>String</code> containing the value of the named
     * initialization parameter, or <code>null</code> if the parameter does not
     * exist.
     *
     * @param name
     *            <code>String</code> specifying the name of the initialization
     *            parameter
     *
     * @return <code>String</code> containing the value of the initialization
     *         parameter
     */
    public String getInitParameter(String name); // ============= 根据名字获取配置的初始化参数对应的值

    /**
     * Returns the names of the filter's initialization parameters as an
     * <code>Enumeration</code> of <code>String</code> objects, or an empty
     * <code>Enumeration</code> if the filter has no initialization parameters.
     *
     * @return <code>Enumeration</code> of <code>String</code> objects
     *         containing the names of the filter's initialization parameters
     */
    public Enumeration<String> getInitParameterNames(); // ============ 获取到所有配置的初始化参数的名字

}
4.使用
public class UserFilter1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("userFilter1 init...自定义过滤器userFilter1初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, 
                         ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, ServletException {
        
        System.out.println("userFilter1 doFilter...自定义过滤器userFilter1执行");
        // 放行, 不执行这一句将会拦截掉请求, 不会执行后面的filter和servlet,责任链模式
        filterChain.doFilter(servletRequest,servletResponse); 
    }

    @Override
    public void destroy() {
        System.out.println("userFilter1 destroy...自定义过滤器userFilter1销毁");
    }
}
5.源码分析
public final class ApplicationFilterChain implements FilterChain {
    /**
     * Filters. ApplicationFilterConfig封装了匹配当前请求的所有filter
     *                                 (注意这里并不是所有的filter,匹配的过程是tomcat自己做的)
     *                                 (并且执行完请求后,这个数据的每个元素都会release掉,即置为null)
     */
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];


    /**
     * The int which is used to maintain the current position
     * in the filter chain.
     */
    private int pos = 0; // 索引, 当前执行的filter在上面这个数组中的位置


    /**
     * The int which gives the current number of filters in the chain.
     */
    private int n = 0; // 一共配置的filter的数量


    /**
     * The servlet instance to be executed by this chain.
     */
    private Servlet servlet = null;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response); // 调用下面这个方法
        }
    }

    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        // 0 -> 2  ,假设一共有两个Filter
        // 第一轮: pos = 0, n = 2, 做判断 0 < 2, 好,这个时候pos + 1 所以此时pos = 1
        //        假设此时的第一个Filter没有调用chain.doFilter(req,resp)方法,那么会执行下面的return;
        //        就不会执行下面的servlet调用service方法了
        // 假设第一轮中,调用了chain.doFilter,这个,又回到上面这个方法,接着又调用本方法, 注意此时是同一个FilterChain
        //        此时 pos = 1, 所以 1 < 2, 依旧成立, 好,这个时候pos + 1 所以此时pos = 2
        //        假设第二个过滤器如果又没调用chain.doFilter那么filter.doFilter(request, response, this);
        //        这一句又断了,就直接执行return;
        //        就不会执行下面的servlet调用service方法了
        // 第二轮: 假设第二轮调用了,那么又会回到当前的方法2<2不成立, 那么就会执行下面的servlet调用service方法了
        // 其实chain.doFilter可以这样理解,chain把过滤的具体功能交给filter来实现, 而filter做完自己的过滤功能后,
        //    因为已经拿到了chain, 那么就有权决定,是否要把执行权交回给chain, 如果不交回, 那么chain就断了。
        // =========这是责任链模式的另外一种用法========
        // 思考: 假设其中某一个Filter, 它不是在最后一句调用的filterChain.doFilter
        //      那么其实也实现了spring mvc的拦截器一样的功能,等servlet处理完了之后, 
        //      然后逆序调用filter的那句话后面的代码,
        //      特别注意: 这个时候处理完的request和response已经让servlet处理过了, 
        //      所以filter可以继续接着对这个处理完成了的请求中的request和response对象处理
        //      然后再返回给客户端。
        //      假设很多个过滤器, 前面几个都通过了,但是被一个给拦住了,那么也会逆序回调过滤器dofilter代码之后面的代码        

        if (pos < n) { //
            ApplicationFilterConfig filterConfig = filters[pos++]; // 根据索引找filter
            try {
                Filter filter = filterConfig.getFilter(); //  先获取到filter,如果之前创建过,直接返回,如果没有,
                                                          //  那么通过反射创建,并回调初始化方法,然后传个东西给
                                                          //  它。注意这里的filterConfig是
                 										  //  ApplicationFilterConfig。
                // 下面分析以下ApplicationFilterConfig
                //     ApplicationFilterChain中组合了配置的Filter,并都封装成了ApplicationFilterConfig数组,
                //     并且ApplicationFilterConfig中封装了我们配置的filter的所有相关信息,创建filter也是由
                //     ApplicationFilterConfig这个对象创建的。所以tomcat读取web.xml中的filter时,创建的并不是
                //     Filter,而是ApplicationFilterConfig,由这个类来对filter的配置信息做一个封装。真正执行的时候
                //     从ApplicationFilterConfig类中获取filter,如果之前创建过,那么直接返回,如果之前没有创建过,
                //     那么通过反射创建Filter,创建好后。
                //     调用Filter的init方法,并且把当前对象this,也就是ApplicationFilterConfig,传进去。因为
                //     ApplicationFilterConfig还实现了FilterCOnfig接口,它里面还封装了Context,而从Context里面
                //     由能拿到ServletContext。
                //     所以filter重写的init方法中的filterConfig实际上就是ApplicationFilterconfig

                
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return; // 这个return;语句是关键,如果中间的FilterChain断了的话,
                    //            那么会直接到这里来,下面调用servlet的方法也不会执行了
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                // 上面的过滤器执行完了,才轮到servlet去处理这个请求
                //     注意:这里不一定执行完了, 可以看下上面的思考
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }
}

3.监听器

1.介绍

当web应用启动,创建好ServletContext时,会回调配置的监听器的contextInitialized方法,并且将ServletContextEvent对象传递过来。

除了ServletContextListener接口,tomcat还会回调以下Listener(只要把以下listener配置给tomcat,使用xml或注解方式)

  • HttpSessionListener、HttpSessionAttributeListener、HttpSessionIdListener、HttpSessionBindingListener
  • ServletRequestListener、ServletRequestAttributeListener
  • ServletContextListener、ServletContextAttributeListener
2.Listener接口

以ServletContextListener为例

public interface ServletContextListener extends EventListener {

    /**
     ** Notification that the web application initialization process is starting.
     * All ServletContextListeners are notified of context initialization before
     * any filter or servlet in the web application is initialized.
     * @param sce Information about the ServletContext that was initialized
     */
     // 通知web应用开始初始化,通知所有ServletContextListeners回调contextInitialized方法,  
     // (并且这个回调的contextInitialized方法的执行时机,任何的filter或者servlet之前)
     //          1.可以从ServletContextEvent事件对象中取出ServletContext 
     //          2.这样又可以从servletContext中获取到配置的全局属性,即context-param配置的
     //          3.也可以在此处获取到servletContext,然后使用它来注册servlet或者filter(添加返回的Dynamic配置)
    public void contextInitialized(ServletContextEvent sce);

    /**
     ** Notification that the servlet context is about to be shut down. All
     * servlets and filters have been destroy()ed before any
     * ServletContextListeners are notified of context destruction.
     * @param sce Information about the ServletContext that was destroyed
     */ 
    // 容器销毁, 这个方法被执行, 并且执行时间是在 任何的filter或者servlet执行销毁方法之后
    // 可以从ServletContextEvent事件对象中取出ServletContext  
    public void contextDestroyed(ServletContextEvent sce);
}
3.ServletContext
public interface ServletContext {
    String TEMPDIR = "javax.servlet.context.tempdir";
    String ORDERED_LIBS = "javax.servlet.context.orderedLibs";

    String getContextPath();

    ServletContext getContext(String var1);

    int getMajorVersion();

    int getMinorVersion();

    int getEffectiveMajorVersion();

    int getEffectiveMinorVersion();

    String getMimeType(String var1);

    Set<String> getResourcePaths(String var1);

    URL getResource(String var1) throws MalformedURLException;

    InputStream getResourceAsStream(String var1);

    RequestDispatcher getRequestDispatcher(String var1);

    RequestDispatcher getNamedDispatcher(String var1);

    /** @deprecated */
    Servlet getServlet(String var1) throws ServletException;

    /** @deprecated */
    Enumeration<Servlet> getServlets();

    /** @deprecated */
    Enumeration<String> getServletNames();

    void log(String var1);

    /** @deprecated */
    void log(Exception var1, String var2);

    void log(String var1, Throwable var2);

    String getRealPath(String var1);

    String getServerInfo();

    // 根据在web.xml配置的<context-param>标签中配置的key,拿配置的value
    /* <context-param>
          <param-name>name</param-name>
          <param-value>zzhua173</param-value>
       </context-param>
    */
    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();

    boolean setInitParameter(String var1, String var2);

    Object getAttribute(String var1);

    Enumeration<String> getAttributeNames();

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);

    String getServletContextName();

    Dynamic addServlet(String var1, String var2);

    Dynamic addServlet(String var1, Servlet var2);

    Dynamic addServlet(String var1, Class<? extends Servlet> var2);

    <T extends Servlet> T createServlet(Class<T> var1) throws ServletException;

    ServletRegistration getServletRegistration(String var1);

    Map<String, ? extends ServletRegistration> getServletRegistrations();

    javax.servlet.FilterRegistration.Dynamic addFilter(String var1, String var2);

    javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Filter var2);

    javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Class<? extends Filter> var2);

    <T extends Filter> T createFilter(Class<T> var1) throws ServletException;

    FilterRegistration getFilterRegistration(String var1);

    Map<String, ? extends FilterRegistration> getFilterRegistrations();

    SessionCookieConfig getSessionCookieConfig();

    void setSessionTrackingModes(Set<SessionTrackingMode> var1);

    Set<SessionTrackingMode> getDefaultSessionTrackingModes();

    Set<SessionTrackingMode> getEffectiveSessionTrackingModes();

    void addListener(String var1);

    <T extends EventListener> void addListener(T var1);

    void addListener(Class<? extends EventListener> var1);

    <T extends EventListener> T createListener(Class<T> var1) throws ServletException;

    JspConfigDescriptor getJspConfigDescriptor();

    ClassLoader getClassLoader();

    void declareRoles(String... var1);

    String getVirtualServerName();
}
4.使用
public class UserListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute("ctx", servletContext.getContextPath());
        System.out.println("UserListener contextInitialized...自定义监听器初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("UserListener contextDestroyed...自定义监听器销毁");
    }
}

注解的配置方式

自servlet3.0开始, tomcat支持使用注解的方式代替web.xml配置的方式, 向web容器中注册Servlet,Filter,Listener.

如:@WebServlet,@WebFilter,@WebListener。

只要将继承自Servlet的非抽象类上加上@WebServlet的注解,启动tomcat后这个类将会被自动扫描到,并实例化成servlet来处理请求,同理filter和listener

1.@WebServlet
/**
 * 自servlet3.0开始, tomcat支持使用注解的方式代替web.xml配置的方式, 向web容器中注册Servlet,Filter,Listener
 * 			只要把这个类打进war包里,tomcat会自动识别到这个类。因此可以猜测:tomcat会对war包里面的class逐个扫描,
 *          检查每个类上是否有这个注解,如果有, 那么读取注解上的配置(和读取web.xml类似), 并向web容器中注册这个组件。
 *          有点像SPI机制
 */
@WebServlet(
        urlPatterns = {"/anno"} , // 不能丢“/”
        initParams = {            // 配置初始化参数
                @WebInitParam(name = "name",value="zzhua"),
                @WebInitParam(name = "age",value="25")
        }
)
public class AnnoServlet implements Servlet {

    private static ServletConfig SERVLET_CONFIG;

    private static ServletContext SERVLET_CONTEXT = null;

    public AnnoServlet() {
        System.out.println(1);
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String initParamName = initParameterNames.nextElement();
            System.out.println(initParamName + " - " + servletConfig.getInitParameter(initParamName));
        }
        SERVLET_CONTEXT = servletConfig.getServletContext();
        AnnoServlet.SERVLET_CONFIG = servletConfig;
    }

    @Override
    public ServletConfig getServletConfig() {
        return AnnoServlet.SERVLET_CONFIG;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("我拿到了req和res");
    }

    @Override
    public String getServletInfo() {
        return "annoServlet";
    }

    @Override
    public void destroy() {

    }
}
2.@WebFilter
public class Demo1Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String initParamName = initParameterNames.nextElement();
            System.out.println(initParamName + "-" + filterConfig.getInitParameter(initParamName));

        }
    }

    @Override
    public void doFilter(ServletRequest servletRequest, 
                         ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, ServletException {
        System.out.println("经过demo1过滤器...");
    }

    @Override
    public void destroy() {

    }
}
3.@WebListener
@WebListener
public class RequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("请求对象被销毁");
        RequestHolder.setRequest(null); // 清除该请求对象

    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("当前线程: " + Thread.currentThread().getName());
        HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
        // 请求能这么存的前提是: tomcat是在本线程里面回调的此方法,并且也是在本线程里面处理该请求,证明确实是这样的
        RequestHolder.setRequest(servletRequest); // 将tomcat当前线程上创建的request对象放入RequestHolder中
    }
}

ServletContextListener配置tomcat组件方式

​ 在tomcat中,配置ServletContextListener接口的监听器给tomcat(用上面的2种方式,web.xml方式或者@WebListener注解方式),tomcat会在创建完ServletContext时,回调配置的ServletContextListener监听器的contextInitialized方法,并且会回传ServletContextEvent事件对象,该事件对象中包装了ServletContext对象(即可以从contextInitialized方法中回传的ServletContextEvent对象参数中获取到ServletContext)。特别注意:此时,filter和servlet都还未初始化,所以此时我们可以往tomcat中添加listener、filter、servlet,(这里不能添加ServletContextListener接口的监听器,只能添加其它监听器接口的监听器,比如HttpSessionListener、ServletRequestListener等)

@WebListener
public class UserListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        
        // 从servletContextEvent中获取ServletContext
        ServletContext servletContext = servletContextEvent.getServletContext();
        
        // 使用ServletContext监听器注册了一个ServletListener
        servletContext.addListener(new ServletRequestListener() {
            @Override
            public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
                ServletRequest servletRequest = servletRequestEvent.getServletRequest();
                System.out.println("==>请求被销毁");
            }

            @Override
            public void requestInitialized(ServletRequestEvent servletRequestEvent) {
                System.out.println("==>监听器listener");
            }
        });
        
         // 使用ServletContext监听器注册了一个HttpSessionListener
        servletContext.addListener(new HttpSessionListener() {
            @Override
            public void sessionCreated(HttpSessionEvent httpSessionEvent) {
                System.out.println("session created...");
            }

            @Override
            public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
                System.out.println("session destroyed...");
            }
        });
        
        // ServletContext监听器注册了一个servlet
        ServletRegistration.Dynamic dynamicServlet = servletContext.addServlet("anonymousServlet", 
                                                                               new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, 
                                 HttpServletResponse resp) throws ServletException, IOException {
                resp.getWriter().write("==>hello dynamicServlet");
            }

            @Override
            public void init() throws ServletException {
                System.out.println("init....anonymous servlet");
            }
        });
        dynamicServlet.addMapping("/anonymous");
        dynamicServlet.setLoadOnStartup(1);

        // ServletContext监听器注册了一个filter
        FilterRegistration.Dynamic dynamicFilter 
            = servletContext.addFilter("anonymousFilter", 
                                       new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                System.out.println("==>"+filterConfig);
            }

            @Override
            public void doFilter(ServletRequest servletRequest, 
                                 ServletResponse servletResponse, 
                                 FilterChain filterChain) throws IOException, ServletException {
                System.out.println("==>拦截");
                filterChain.doFilter(servletRequest,servletResponse);
            }

            @Override
            public void destroy() {

            }
        });
        dynamicFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class),
                                               true,"/anonymous/*");

        System.out.println("UserListener contextInitialized...自定义监听器初始化");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("UserListener contextDestroyed...自定义监听器销毁");
    }
}
servlet 3.0之动态注册

先看下Registration的继承体系

image-20210308142902610

动态注册使用

说实话,对servlet 3动态加载servlet的机制有些失望,本来期望着可以在运行时完成对servlet的注册和销毁,但现状是,只能在webapp启动在初始化时进行完成注册,可能是为了安全考虑吧。

在Servlet3.0中可以动态注册Servlet,Filter,Listener,每个组件注册都提供三个方法,很细心。

下面谈谈动态注册Servlet,但不要希望太高,只能在初始化时进行注册。在运行时为了安全原因,无法完成注册。在初始化情况下的注册Servlet组件有两种方法:

  • 1.实现ServletContextListener接口,在contextInitialized方法中完成注册.
  • 2.在jar文件中放入实现ServletContainerInitializer接口的初始化器

在ServletContext类中对应注册API为:

  /** 
   * 添加Servlet 
   */ 
  public ServletRegistration.Dynamic addServlet(String servletName, String className); 

  public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet); 

  public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> 
                                                                                    servletClass); 

  /** 
   * 添加Filter 
   */ 
  public FilterRegistration.Dynamic addFilter(String filterName, String className); 

  public FilterRegistration.Dynamic addFilter(String filterName, Filter filter); 

  public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass); 

  /** 
   * 添加Listener 
   */ 
  public void addListener(String className); 

  public <T extends EventListener> void addListener(T t); 

  public void addListener(Class<? extends EventListener> listenerClass);

先说在ServletContextListener监听器中完成注册。

public void contextInitialized(ServletContextEvent sce) { 

    ServletContext sc = sce.getServletContext(); 

    // Register Servlet 
    ServletRegistration sr = sc.addServlet("DynamicServlet", 
                                           "web.servlet.dynamicregistration_war.TestServlet"); 
    sr.setInitParameter("servletInitName", "servletInitValue"); 
    sr.addMapping("/*"); 

    // Register Filter 
    FilterRegistration fr = sc.addFilter("DynamicFilter", 
                                         "web.servlet.dynamicregistration_war.TestFilter"); 
    fr.setInitParameter("filterInitName", "filterInitValue"); 
    fr.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), 
                                 true, "DynamicServlet"); 

    // Register ServletRequestListener 
    sc.addListener("web.servlet.dynamicregistration_war.TestServletRequestListener"); 
}

再说说在jar文件中的servlet组件注册,

需要在jar包含META-INF/services/javax.servlet.ServletContainerInitializer文件,文件内容为已经实现ServletContainerInitializer接口的类:

com.learn.servlet3.jardync.CustomServletContainerInitializer

该实现部分代码:

@HandlesTypes({ JarWelcomeServlet.class }) 
public class CustomServletContainerInitializer implements ServletContainerInitializer { 
  private static final Log log = LogFactory .getLog(CustomServletContainerInitializer.class); 

  private static final String JAR_HELLO_URL = "/jarhello"; 

  public void onStartup(Set<Class<?>> c, ServletContext servletContext)  throws ServletException { 
      
    log.info("CustomServletContainerInitializer is loaded here..."); 
    log.info("now ready to add servlet : " + JarWelcomeServlet.class.getName()); 
    
    ServletRegistration.Dynamic servlet = servletContext.addServlet( 
                                                        JarWelcomeServlet.class.getSimpleName(), 
                                                        JarWelcomeServlet.class
                                          ); 
    servlet.addMapping(JAR_HELLO_URL); 

    log.info("now ready to add filter : " + JarWelcomeFilter.class.getName()); 
      
    FilterRegistration.Dynamic filter = servletContext.addFilter( 
                                                              JarWelcomeFilter.class.getSimpleName(), 
                                                              JarWelcomeFilter.class); 

    EnumSet<DispatcherType> dispatcherTypes = EnumSet .allOf(DispatcherType.class); 
    dispatcherTypes.add(DispatcherType.REQUEST); 
    dispatcherTypes.add(DispatcherType.FORWARD); 

    filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL); 

    log.info("now ready to add listener : " + JarWelcomeListener.class.getName()); 
    servletContext.addListener(JarWelcomeListener.class); 
  } 
}

其中@HandlesTypes注解表示 CustomServletContainerInitializer 可以处理的类,在 onStartup方法中,可以通过 <Set> c获取得到。

jar文件中不但可以包含需要自定义注册的servlet,也可以包含应用注解的servlet,具体怎么做,视具体环境而定。把处理某类事物的servlet组件打包成jar文件,有利于部署和传输,功能不要了,直接去除掉jar即可,方便至极!

ServletContainerInitializer

SCI规范

规范

  1. 在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filters等,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的==META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer==的文件,文件内容指定具体的**ServletContainerInitializer实现类**,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作

    (注意:在springboot中这样做是没有效果的)。

  2. 伴随着ServletContainerInitializer一起使用的还有**@HandlesTypes**注解,通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup方法作为参数传入(这个感兴趣的类从所有的类中找,搜索算法由tomcat负责)。

/**
 * 
 * ServletContainerInitializers (SCIs) are registered via an entry in the
 * file META-INF/services/javax.servlet.ServletContainerInitializer that must be
 * included in the JAR file that contains the SCI implementation.
 * <p>
 * SCI processing is performed regardless of the setting of metadata-complete.
 * SCI processing can be controlled per JAR file via fragment ordering. If
 * absolute ordering is defined, then only the JARs included in the ordering
 * will be processed for SCIs. To disable SCI processing completely, an empty
 * absolute ordering may be defined.
 * <p>
 * SCIs register an interest in annotations (class, method or field) and/or
 * types via the {@link javax.servlet.annotation.HandlesTypes} annotation which
 * is added to the class.
 *
 * @since Servlet 3.0  3.0开始支持SCIs
 */
public interface ServletContainerInitializer {

    /**
     * ServletContainerInitializer的实现类上可以添加@HandleTypes,并且在这个注解里写上感兴趣的类,
     *     则这个类的子类(子接口、子抽象类、子实现类,总之只会向下寻找,并且不包括当前类)都将添加到一个set集合中,
     *     并将这个set集合传给ServletContainerInitializer的实现类的onStartUp方法作为第一个参数
     *                                                       ServletContext作为第二个参数
     *
     * Receives notification during startup of a web application of the classes
     * within the web application that matched the criteria defined via the
     * {@link javax.servlet.annotation.HandlesTypes} annotation.
     *
     * @param c     The (possibly null) set of classes that met the specified
     *              criteria
     * @param ctx   The ServletContext of the web application in which the
     *              classes were discovered
     *
     * @throws ServletException If an error occurs
     */
    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

tomcat启动

image-20210307230629598

ServletContainerInitializer

maven启动

image-20210307230900580

执行顺序

请看【tomcat组件初始化顺序和请求执行顺序】这一节

意义
  • 1.我们可以利用ServletContainerInitializer机制,使用完全基于代码的方式配置ServletContainer容器(注册三大组件),取代以前使用web.xml和注解这种两种注册组件的方式
  • 2.因为ServletContainerInitializer是在jar包的META-INF/services下的javax.servlet.ServletContainerInitializer文件中的,我们可以添加或者移除这个jar包,从而实现功能的可插拔性(可插拔性指的是对ServletContext的配置的删减)。
  • spring-web jar包下有:SpringServletContainerInitializer这个类,它实现了ServletContainerInitializer接口。

6.tomcat组件初始化顺序和请求执行顺序

初始化顺序

image-20210418163540994

请求执行顺序

tomcat请求执行顺序

7.servlet的基本用法

1.请求转发Forward

转发给其它servlet

@WebServlet("/skip")
public class SkipServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, 
                           HttpServletResponse resp) throws ServletException, IOException {
        // 请求转发是服务器内部的行为,
        req.getRequestDispatcher("forward").forward(req,resp); // 转发给下面这个servlet处理
    }
}
@WebServlet("/forward")
public class ForwardServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, 
                         HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("hello forward");
        System.out.println("请求转发是服务内部的转发,让另外一个servlet接着处理...");
        // 这个就是springmvc中InternalResourceViewResolver视图解析器的原理
        // tomcat对WEB-INF下的jsp(包括其它在内的所有资源)是不能直接访问的,必须要通过请求转发,才能渲染出来被访问到
        // (但请求转发意味着又会让tomcat决定走哪个Servlet(即:是走jsp呢?还是继续走DispacherServlet呢?)
        //   如果转发请求的后缀是以jsp结尾,那么优先是根据转发请求的url找对应目录下的jsp处理请求并渲染出来,注意这个
        //   优先是指在没有配置/*的前提下(springmvc的前端控制器的url-pattern须配置成/而非/*))
        // 而对其它目录下的jsp试可以直接在浏览器输入url访问的。
        req.getRequestDispatcher("/WEB-INF/forward.jsp").forward(req,resp);
    }
}

转发给jsp

@WebServlet("/test01")
public class Test1Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
                                                     throws ServletException, IOException {
        req.getRequestDispatcher("/test1.jsp").forward(req,resp);
    }
}

image-20210504110806754

此处踩过坑一:在web.xml中配置spring-mvc的前端控制器时(即DispatcherServlet),把url-pattern设置成了/*,这样会导致所有的请求都给该DispatcherServlet处理了,哪怕是调用RequestDispatcher#forward(req,resp)方法转发的请求,也都被该前端控制器处理了,结果就是导致写的jsp根本渲染不出数据(只在浏览器上显示了jsp的源代码)。所以应该配置为"/“,而不是”/*"。显示为jsp源代码的原因就是,springmvc的InternalResourceViewResolver视图解析器组件能拿到这个文件,并且render出来。而并不是将请求转发给tomcat后,tomcat没有把请求交给该jsp(jsp本质上也是个Servlet)处理。所以tomcat在接收到我们需要转发请求的需求时,它把请求继续交给DispatcherServlet处理,但实际理应交给jsp来处理(这样才会走jsp渲染的逻辑),就是因为配置了/*,而把所有的请求都给了DispatcherServlet处理了。也就是说转发请求的时候,又会让tomcat决定此次转发请求将走哪个Servlet(即:是走jsp呢?还是继续走DispacherServlet呢?),并且如果转发请求的url是以jsp结尾,那么在springmvc如果配置的url-pattern是/而不是/*的情况下(如果配置的是/*,那么又会让DispatcherServlet去处理该转发请求),是会优先去找对应的jsp去处理请求并渲染出来。

转发给静态html

@WebServlet("/test02")
public class Test2Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req,
                         HttpServletResponse resp)
            throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/test002.html")
                .forward(req,resp);
    }
}

image-20210504111039618

2.重定向Redirect

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, 
                         HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect(req.getContextPath() + "/redirect.jsp"); // 需要加上contextPath
    }
}

3.保存当前线程的请求对象

public class RequestHolder {
    private static final ThreadLocal<HttpServletRequest> requestThreadLocal = new ThreadLocal<>();

    public static void setRequest(HttpServletRequest request) {
        requestThreadLocal.set(request);
    }

    public static HttpServletRequest getRequest() {
        return requestThreadLocal.get();
    }

}
@WebListener
public class RequestListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("请求对象被销毁");
        RequestHolder.setRequest(null); // 清除该请求对象

    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("当前线程: " + Thread.currentThread().getName());
        HttpServletRequest servletRequest = (HttpServletRequest)sre.getServletRequest();
        // 请求能这么存的前提是: tomcat是在本线程里面回调的此方法,并且也是在本线程里面处理该请求,证明确实是这样的
        RequestHolder.setRequest(servletRequest); // 将tomcat当前线程上创建的request对象放入RequestHolder中
    }
}
@WebServlet("/request")
public class RequestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, 
                         HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        System.out.println(session);

        System.out.println("当前线程: " + Thread.currentThread().getName());
        System.out.println("拿到的是同一个请求吗? " +( RequestHolder.getRequest() == req)); // true

    }
}