自己动手写一个tomcat之7监听器和过滤器

写在前面

源码

本文来实现下过滤器和监听器相关内容。

1:过滤器

过滤器的功能是在servlet执行前和执行后执行一些动作,即属于wrapper容器内的功能。servlet规范中也定义了相关的接口,比如过滤器接口javax.servlet.Filter:

java 复制代码
package javax.servlet;

import java.io.IOException;

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() {}
}

以及对应的配置接口:

java 复制代码
package javax.servlet;

import java.util.Enumeration;

public interface FilterConfig {

    public String getFilterName();

    public ServletContext getServletContext();
    public String getInitParameter(String name);

    public Enumeration<String> getInitParameterNames();

}

首先定义配置类,同时维护将来用户自定义的Filter:

java 复制代码
package monitomcat.server.core;

// ...
/**
 * 包装filter+filter配置
 */
final class ApplicationFilterConfig implements FilterConfig {

    public ApplicationFilterConfig(Context context, FilterDef filterDef)
            throws ClassCastException, ClassNotFoundException,
            IllegalAccessException, InstantiationException,
            ServletException {
        super();
        this.context = context;
        setFilterDef(filterDef);
    }

    private Context context = null;
    private Filter filter = null;
    private FilterDef filterDef = null;

    // ...
}

FilterDef是用户配置的过滤器定义信息,如下:

java 复制代码
package monitomcat.server.core;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 因为filter是需要用户在配置文件中定义的,所以需要定义类来对应用户的配置信息,即filter的定义,filter definition
 * <!-- 1.注册Filter -->
 *     <filter>
 *         <!-- filter名字,自定义 -->
 *         <filter-name>EncodingFilter</filter-name>
 *         <!-- 过滤器全类名 -->
 *         <filter-class>com.example.filter.EncodingFilter</filter-class>
 *
 *         <!-- 过滤器初始化参数,filterConfig可以读取 -->
 *         <init-param>
 *             <param-name>charset</param-name>
 *             <param-value>UTF-8</param-value>
 *         </init-param>
 *
 *         <!-- 容器启动就实例化filter,数字代表加载顺序,数字越小越先加载 -->
 *         <load-on-startup>1</load-on-startup>
 *     </filter>
 */
public final class FilterDef {
    private String description = null;
    private String filterClass = null;
    
    private String filterName = null;

    private Map<String, String> parameters = new ConcurrentHashMap<>();
}

filter多个时支持链式管理:

java 复制代码
package monitomcat.server.core;

// ...

/**
 * 过滤器filter 链,实现责任链,即链式调用
 */
final class ApplicationFilterChain implements FilterChain {
    public ApplicationFilterChain() {
        super();
    }
    private ArrayList<ApplicationFilterConfig> filters = new ArrayList<>();

    private Iterator<ApplicationFilterConfig> iterator = null;

    private Servlet servlet = null;

    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
        System.out.println("FilterChain doFilter()");
        internalDoFilter(request,response);
    }

    private void internalDoFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
        // Construct an iterator the first time this method is called
        if (this.iterator == null)
            this.iterator = filters.iterator();

        // Call the next filter if there is one
        if (this.iterator.hasNext()) {
            ApplicationFilterConfig filterConfig =
                    (ApplicationFilterConfig) iterator.next();
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                System.out.println("Filter doFilter()");

                filter.doFilter(request, response, this);
                // ...

                return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            HttpServletRequest requestFacade = new HttpRequestFacade((HttpRequestImpl) request);
            HttpServletResponse responseFacade = new HttpResponseFacade((HttpResponseImpl) response);

            servlet.service(requestFacade, responseFacade);
            // ...


        }

    void addFilter(ApplicationFilterConfig filterConfig) {
        this.filters.add(filterConfig);
    }

    // ...
}

internalDoFilter方法内部会递归调用doFilter方法从而完成链式调用,形成职责链。当然最终会执行servlet.service(requestFacade, responseFacade);。用户除了需要定义自定义过滤器本身外还需要定义过滤器对哪些请求生效,即过滤哪些请求,针对该信息定义如下对象:

