第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇

Spring源码阅读目录

第一部分——IOC篇

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇

第四部分——MVC篇

第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇

第五部分——Boot篇

第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇



前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


     书接上回,在上篇 第六十章 Spring之假如让你来写Boot——自动装配篇 中,A君 昨天已经完成了自动装配这一大功能,自动装配可以说是 Boot 的半壁江山,剩下的东西也不多了。。。

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大 要求 A君在一周内开发个简单的 IOC容器

    前情提要:A君 已经完成了自动装配这一大功能。。。

第五十版 内嵌Web容器

    “A君,搞定自动装配后,剩下的事情就不多咯。胜利的曙光就在眼前了。” 老大 笑着说到,看的出来,是真的开心,喜上眉梢

    “但愿 BOOT 之后别再有其他玩意就行了。” A君 都快哭了

    听到 A君 说这话,老大 笑声更大了。“哈哈哈,放心,起码相当长的一段时间不会有新东西,这次真的不忽悠你。”

    “行吧,那接下来要做什么?尽管安排吧!” A君 毅然决然,俨然一副慷慨就义的态势

    “今天要做的事情啊,很简单,你之前在做 MVC 时,就已经接触过了。它就是——内嵌Web容器。”

    “啊,就是通过main方法来启动 Tomcat,而不是用脚本?” A君 惊呼道

    老大 收起笑意,神色严肃了些,开口道:“是的,现在我们追求的是去配置,Web容器自然也不用之前那么麻烦的形式了,就拿 Tomcat 来说,改个端口还得去对应目录下,找到server.xml才行,一些都不符合我们现在的理念。”

    “那应该怎么做呢?” A君 疑惑道

    “你现在做完自动装配,结合之前你领悟到的编程式,这一切就可以开展了。无论是 Tomcat 还是 Jetty,现在都支持main方法启动,也就是说提供了写代码的形式来启动。我们要做的事情就很简单了,把用户的配置收集上来,设置进去就行了。如果没有配置,就遵循 约定大于配置 的原则,像 Tomcat 默认的端口是8080!”

    “哦,这部分内容我能理解,但是自动装配又有什么用呢?” A君 提出了新的疑问

    “简单,你想啊,Web容器只适用于Web项目,你得通过添加装配去判断要不要装载Web容器,而且就算是Web项目,Web容器也有很多种,你也得根据条件进行加载。” 老大 回答道

    “这样啊。” A君 恍然

    “还有其他问题吗?没有就准备开始最后的冲刺了。” 老大 又开始下逐客令了

    “OK!” A君 辞别 老大,走出办公室后,开始琢磨如何实现的事情。。。

Web容器配置

    回到工位上后,A君 并没有纠结多久。要怎么做,老大 已经给出具体的思路了,从前在 Tomcat 中,或者是在web.xml中的配置都用注解或者是项目配置文件来替换。那第一步要做的事情就简单了,无论这些配置从哪里来,都要有个对象来描述吧。正所谓:万物皆对象嘛。嘿嘿。A君 一阵自鸣得意。那就规定以server开头的配置就是Web容器相关的配置

在这里插入图片描述

配置文件规定好了,就可以开始写代码了。新增ServerProperties类。代码如下:

/**
 * 服务器配置类
 */
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    /**
     * http端口
     */
    private Integer port;
    /**
     * 服务器应该绑定到的网络地址
     */
    private InetAddress address;
    /**
     * 用于服务器响应头的值(如果为空,则不发送头)
     */
    private String serverHeader;
    /**
     * HTTP消息头的最大大小
     */
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);
   /**
     * Servlet服务器属性
     */
    public static class Servlet {
        /**
         * 应用程序的上下文路径
         */
        private String contextPath;
        /**
         * 显示应用程序的名称
         */
        private String applicationDisplayName = "application";
   }
	//省略其他代码。。。
}
web容器

    A君 已经意识到Web容器的创建最适合使用的设计模式就是策略模式+工厂模式,策略模式用以实现不同的Web容器,工厂模式用于配置并创建Web容器。只要做好这两步就可以根据用户的配置优雅地创建出相应的Web容器了。策略+工厂,那自然先有实现,工厂才有东西创建。为此,A君 定义WebServer接口,代码如下:

/**
 * Web容器接口
 */
public interface WebServer {
    /**
     * 启动Web容器
     */
    void start() throws WebServerException;

    /**
     * 暂停Web容器
     */
    void stop() throws WebServerException;

