Tomcat源码分析-启动分析(四) webapp

1.3、调用 ServletContainerInitializer

在初始化 Servlet、Listener 之前,便会先调用 ServletContainerInitializer,进行额外的初始化处理。注意:ServletContainerInitializer 需要的是 Class 对象,而不是具体的实例对象,这个时候 servlet 相关的 Listener 并没有被实例化,因此不会产生矛盾

// 指定 ServletContext 的相关参数
mergeParameters();

// 调用 ServletContainerInitializer#onStartup()
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;
    }

1.4、启动 Servlet 相关的 Listener

WebConfig 加载 Listener 时,只是保存了 className,实例化动作由 StandardContext 触发。前面在介绍 StandardContext 的时候提到了 InstanceManager,创建实例的逻辑由 InstanceManager 完成。

Listener 监听器分为 Event、Lifecycle 监听器,WebConfig 在加载 Listener 的时候是不会区分的,实例化之后才会分开存储。在完成 Listener 实例化之后,tomcat 容器便启动 OK 了。此时,tomcat 需要通知应用程序定义的 ServletContextListener,方便应用程序完成自己的初始化逻辑,它会遍历 ServletContextListener 实例,并调用其 contextInitialized 方法,比如 spring 的 ContextLoaderListener

有以下 Event 监听器,主要是针对事件通知:
- ServletContextAttributeListener
- ServletRequestAttributeListener
- ServletRequestListener
- HttpSessionIdListener
- HttpSessionAttributeListener

有以下两种 Lifecycle 监听器,主要是针对 ServletContext、HttpSession 的生命周期管理,比如创建、销毁等
- ServletContextListener
- HttpSessionListener

1.5、初始化 Filter

ContextConfig 在处理 CONFIGURE_START_EVENT 事件的时候,会使用 FilterDef 保存 Filter 信息。而 StandardContext 会把 FilterDef 转化成 ApplicationFilterConfig,在 ApplicationFilterConfig 构造方法中完成 Filter 的实例化,并且调用 Filter 接口的 init 方法,完成 Filter 的初始化。ApplicationFilterConfig 是 javax.servlet.FilterConfig
接口的实现类。

public boolean filterStart() {
    boolean ok = true;
    synchronized (filterConfigs) {
        filterConfigs.clear();
        for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
            String name = entry.getKey();
            try {
                // 在构造方法中完成 Filter 的实例化,并且调用 Filter 接口的 init 方法,完成 Filter 的初始化
                ApplicationFilterConfig filterConfig =
                        new ApplicationFilterConfig(this, entry.getValue());
                filterConfigs.put(name, filterConfig);
            } catch (Throwable t) {
                // 省略 logger 处理
                ok = false;
            }
        }
    }
    return ok;

1.6、处理 Wrapper 容器

Servlet 对应 tomcat 的 Wrapper 容器,完成 Filter 初始化之后便会对 Wrapper 容器进行处理,如果 Servlet 的 loadOnStartup >= 0,便会在这一阶段完成 Servlet 的加载,并且值越小越先被加载,否则在接受到请求的时候才会加载 Servlet。

加载过程,主要是完成 Servlet 的实例化,并且调用 Servlet 接口的 init 方法,具体的逻辑将在下文进行详细分析

// StandardWrapper 实例化并且启动 Servlet,由于 Servlet 存在 loadOnStartup 属性
// 因此使用了 TreeMap,根据 loadOnStartup 值 对 Wrapper 容器进行排序,然后依次启动 Servlet
if (ok) {
    if (!loadOnStartup(findChildren())){
        log.error(sm.getString("standardContext.servletFail"));
        ok = false;
    }

loadOnStartup 方法使用 TreeMap 对 Wrapper 进行排序,loadOnStartup 值越小越靠前,值相同的 Wrapper 放在同一个 List 中,代码如下所示:

public boolean loadOnStartup(Container children[]) {

    // 使用 TreeMap 对 Wrapper 进行排序,loadOnStartup 值越小越靠前,值相同的 Wrapper 放在同一个 List 中
    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
    for (int i = 0; i < children.length; i++) {
        Wrapper wrapper = (Wrapper) children[i];
        int loadOnStartup = wrapper.getLoadOnStartup();
        if (loadOnStartup < 0)
            continue;
        Integer key = Integer.valueOf(loadOnStartup);
        ArrayList<Wrapper> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        list.add(wrapper);
    }

    // 根据 loadOnStartup 值有序加载 Wrapper 容器
    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } catch (ServletException e) {
                if(getComputedFailCtxIfServletStartFails()) {
                    return false;
                }
            }
        }
    }
    return true;