java 复制代码
package monitomcat.server.core;

import monitomcat.server.util.URLDecoder;

/**
 * servlet -> url 映射信息,每个filter都有该配置,用来确定filter对哪些servlet请求生效
 * <!-- 2.映射:指定哪些url走这个过滤器 -->
 *     <filter-mapping>
 *         <!-- 和上面filter-name保持一致 -->
 *         <filter-name>EncodingFilter</filter-name>
 *         <!-- 拦截所有请求 -->
 *         <url-pattern>/*</url-pattern>
 *
 *         <!-- 可指定拦截的Servlet名称 -->
 *         <!-- <servlet-name>MyServlet</servlet-name> -->
 *
 *         <!-- 指定拦截请求方式:REQUEST(普通请求),FORWARD,INCLUDE,ERROR -->
 *         <dispatcher>REQUEST</dispatcher>
 *         <dispatcher>FORWARD</dispatcher>
 *     </filter-mapping>
 */
public final class FilterMap {
    private String filterName = null;

    private String servletName = null;

    private String urlPattern = null;
}

在wrapper容器的父容器context中,即StandardContext类中维护filter的定义以及映射信息:

java 复制代码
package monitomcat.server.core;

public class StandardContext extends ContainerBase implements Context {
    // ...

    public void addFilterDef(FilterDef filterDef) {
        filterDefs.put(filterDef.getFilterName(), filterDef);
    }

    public void addFilterMap(FilterMap filterMap) {
        // 验证所建议的过滤器映射
        String filterName = filterMap.getFilterName();
        String servletName = filterMap.getServletName();
        String urlPattern = filterMap.getURLPattern();
        // ...
        // 因为过滤器模式是2.3中的新功能,所以不需要调整
        // 对于2.2版本的向后兼容性
        if ((urlPattern != null) && !validateURLPattern(urlPattern))
            throw new IllegalArgumentException("standardContext.filterMap.pattern" + urlPattern);
        // 将这个过滤器映射添加到我们已注册的集合中
        synchronized (filterMaps) {
            FilterMap results[] = new FilterMap[filterMaps.length + 1];
            System.arraycopy(filterMaps, 0, results, 0, filterMaps.length);
            results[filterMaps.length] = filterMap;
            filterMaps = results;
        }
    }
    
    // ...

    //对配置好的所有filter名字,创建实例,存储在filterConfigs中,可以生效了
    public boolean filterStart() {
        System.out.println("Filter init..........");
        // 为每个定义的过滤器实例化并记录一个FilterConfig
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            Iterator<String> names = filterDefs.keySet().iterator();
            while (names.hasNext()) {
                String name = names.next();
                ApplicationFilterConfig filterConfig = null;
                try {
                    filterConfig = new ApplicationFilterConfig
                            (this, (FilterDef) filterDefs.get(name));
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    ok = false;
                }
            }
        }
        return (ok);
    }

}

filterStart用来初始化filter信息,这里叫xxxStart只是更加形象而已,不用纠结。接着还要来修改wrapper,就不直接调用servlet了而是调用filter chain:

java 复制代码
package monitomcat.server.core;

// ...

public class StandardWrapperValve extends ValveBase {
    @Override
    public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
        // TODO Auto-generated method stub
        System.out.println("StandardWrapperValve invoke()");
        Servlet instance = ((StandardWrapper) getContainer()).getServlet();
        ApplicationFilterChain filterChain = createFilterChain(request, instance);
        if ((instance != null) && (filterChain != null)) {
            filterChain.doFilter((ServletRequest) request, (ServletResponse) response);
        }
        filterChain.release();
    }

    private ApplicationFilterChain createFilterChain(Request request, Servlet servlet) {
        System.out.println("createFilterChain()");
        if (servlet == null)
            return (null);
        ApplicationFilterChain filterChain = new ApplicationFilterChain();
        // ...
        return (filterChain);
    }

}