    /**
     * 获取端口
     */
    int getPort();

    /**
     * 关闭回调
     */
    default void shutDownGracefully(GracefulShutdownCallback callback) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
    }
}

Web容器嘛,无非就是启动、暂停这些功能。接口定义好了,接下来就是具体实现了,市面上Web容器有点多,这里就以 Tomcat 为例。要注意的是,在配置没有加载之前,有些组件得延迟加载,不然就无法进行正确的配置了。A君 新增TomcatWebServer类,代码如下:

public class TomcatWebServer implements WebServer {

    private static final AtomicInteger containerCounter = new AtomicInteger(-1);
    //锁
    private final Object monitor = new Object();
    //保存服务对应的连接器
    private final Map<Service, Connector[]> serviceConnectors = new HashMap<>();
    //Tomcat对象
    private final Tomcat tomcat;
    //是否自动启动
    private final boolean autoStart;
    //停止时候回调
    private final GracefulShutdown gracefulShutdown;
    //是否已启动
    private volatile boolean started;

	public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
        initialize();
    }

    private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();
                //找到当前上下文
                Context context = findContext();
                //添加生命周期
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                        /**
                         * 移除对应的连接器并添加到serviceConnectors中
                         * 方便设置用户的配置
                         */
                        removeServiceConnectors();
                    }
                });
                //启动tomcat
                this.tomcat.start();
                //获取启动时候的异常
                rethrowDeferredStartupExceptions();
                try {
                    //绑定了类加载器
                    ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
                } catch (NamingException ex) {
                    // Naming is not enabled. Continue
                }
                //阻塞主线程,防止退出
                startDaemonAwaitThread();
            } catch (Exception ex) {
                stopSilently();
                destroySilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }
    @Override
    public void start() throws WebServerException {
        synchronized (this.monitor) {
            if (this.started) {
                return;
            }
            try {
                //重新添加连接器
                addPreviouslyRemovedConnectors();
                Connector connector = this.tomcat.getConnector();
                if (connector != null && this.autoStart) {
                    //延迟加载Host下容器,为了配置信息生效
                    performDeferredLoadOnStartup();
                }
                checkThatConnectorsHaveStarted();
                this.started = true;
                logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
                        + getContextPath() + "'");
            } finally {
                Context context = findContext();
                //解绑类加载器
                ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            }
        }
    }
	//省略其他代码。。。
}
web容器工厂
Web容器工厂

    包装配置信息的类已经有了,接着就要把这些配置信息配置到Web容器中去,不过呢。需要注意的事,Web容器也有一大堆呢,Tomcat 只是其中的一个,光是 A君 听说过的就有不少了,像:JettyJBossWebLogicWebSphere。Web容器虽然这么多,但是都遵循 Servlet规范,这也就意味着可以定义接口,然后有不同的实现,典型的策略模式。策略模式一般都是搭配工厂模式使用。这点 A君 已经领悟到了。至于接口怎么定义,也有说道,像是某一种类型的接口,一般都要先定义一个标记接口。这点 A君 已经摸清套路了。新增WebServerFactory接口,代码如下,诶,不对,没有代码,就个空接口。如下:

/**
 * web容器工厂标记接口
 */
public interface WebServerFactory {
}

标记接口定义好了,接着就可以定义Web容器配置接口了,用来设置Web容器配置信息的接口。没啥新鲜玩意,一堆set方法罢了。A君 新增ConfigurableWebServerFactory接口,代码如下:

/**
 * Web容器配置接口
 */
public interface ConfigurableWebServerFactory extends WebServerFactory, ErrorPageRegistry {

    /**
     * 设置端口
     */
    void setPort(int port);

    void setAddress(InetAddress address);

    //省略其它代码。。。
}

看着这一堆的set方法,A君 想想每个实现类要重复写一大堆得set方法就头皮发麻,按照 约定大于配置 规则的话,没有特殊说明就用约定俗成的配置,这更需要一个抽象类了。于是,A君 新增AbstractConfigurableWebServerFactory抽象类,代码如下:

public abstract class AbstractConfigurableWebServerFactory implements ConfigurableWebServerFactory {
    /**
     * 默认端口
     */
    private int port = 8080;
    /**
     * 默认地址
     */
	private InetAddress address;
	
    @Override
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public void setAddress(InetAddress address) {
        this.address = address;
    }
    //省略其它代码。。。
}

一堆get/set方法实在没啥好说的,下一个。。。

