Tomcat源码解析(一):wrapper

Wrapper的作用

StandardWrapper同StandardContext一样,继承自ContainerBase,Wrapper表示了一个servlet定义,一个servlet类对应一个wrapper。wrapper的parent container必须是context,而wrapper不能有child container。
定义如下:

public final class StandardWrapper
extends ContainerBase
implements ServletConfig, Wrapper {

// ----------------------------------------------------------- Constructors

/**
 * Create a new StandardWrapper component with the default basic Valve.
 */
public StandardWrapper() {

    super();
    pipeline.setBasic(new StandardWrapperValve());

}

一个servletClass对应一个wrapper。 ContainerBase中用HashMap存储children,以child的名字为键。

/**
* The child Containers belonging to this Container, keyed by name.
*/

protected HashMap children = new HashMap();

public void addChild(Container child) {
    if (System.getSecurityManager() != null) {
        PrivilegedAction dp =
            new PrivilegedAddChild(child);
        AccessController.doPrivileged(dp);
    } else {
        addChildInternal(child);
    }
}

private void addChildInternal(Container child) {

    synchronized(children) {
        if (children.get(child.getName()) != null)
            throw new IllegalArgumentException("addChild:  Child name '" +
                                               child.getName() +
                                               "' is not unique");
        child.setParent((Container) this);  // May throw IAE
        //添加的同时,执行start()
        if (started && (child instanceof Lifecycle)) {
            try {
                ((Lifecycle) child).start();
            } catch (LifecycleException e) {
                log("ContainerBase.addChild: start: ", e);
                throw new IllegalStateException
                    ("ContainerBase.addChild: start: " + e);
            }
        }
        children.put(child.getName(), child);
        fireContainerEvent(ADD_CHILD_EVENT, child);
    }

}

主流程

请求进入后,context利用mapper匹配request url对应的wrapper,交给classloader来load wrapper,然后调用wrapper的invoke()方法。wrapper也是一个pipeline,拿到请求后,交给valve即StandardWrapperValve处理。
StandardWrapperValve.invoke()的主要逻辑如下。

  1. 创建servlet实例。一个wrapper对应一个servletclass。wrapper需要根据servlet是否实现SingleThreadModel进行不同的处理。
  2. response.sendAcknowledgement()告知request已收到。具体怎么做的呢??
  3. 创建一个filterchain过滤器链,filterchain对象中包含成员servlet,表示正在处理的servlet。创建filterchain,就是根据servletname匹配对应的filter,加入到链中。
  4. 调用filterchain.filter()。filter方法调用完过滤器后,调用servlet.service()方法,执行业务逻辑。
    // Allocate a servlet instance to process this request
    try {
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
    } catch (ServletException e) {
        log(sm.getString("standardWrapper.allocateException",
                         wrapper.getName()), e);
        throwable = e;
        exception(request, response, e);
        servlet = null;
    } catch (Throwable e) {
        log(sm.getString("standardWrapper.allocateException",
                         wrapper.getName()), e);
        throwable = e;
        exception(request, response, e);
        servlet = null;
    }

    // Acknowlege the request
    try {
        response.sendAcknowledgement();
    } catch (IOException e) {
        sreq.removeAttribute(Globals.JSP_FILE_ATTR);
        log(sm.getString("standardWrapper.acknowledgeException",
                         wrapper.getName()), e);
        throwable = e;
        exception(request, response, e);
    } catch (Throwable e) {
        log(sm.getString("standardWrapper.acknowledgeException",
                         wrapper.getName()), e);
        throwable = e;
        exception(request, response, e);
        servlet = null;
    }

    // Create the filter chain for this request
    ApplicationFilterChain filterChain =
        createFilterChain(request, servlet);

    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    try {
        String jspFile = wrapper.getJspFile();
        if (jspFile != null)
            sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
        else
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
        if ((servlet != null) && (filterChain != null)) {
            filterChain.doFilter(sreq, sres);
        }
        sreq.removeAttribute(Globals.JSP_FILE_ATTR);
    } 
    //省略异常处理和调用结束后清理现场的代码

wrapper创建servlet的代码如下。如果servlet没有实现SingleThreadModel,直接获取并返回servlet单例即可。

如果实现了SingleThreadModel,用一个实例池来控制servlet对象。也就是,实现了SingleThreadModel的servlet,在tomcat中并不是单例的。countAllocated表示该servletclass当前正在服务的servlet实例个数,servlet服务完成后,deallocate(Servlet servlet)方法将servlet放回池中,并将countAllocated减1。nInstances表示创建的servlet实例总个数,实例池中的实例个数+countAllocated=nInstances。所以countAllocated>=nInstances,表示当前实例池中没有闲着的实例,需要创建新的servlet实例了。


// If not SingleThreadedModel, return the same instance every time
    if (!singleThreadModel) {

        // Load and initialize our instance if necessary
        if (instance == null) {
            synchronized (this) {
                if (instance == null) {
                    try {
                        instance = loadServlet();
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new ServletException
                            (sm.getString("standardWrapper.allocate"), e);
                    }
                }
            }
        }

        if (!singleThreadModel) {
            if (debug >= 2)
                log("  Returning non-STM instance");
            countAllocated++;
            return (instance);
        }

    }

    synchronized (instancePool) {

        while (countAllocated >= nInstances) {
            // Allocate a new instance if possible, or else wait
            if (nInstances < maxInstances) {
                try {
                    instancePool.push(loadServlet());
                    nInstances++;
                } catch (ServletException e) {
                    throw e;
                } catch (Throwable e) {
                    throw new ServletException
                        (sm.getString("standardWrapper.allocate"), e);
                }
            } else {
                try {
                    instancePool.wait();
                } catch (InterruptedException e) {
                    ;
                }
            }
        }
        if (debug >= 2)
            log("  Returning allocated STM instance");
        countAllocated++;
        return (Servlet) instancePool.pop();

    }

servlet服务完成后,deallocate(Servlet servlet)负责将servlet实例放回到实例池中,同时countAllocated减一。

public void deallocate(Servlet servlet) throws ServletException {

    // If not SingleThreadModel, no action is required
    if (!singleThreadModel) {
        countAllocated--;
        return;
    }

    // Unlock and free this instance
    synchronized (instancePool) {
        countAllocated--;
        instancePool.push(servlet);
        instancePool.notify();
    }

}
servlet.init()

StandardWrapper还实现了ServletConfig接口。在load servle中,创建了servlet实例后,会调用servlet.init(ServletConfig)对servlet进行初始化,这时传入的不是StandardWrapper实例本身servlet.init(facade),而是传入它的包装类对象StandardWrapperFacade。facade定义如下:

private StandardWrapperFacade facade =
        new StandardWrapperFacade(this);

StandardWrapperFacade的作用是什么呢?StandardWrapper的public方法较多,大部分都不是servlet中应该用到的,如果传给servlet,可能会被误用。所以用StandardWrapperFacade包装起来,只提供必须的一些方法。

public final class StandardWrapperFacade
    implements ServletConfig {
/**
 * Create a new facede around a StandardWrapper.
 */
public StandardWrapperFacade(StandardWrapper config) {

    super();
    this.config = (ServletConfig) config;

}

// ----------------------------------------------------- Instance Variables
/**
 * Wrapped config.
 */
private ServletConfig config = null;


// -------------------------------------------------- ServletConfig Methods
public String getServletName() {
    return config.getServletName();
}

public ServletContext getServletContext() {
    ServletContext theContext = config.getServletContext();
    if ((theContext != null) &&
        (theContext instanceof ApplicationContext))
        theContext = ((ApplicationContext) theContext).getFacade();
    return (theContext);
}

filter

tomcat可以配置过滤器,在servlet调用前对请求进行预处理。我写了个简单的过滤器,试了一下过滤器功能。filter代码如下:

/**
 * Created by zhangguixian on 8/1/16
 */
public class HelloFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init HelloFilter");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter HelloFilter");
        //执行完之后,显式调用chain的doFilter方法,执行剩余的过滤器
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("destroy HelloFilter");
    }
}

配置filter的代码:

Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/myApp");
context.setDocBase("myApp");
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);

context.addChild(wrapper1);
context.addChild(wrapper2);

//加filter
FilterMap filterMap = new FilterMap();
filterMap.setFilterName("hello filter");
//    filterMap.setServletName("HelloFilter");
filterMap.setURLPattern("/Modern");

FilterDef filterDef = new FilterDef();
filterDef.setFilterName(filterMap.getFilterName());
filterDef.setFilterClass("HelloFilter");
context.addFilterDef(filterDef);

context.addFilterMap(filterMap);

总结

wrapper功能看起来简单,但结构同context一样,实现了container、pipeline、lifecycle,代码结构非常清晰,非常便于扩展。tomcat看了这么久,还是觉得设计的很美呢~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值