这样过滤器链就运转起来了,最终会调用到目标servlet。最后在启动类中注册用户过滤器信息就行了:

java 复制代码
package monitomcat.server.startup;

// ...

/**
 * 总的服务器对象,负责管理类工作的对象(类似于公司的管理层,负责管理,但不负责具体干活,但管理本身不也是一种职责嘛!所以也可以用单一职责来进行说明)
 */
public class Bootstrap {
    // ...

    public static void main(String[] args) {
        // ...

        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName("TestFilter");
//        filterDef.setFilterClass("test.TestFilter");
        filterDef.setFilterClass("monitomcat.server.test.TestFilter");
        servletContainer.addFilterDef(filterDef);
        FilterMap filterMap = new FilterMap();
        filterMap.setFilterName("TestFilter");
        filterMap.setURLPattern("/*");
        servletContainer.addFilterMap(filterMap);
        servletContainer.filterStart();

        // ...
    }
}

过滤器定义如下:

java 复制代码
package monitomcat.server.test;

// ...

public class TestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("test filter invoked ......");
        chain.doFilter(request, response);
    }
}

启动测试:

2:监听器

针对各种容器,servlet,session的生命周期提供监听功能。

定义容器的事件对象和监听器对象:

java 复制代码
package monitomcat.server;

import java.util.EventObject;

/**
 * 容器事件,加入jdk的时间体系
 */
public final class ContainerEvent extends EventObject {

}
java 复制代码
package monitomcat.server;

/**
 * 容器监听器,监听容器事件
 */
public interface ContainerListener {
    public void containerEvent(ContainerEvent event);
}

定义servlet的事件对象和监听器对象:

java 复制代码
package monitomcat.server;

// ...

/**
 * servlet事件
 */
public final class InstanceEvent extends EventObject {

}

定义session的事件对象和监听器对象:

java 复制代码
package monitomcat.server;

import java.util.EventObject;

/**
 * session事件
 */
public final class SessionEvent extends EventObject {

}
java 复制代码
package monitomcat.server;

public interface SessionListener {
    public void sessionEvent(SessionEvent event);
}

接着在StandardContext中增加监听器相关代码:

java 复制代码
package monitomcat.server.core;

// ...

public class StandardContext extends ContainerBase implements Context {
    // ...

    private ArrayList<ContainerListenerDef> listenerDefs = new ArrayList<>();
    private ArrayList<ContainerListener> listeners = new ArrayList<>();

    public void start() {
        // 触发一个容器启动事件
        fireContainerEvent("Container Started", this);
    }

    public void addContainerListener(ContainerListener listener) {
        // 添加一个新的容器监听器到监听器列表,并确保线程安全
        synchronized (listeners) {
            listeners.add(listener);
        }
    }

    public void removeContainerListener(ContainerListener listener) {
        // 移除指定的容器监听器,并确保线程安全
        synchronized (listeners) {
            listeners.remove(listener);
        }
    }

    public void fireContainerEvent(String type, Object data) {
        // 检查是否已经有监听器,如果没有则直接返回
        if (listeners.size() < 1)
            return;
        ContainerEvent event = new ContainerEvent(this, type, data);
        ContainerListener list[] = new ContainerListener[0];
        synchronized (listeners) {
            list = (ContainerListener[]) listeners.toArray(list);
        }
        // 遍历所有监听器并触发事件
        for (int i = 0; i < list.length; i++)
            ((ContainerListener) list[i]).containerEvent(event);
    }

    public void addListenerDef(ContainerListenerDef listenererDef) {
        synchronized (listenerDefs) {
            listenerDefs.add(listenererDef);
        }
    }