Servlet容器工厂

    配置Web容器的接口和抽象类都已经弄好了,接下来就轮到Servlet容器了,又是Web容器,又是Servlet容器,整的 A君 有点头晕,简单的说:Web容器是个特殊的Servlet容器,它既可以处理 Http请求,又可以解析Servlet。Servlet容器是Web容器的一部分功能。明白了两者的关系,就可以设计Servlet容器接口了,不过这回可不能再是标记接口,得有点东西了。A君 新增ServletWebServerFactory接口,代码如下:

@FunctionalInterface
public interface ServletWebServerFactory extends WebServerFactory {

    /**
     * 创建一个Web容器
     */
    WebServer getWebServer(ServletContextInitializer... initializers);

}

接下来就是配置Servlet容器的信息,前文已经说到了Web容器包含Servlet容器,所以Servlet容器配置接口需要集成Web容器的配置接口。新增ConfigurableServletWebServerFactory接口,代码如下:

public interface ConfigurableServletWebServerFactory
        extends ConfigurableWebServerFactory, ServletWebServerFactory, WebListenerRegistry {

    /**
     * 设置上下文
     *
     * @param contextPath
     */
    void setContextPath(String contextPath);

    /**
     * 设置部署在web服务器中的应用程序的显示名称
     */
    void setDisplayName(String displayName);

    /**
     * 设置Session
     */
    void setSession(Session session);

	//省略其它代码。。。
}

这还是一堆set方法,那就和Web容器的处理方式一样了。新增AbstractServletWebServerFactory抽象类,代码如下:

public abstract class AbstractServletWebServerFactory extends AbstractConfigurableWebServerFactory
        implements ConfigurableServletWebServerFactory {

    protected final Log logger = LogFactory.getLog(getClass());
    private final DocumentRoot documentRoot = new DocumentRoot(this.logger);
    private final StaticResourceJars staticResourceJars = new StaticResourceJars();
    private final Set<String> webListenerClassNames = new HashSet<>();
    private String contextPath = "";
    private String displayName;
	@Override
    public void setContextPath(String contextPath) {
        checkContextPath(contextPath);
        this.contextPath = contextPath;
    }
    //省略其它代码。。。
}

还是一堆get/set方法,过。。。抽象类都定义好了,接着就是真正的实现类了,Web容器那么多,没必要一个个看过去,这里 A君 就以 Tomcat 为例。说是这么说,这玩意还是需要对 Tomcat 有点了解才行,不然还真不好写。A君 先去看了熟悉的server.xml配置文件:

<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
    <!-- 用于预防 JRE 内存泄漏的监听器 -->
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
    <!-- 定义服务 -->
    <Service name="Catalina">
        <!-- HTTP 连接器 -->
        <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />

        <!-- 定义引擎 -->
        <Engine name="Catalina" defaultHost="localhost">
            <!-- 定义默认主机 -->
            <Host name="localhost" appBase="webapps"
                  unpackWARs="true" autoDeploy="true">
                <!-- 默认上下文配置(可选) -->
                <!--
                <Context path="" docBase="ROOT" />
                -->
            </Host>
        </Engine>
    </Service>
</Server>

Tomcat 大致包含ServerListener ServiceConnectorEngineHost。他们就像上面xml配置的那样,有着层级结构。由于 Tomcat 是将对应的xml转成对象,所以xml的结构就是类的结构。好了,知道这些就行了,毕竟这里不是 Tomcat 的主场,了解即可。A君 新增TomcatServletWebServerFactory类。代码如下:

/**
 * Tomcat创建工厂
 */
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    //基础路径
    private File baseDirectory;
    //engine管道处理
    private List<Valve> engineValves = new ArrayList<>();
    //context管道处理
    private List<Valve> contextValves = new ArrayList<>();
    //context生命周期事件
    private List<LifecycleListener> contextLifecycleListeners = new ArrayList<>();
    //server生命周期事件
    private List<LifecycleListener> serverLifecycleListeners = getDefaultServerLifecycleListeners();
    //context自定义配置
    private Set<TomcatContextCustomizer> tomcatContextCustomizers = new LinkedHashSet<>();
    //Connector自定义配置
    private Set<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new LinkedHashSet<>();
    //协议自定义配置
    private Set<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new LinkedHashSet<>();
    private ResourceLoader resourceLoader;
    private String protocol = DEFAULT_PROTOCOL;
    private Charset uriEncoding = DEFAULT_CHARSET;

@Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        //配置基础路径
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        //添加server监听
        for (LifecycleListener listener : this.serverLifecycleListeners) {
            tomcat.getServer().addLifecycleListener(listener);
        }
        //配置连接器
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        //添加连接器
        tomcat.getService().addConnector(connector);
        //配置连接器信息:协议、编码等
        customizeConnector(connector);
        tomcat.setConnector(connector);
        //取消自动部署
        tomcat.getHost().setAutoDeploy(false);
        //配置引擎:管道信息,类似于过滤器作用
        configureEngine(tomcat.getEngine());
        //添加额外的连接器
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }
    //省略其他代码。。。
}

参数配置

    现在工厂有了,Web容器也有了。剩下的就是如何把配置设置到Web容器中。配置属性可以分成两种:一是通用配置,毕竟都是遵循 Servlet规范 嘛。二是各个Web容器特殊配置,这也好说,各个厂商都有自己的设计思路,肯定不会完全一样。还是老规矩,先定义接口。A君 新增WebServerFactoryCustomizer接口,代码如下:

/**
 * Web容器参数配置接口
 *
 * @param <T>
 */
@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
    /**
     * 配置属性
     *
     * @param factory
     */
    void customize(T factory);
}

接口有了,就轮到实现了,实现分为两部分,A君 打算先从通用配置入手。为什么?没有为什么?两个同样简单,调用一堆set方法就完事了。新增ServletWebServerFactoryCustomizer类,代码如下:

/**
 * 通用配置
 */
public class ServletWebServerFactoryCustomizer
        implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
    /**
     * 配置信息
     */
    private final ServerProperties serverProperties;
    /**
     * 监听类
     */
    private final List<WebListenerRegistrar> webListenerRegistrars;

    private final List<CookieSameSiteSupplier> cookieSameSiteSuppliers;

    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        map.from(this.serverProperties::getPort).to(factory::setPort);
        map.from(this.serverProperties::getAddress).to(factory::setAddress);
        map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
        map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
        map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
        map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
        map.from(this.serverProperties::getSsl).to(factory::setSsl);
        map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
        map.from(this.serverProperties::getCompression).to(factory::setCompression);
        map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
        map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
        map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
        map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
        for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
            registrar.register(factory);
        }
        if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
            factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
        }
    }
	//省略其他代码。。。
}

在定义一个 Tomcat 专属的配置就齐活了。A君 新增TomcatServletWebServerFactoryCustomizer类,代码如下:

/**
 * Tomcat专属配置
 */
public class TomcatServletWebServerFactoryCustomizer
        implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {

    private final ServerProperties serverProperties;

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
        //配置TLD
        if (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {
            factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());
        }
        //配置重定向
        if (tomcatProperties.getRedirectContextRoot() != null) {
            customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());
        }
        //配置相对路径重定向
        customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());
        //配置Mbean
        factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
    }

	//省略其他代码。。。
}


自动配置

    现在创建Web容器的前置工作都做好了,剩下的还要思考一件事:WebServerFactoryCustomizer相关的类要什么时候创建?要知道Web容器工厂需要等到配置信息设置进去才能进行创建,不然创建出来的Web容器就不对了

注册BeanPostProcessor

    要想在创建工厂对象之前创建好WebServerFactoryCustomizer相关对象,那只能通过BeanPostProcessor了。先定义WebServerFactoryCustomizerBeanPostProcessor类,代码如下:

/**
 * Web容器配置
 */
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {

    private ListableBeanFactory beanFactory;

    private List<WebServerFactoryCustomizer<?>> customizers;
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //bean是WebServerFactory才进行处理
        if (bean instanceof WebServerFactory) {
            postProcessBeforeInitialization((WebServerFactory) bean);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @SuppressWarnings("unchecked")
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        /**
         * 调用所有的WebServerFactoryCustomizer对象进行配置
         */
        LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
                .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
                .invoke((customizer) -> customizer.customize(webServerFactory));
    }

    //省略其他代码。。。

}

创建时机是解决了,又引来了新的问题:要知道现在定义的Bean都是在配置类里边,如何注册BeanPostProcessor。嘿嘿,这时候就得轮到ImportBeanDefinitionRegistrar接口出场了,当初定义这个接口的时候 A君 还有点懵逼,现在有点明白了。现在疑虑都扫除了,可以开始撸代码了。新增BeanPostProcessorsRegistrar类,代码如下:

/**
     * BeanPostProcessors注册器
     */
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                            BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            //注册WebServerFactoryCustomizerBeanPostProcessor
            registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class,
                    WebServerFactoryCustomizerBeanPostProcessor::new);
            //注册ErrorPageRegistrarBeanPostProcessor
            registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
        }
		//省略其他代码。。。
    }
配置Web容器工厂

    现在就可以配置Web容器工厂了,和平时定义配置类一样的。A君 新增ServletWebServerFactoryConfiguration类,代码如下:

@Configuration(proxyBeanMethods = false)
public class ServletWebServerFactoryConfiguration {

    /**
     * 存在Servlet、Tomcat且不存在ServletWebServerFactory才进行创建
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {

        @Bean
        TomcatServletWebServerFactory tomcatServletWebServerFactory(
                ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
                ObjectProvider<TomcatContextCustomizer> contextCustomizers,
                ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {

            TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
            //添加所有Connector配置
            factory.getTomcatConnectorCustomizers()
                    .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
            //添加所有Context配置
            factory.getTomcatContextCustomizers()
                    .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
            //添加所有协议配置
            factory.getTomcatProtocolHandlerCustomizers()
                    .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
            return factory;
        }

    }
}
自动配置Web容器工厂

    单单配置类远远不用,A君 要的是自动配置。自动配置时候需要引入所需的配置类就行了,还有一个就是需要是Web项目并且引入 Tomcat 相关依赖的情况下才能创建对应的Bean对象。A君 新增ServletWebServerFactoryAutoConfiguration类,代码如下:

/**
 * Web容器工厂自动装配
 */
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
})
public class ServletWebServerFactoryAutoConfiguration {

	/**
     * 通用配置
     *
     * @param serverProperties
     * @param webListenerRegistrars
     * @param cookieSameSiteSuppliers
     * @return
     */
    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
                                                                               ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
                                                                               ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
        return new ServletWebServerFactoryCustomizer(serverProperties,
                webListenerRegistrars.orderedStream().collect(Collectors.toList()),
                cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
    }

    /**
     * 只有是Tomcat的时候才生效
     *
     * @param serverProperties
     * @return
     */
    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }
	//省略其它代码。。。
}

自动装配的类有了,剩下的别忘了 SPI 机制。在META-INF/spring文件夹下新增com.hqd.ch03.v50.boot.autoconfigure.AutoConfiguration.imports文件。内容如下:

com.hqd.ch03.v50.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
测试

    现在内嵌Web容器代码已经全部写完了,剩下的只有测试了。只需把几个类注册进去就行了。测试代码如下:

@Test
    public void v50() throws NoSuchMethodException, IOException, InterruptedException {
        System.out.println("############# 第五十版: 内嵌Web容器篇 #############");
        /**
         * 创建Web上下文
         */
        ServletWebServerApplicationContext applicationContext = new ServletWebServerApplicationContext();

        /**
         * 注册ApplicationContext
         */
        com.hqd.ch03.v50.beans.factory.annotation.AnnotatedGenericBeanDefinition beanDefinition =
                new com.hqd.ch03.v50.beans.factory.annotation.AnnotatedGenericBeanDefinition(
                        com.hqd.ch03.test.boot.v50.MainApplication.class);
        beanDefinition.setBeanClass(com.hqd.ch03.test.boot.v50.MainApplication.class);
        beanDefinition.setLazyInit(true);
        applicationContext.registerBeanDefinition("mainApplication", beanDefinition);
        /**
         * 注册asm读取器
         */
        com.hqd.ch03.v50.factory.support.GenericBeanDefinition mrfBD =
                new com.hqd.ch03.v50.factory.support.GenericBeanDefinition(
                        CachingMetadataReaderFactory.class);
        mrfBD.setBeanClass(CachingMetadataReaderFactory.class);
        applicationContext.registerBeanDefinition(SharedMetadataReaderFactoryContextInitializer.BEAN_NAME, mrfBD);
        /**
         *注册自动配置
         */
        applicationContext.getBeanFactoryPostProcessors().add(new com.hqd.ch03.v50.context.annotation.ConfigurationClassPostProcessor());
        applicationContext.refresh();
     
        //防止退出
        Object object = new Object();
        synchronized (object) {
            object.wait();
        }
    }

测试结果如下:

在这里插入图片描述

用浏览器访问测试下:

在这里插入图片描述

OK,现在也把Web容器内嵌到容器中去了,现在距离成功真的只剩一步之遥了。。A君 喜不自禁,但是该下班还是得下班的。。

在这里插入图片描述


总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

穷儒公羊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值