2、Wrapper 容器

Wrapper 容器是 tomcat 所有容器中最底层子容器,它没有子容器,并且父容器是 Context,对这一块不了解的童鞋请移步前面的博客《tomcat框架设计》。默认实现是 StandardWrapper,我们先来看看类定义,它继承至 ContainBase,实现了 servlet 的 ServletConfig 接口,以及 tomcat 的 Wrapper 接口,说明 StandardWrapper 不仅仅是一个 Wrapper 容器实现,还是 ServletConfig 实现,部分代码如下所示:

public class StandardWrapper extends ContainerBase
    implements ServletConfig, Wrapper, NotificationEmitter {

    // Wrapper 的门面模式,调用 Servlet 的 init 方法传入的是该对象
    protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);    
    protected volatile Servlet instance = null; // Servlet 实例对象
    protected int loadOnStartup = -1;   // 默认值为 -1,不立即启动 Servlet
    protected String servletClass = null;

    public StandardWrapper() {
        super();
        swValve=new StandardWrapperValve();
        pipeline.setBasic(swValve);
        broadcaster = new NotificationBroadcasterSupport();
    }

由前面对 Context 的分析可知,StandardContext 在启动的时候会发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,通过解析 web.xml 或者读取注解信息获取 Wrapper 子容器,并且会添加到 Context 容器中。由于 StandardContext 继承至 ContainerBase,在调用 addChild 的时候默认会启动 child 容器(即 Wrapper),我们来看看 StandardWrapper 的启动逻辑

2.1、启动 Wrapper 容器

StandardWrapper 没有子容器,启动逻辑相对比较简单清晰,它重写了 startInternal 方法,主要是完成了 jmx 的事件通知,先后向 jmx 发出 starting、running 事件,代码如下所示:

protected synchronized void startInternal() throws LifecycleException {
    // 发出 j2ee.state.starting 事件通知
    if (this.getObjectName() != null) {
        Notification notification = 
            new Notification("j2ee.state.starting", this.getObjectName(), sequenceNumber++);
        broadcaster.sendNotification(notification);
    }

    // ConainerBase 的启动逻辑
    super.startInternal();
    setAvailable(0L);

    // 发出 j2ee.state.running 事件通知
    if (this.getObjectName() != null) {
        Notification notification =
            new Notification("j2ee.state.running", this.getObjectName(), sequenceNumber++);
        broadcaster.sendNotification(notification);
    }

2.2 加载 Wrapper

由前面对 Context 容器的分析可知,Context 完成 Filter 初始化之后,如果 loadOnStartup >= 0 便会调用 load 方法加载 Wrapper 容器。StandardWrapper 使用 InstanceManager 实例化 Servlet,并且调用 Servlet 的 init 方法进行初始化,传入的 ServletConfig 是 StandardWrapperFacade 对象

public synchronized void load() throws ServletException {

    // 实例化 Servlet,并且调用 init 方法完成初始化
    instance = loadServlet();

    if (!instanceInitialized) {
        initServlet(instance);
    }

    if (isJspServlet) {
        // 处理 jsp Servlet
    }

总结

tomcat 实现了 javax.servlet.ServletContext
接口,在 Context 启动的时候会实例化该对象。由 Context 容器通过 web.xml 或者 扫描 class 字节码读取 servlet3.0 的注解配置,从而加载 webapp 定义的 Listener、Servlet、Filter 等 servlet 组件,但是并不会立即实例化对象。全部加载完毕之后,依次对 Listener、Filter、Servlet 进行实例化、并且调用其初始化方法,比如
ServletContextListener#contextInitialized()Flter#init()
等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java面试大全

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值