    public boolean listenerStart() {
        System.out.println("Listener init..........");
        boolean ok = true;
        synchronized (listeners) {
            listeners.clear();
            Iterator<ContainerListenerDef> defs = listenerDefs.iterator();
            while (defs.hasNext()) {
                ContainerListenerDef def = defs.next();
                ContainerListener listener = null;
                try {
                    // ...
                    addContainerListener(listener);
                } catch (Throwable t) {
                    t.printStackTrace();
                    ok = false;
                }
            }
        }
        return (ok);
    }

}

这里listenerStart方法同过滤器的filterStart方法。定义监听器定义对象:

java 复制代码
package monitomcat.server.core;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * listener 定义对象
 */
public final class ContainerListenerDef {
    private String description = null;

    private String listenerName = null;

    private Map<String, String> parameters = new ConcurrentHashMap<>();
} 

session中添加监听器相关代码:

java 复制代码
package com.minit.session;
public class StandardSession implements HttpSession, Session {
    private transient ArrayList<SessionListener> listeners = new ArrayList<>();
    public void addSessionListener(SessionListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }
    public void removeSessionListener(SessionListener listener) {
        synchronized (listeners) {
            listeners.remove(listener);
        }
    }
    public void fireSessionEvent(String type, Object data) {
        if (listeners.size() < 1)
            return;
        SessionEvent event = new SessionEvent(this, type, data);
        SessionListener list[] = new SessionListener[0];
        synchronized (listeners) {
            list = (SessionListener[]) listeners.toArray(list);
        }
        for (int i = 0; i < list.length; i++)
            ((SessionListener) list[i]).sessionEvent(event);
    }

    public void setId(String sessionId) {
        this.sessionid = sessionId;
        fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
    }
}

最后在启动类Bootstrap中增加监听器注册相关代码:

java 复制代码
package monitomcat.server.startup;

// ...

/**
 * 总的服务器对象,负责管理类工作的对象(类似于公司的管理层,负责管理,但不负责具体干活,但管理本身不也是一种职责嘛!所以也可以用单一职责来进行说明)
 */
public class Bootstrap {
    //    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes" + File.separator + "webroot";
    public static final String WEB_ROOT_SERVLET = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes" + File.separator + "monitomcat" + File.separator + "server";

    public static void main(String[] args) {
        // ...

        ContainerListenerDef listenerDef = new ContainerListenerDef();
        listenerDef.setListenerName("TestListener");
        listenerDef.setListenerClass("monitomcat.server.test.TestListener");
        servletContainer.addListenerDef(listenerDef);
        servletContainer.listenerStart();
        servletContainer.start();
        connector.start();
    }

}

监听器:

java 复制代码
public class TestListener implements ContainerListener {

    @Override
    public void containerEvent(ContainerEvent event) {
        System.out.println("test listener invoked...");
    }

}

该监听器是容器启动的监听器,所以在启动时会执行,如下:

写在后面

参考文章列表

手把手带你写一个 MiniTomcat

相关推荐
梅孔立43 分钟前
Pi-Agent 终极极简配置文档(Java+Vue+Python爬虫 4-5千文件项目)
java·vue.js·python
Query*1 小时前
Agent 开发之项目 AI Native 化:通过大模型与 RAG 赋予产品智能能力
java·人工智能·ai
孫治AllenSun1 小时前
【LangChain4J-03】Springboot 项目搭建框架
java·spring boot·后端
君顾11 小时前
AI新零售线上商城系统实战:架构设计与开发全流程指南
java·开发语言·零售
qq_485015211 小时前
MyBatis-Plus 3.x FieldStrategy 作用
java·数据库·mybatis
lhldsg1 小时前
课程排课系统实战指南:从数据库设计到算法调优全流程解析
java·数据库·算法·小程序
光电的一只菜鸡1 小时前
高通tuning中eis需要调什么
java·开发语言·前端
API快乐传递者2 小时前
1688 跨境电商 API 接口实战指南:从寻源到代采的全链路技术方案
java·前端·数据库
孔明click332 小时前
不想写代码,但想要集成一个登录页面?Sa-Token-Quick-Login 帮你实现!
java·sa-token·开源·springboot·登录·权限·权限认证