前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring MVC 原理探秘 - 容器的创建过程

Spring MVC 原理探秘 - 容器的创建过程

作者头像
田小波
发布于 2018-07-05 02:56:00
发布于 2018-07-05 02:56:00
73201
代码可运行
举报
运行总次数:1
代码可运行

1.简介

在上一篇文章中,我向大家介绍了 Spring MVC 是如何处理 HTTP 请求的。Spring MVC 可对外提供服务时,说明其已经处于了就绪状态。再次之前,Spring MVC 需要进行一系列的初始化操作。正所谓兵马未动,粮草先行。这些操作包括创建容器,加载 DispatcherServlet 中用到的各种组件等。本篇文章就来和大家讨论一下这些初始化操作中的容器创建操作,容器的创建是其他一些初始化过程的基础。那其他的就不多说了,我们直入主题吧。

2.容器的创建过程

一般情况下,我们会在一个 Web 应用中配置两个容器。一个容器用于加载 Web 层的类,比如我们的接口 Controller、HandlerMapping、ViewResolver 等。在本文中,我们把这个容器叫做 web 容器。另一个容器用于加载业务逻辑相关的类,比如 service、dao 层的一些类。在本文中,我们把这个容器叫做业务容器。在容器初始化的过程中,业务容器会先于 web 容器进行初始化。web 容器初始化时,会将业务容器作为父容器。这样做的原因是,web 容器中的一些 bean 会依赖于业务容器中的 bean。比如我们的 controller 层接口通常会依赖 service 层的业务逻辑类。下面举个例子进行说明:

如上,我们将 dao 层的类配置在 application-dao.xml 文件中,将 service 层的类配置在 application-service.xml 文件中。然后我们将这两个配置文件通过 标签导入到 application.xml 文件中。此时,我们可以让业务容器去加载 application.xml 配置文件即可。另一方面,我们将 Web 相关的配置放在 application-web.xml 文件中,并将该文件交给 Web 容器去加载。

这里我们把配置文件进行分层,结构上看起来清晰了很多,也便于维护。这个其实和代码分层是一个道理,如果我们把所有的代码都放在同一个包下,那看起来会多难受啊。同理,我们用业务容器和 Web 容器去加载不同的类也是一种分层的体现吧。当然,如果应用比较简单,仅用 Web 容器去加载所有的类也不是不可以。

2.1 业务容器的创建过程

前面说了一些背景知识作为铺垫,那下面我们开始分析容器的创建过程吧。按照创建顺序,我们先来分析业务容器的创建过程。业务容器的创建入口是 ContextLoaderListener 的 contextInitialized 方法。顾名思义,ContextLoaderListener 是用来监听 ServletContext 加载事件的。当 ServletContext 被加载后,监听器的 contextInitialized 方法就会被 Servlet 容器调用。ContextLoaderListener Spring 框架提供的,它的配置方法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<web-app>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application.xml</param-value>
    </context-param>
    
    <!-- 省略其他配置 -->
</web-app>

如上,ContextLoaderListener 可通过 ServletContext 获取到 contextConfigLocation 配置。这样,业务容器就可以加载 application.xml 配置文件了。那下面我们来分析一下 ContextLoaderListener 的源码吧。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    // 省略部分代码

    @Override
    public void contextInitialized(ServletContextEvent event) {
        // 初始化 WebApplicationContext
        initWebApplicationContext(event.getServletContext());
    }
}

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    /*
     * 如果 ServletContext 中 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 属性值
     * 不为空时,表明有其他监听器设置了这个属性。Spring 认为不能替换掉别的监听器设置
     * 的属性值,所以这里抛出异常。
     */
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
                "Cannot initialize context because there is already a root application context present - " +
                "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {...}
    long startTime = System.currentTimeMillis();

    try {
        if (this.context == null) {
            // 创建 WebApplicationContext
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    /*
                     * 加载父 ApplicationContext,一般情况下,业务容器不会有父容器,
                     * 除非进行配置
                     */ 
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                // 配置并刷新 WebApplicationContext
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }

        // 设置 ApplicationContext 到 servletContext 中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }

        if (logger.isDebugEnabled()) {...}
        if (logger.isInfoEnabled()) {...}

        return this.context;
    }
    catch (RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
    catch (Error err) {
        logger.error("Context initialization failed", err);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
        throw err;
    }
}

如上,我们看一下上面的创建过程。首先 Spring 会检测 ServletContext 中 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 属性有没有被设置,若被设置过,则抛出异常。若未设置,则调用 createWebApplicationContext 方法创建容器。创建好后,再调用 configureAndRefreshWebApplicationContext 方法配置并刷新容器。最后,调用 setAttribute 方法将容器设置到 ServletContext 中。经过以上几步,整个创建流程就结束了。流程并不复杂,可简单总结为创建容器 → 配置并刷新容器 → 设置容器到 ServletContext 中。这三步流程中,最后一步就不进行分析,接下来分析一下第一步和第二步流程对应的源码。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    // 判断创建什么类型的容器,默认类型为 XmlWebApplicationContext
    Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    // 通过反射创建容器
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    /*
     * 读取用户自定义配置,比如:
     * <context-param>
     *     <param-name>contextClass</param-name>
     *     <param-value>XXXConfigWebApplicationContext</param-value>
     * </context-param>
     */
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load custom context class [" + contextClassName + "]", ex);
        }
    }
    else {
        /*
         * 若无自定义配置,则获取默认的容器类型,默认类型为 XmlWebApplicationContext。
         * defaultStrategies 读取的配置文件为 ContextLoader.properties,
         * 该配置文件内容如下:
         * org.springframework.web.context.WebApplicationContext =
         *     org.springframework.web.context.support.XmlWebApplicationContext
         */
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException(
                    "Failed to load default context class [" + contextClassName + "]", ex);
        }
    }
}

简单说一下 createWebApplicationContext 方法的流程,该方法首先会调用 determineContextClass 判断创建什么类型的容器,默认为 XmlWebApplicationContext。然后调用 instantiateClass 方法通过反射的方式创建容器实例。instantiateClass 方法就不跟进去分析了,大家可以自己去看看,比较简单。

继续往下分析,接下来分析一下 configureAndRefreshWebApplicationContext 方法的源码。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // 从 ServletContext 中获取用户配置的 contextId 属性
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            // 设置容器 id
            wac.setId(idParam);
        }
        else {
            // 用户未配置 contextId,则设置一个默认的容器 id
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

    wac.setServletContext(sc);
    // 获取 contextConfigLocation 配置
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }
    
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);

    // 刷新容器
    wac.refresh();
}

上面的源码不是很长,逻辑不是很复杂。下面简单总结 configureAndRefreshWebApplicationContext 方法主要做了事情,如下:

  1. 设置容器 id
  2. 获取 contextConfigLocation 配置,并设置到容器中
  3. 刷新容器

到此,关于业务容器的创建过程就分析完了,下面我们继续分析 Web 容器的创建过程。

2.2 Web 容器的创建过程

前面说了业务容器的创建过程,业务容器是通过 ContextLoaderListener。那 Web 容器是通过什么创建的呢?答案是通过 DispatcherServlet。我在上一篇文章介绍 HttpServletBean 抽象类时,说过该类覆写了父类 HttpServlet 中的 init 方法。这个方法就是创建 Web 容器的入口,那下面我们就从这个方法入手。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// -☆- org.springframework.web.servlet.HttpServletBean
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {...}

    // 获取 ServletConfig 中的配置信息
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            /*
             * 为当前对象(比如 DispatcherServlet 对象)创建一个 BeanWrapper,
             * 方便读/写对象属性。
             */ 
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            // 设置配置信息到目标对象中
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            if (logger.isErrorEnabled()) {...}
            throw ex;
        }
    }

    // 进行后续的初始化
    initServletBean();

    if (logger.isDebugEnabled()) {...}
}

protected void initServletBean() throws ServletException {
}

上面的源码主要做的事情是将 ServletConfig 中的配置信息设置到 HttpServletBean 的子类对象中(比如 DispatcherServlet),我们并未从上面的源码中发现创建容器的痕迹。不过如果大家注意看源码的话,会发现 initServletBean 这个方法稍显奇怪,是个空方法。这个方法的访问级别为 protected,子类可进行覆盖。HttpServletBean 子类 FrameworkServlet 覆写了这个方法,下面我们到 FrameworkServlet 中探索一番。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// -☆- org.springframework.web.servlet.FrameworkServlet
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {...}
    long startTime = System.currentTimeMillis();

    try {
        // 初始化容器
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {...}
}

protected WebApplicationContext initWebApplicationContext() {
    // 从 ServletContext 中获取容器,也就是 ContextLoaderListener 创建的容器
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    /*
     * 若下面的条件成立,则需要从外部设置 webApplicationContext。有两个途径可以设置 
     * webApplicationContext,以 DispatcherServlet 为例:
     *    1. 通过 DispatcherServlet 有参构造方法传入 WebApplicationContext 对象
     *    2. 将 DispatcherServlet 配置到其他容器中,由其他容器通过 
     *       setApplicationContext 方法进行设置
     *       
     * 途径1 可参考 AbstractDispatcherServletInitializer 中的 
     * registerDispatcherServlet 方法源码。一般情况下,代码执行到此处,
     * this.webApplicationContext 为 null,大家可自行调试进行验证。
     */
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 设置 rootContext 为父容器
                    cwac.setParent(rootContext);
                }
                // 配置并刷新容器
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 尝试从 ServletContext 中获取容器
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 创建容器,并将 rootContext 作为父容器
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        onRefresh(wac);
    }

    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        // 将创建好的容器设置到 ServletContext 中
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {...}
    }

    return wac;
}

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    // 获取容器类型,默认为 XmlWebApplicationContext.class
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {...}
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }

    // 通过反射实例化容器
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    // 配置并刷新容器
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // 设置容器 id
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            // 生成默认 id
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    // 后置处理,子类可以覆盖进行一些自定义操作。在 Spring MVC 未使用到,是个空方法。
    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    // 刷新容器
    wac.refresh();
}

以上就是创建 Web 容器的源码,下面总结一下该容器创建的过程。如下:

  1. 从 ServletContext 中获取 ContextLoaderListener 创建的容器
  2. 若 this.webApplicationContext != null 条件成立,仅设置父容器和刷新容器即可
  3. 尝试从 ServletContext 中获取容器,若容器不为空,则无需执行步骤4
  4. 创建容器,并将 rootContext 作为父容器
  5. 设置容器到 ServletContext 中

到这里,关于 Web 容器的创建过程就讲完了。总的来说,Web 容器的创建过程和业务容器的创建过程大致相同,但是差异也是有的,不能忽略。

3.总结

本篇文章对 Spring MVC 两种容器的创建过程进行了较为详细的分析,总的来说两种容器的创建过程并不是很复杂。大家在分析这两种容器的创建过程时,看的不明白的地方,可以进行调试,这对于理解代码逻辑还是很有帮助的。当然阅读 Spring MVC 部分的源码最好有 Servlet 和 Spring IOC 容器方面的知识,这些是基础,Spring MVC 就是在这些基础上构建的。

限于个人能力,文章叙述有误,还望大家指明。也请多多指教,在这里说声谢谢。好了,本篇文章就到这里了。感谢大家的阅读。

参考

附录:Spring 源码分析文章列表

Ⅰ. IOC

更新时间

标题

2018-05-30

Spring IOC 容器源码分析系列文章导读

2018-06-01

Spring IOC 容器源码分析 - 获取单例 bean

2018-06-04

Spring IOC 容器源码分析 - 创建单例 bean 的过程

2018-06-06

Spring IOC 容器源码分析 - 创建原始 bean 对象

2018-06-08

Spring IOC 容器源码分析 - 循环依赖的解决办法

2018-06-11

Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

2018-06-11

Spring IOC 容器源码分析 - 余下的初始化工作

Ⅱ. AOP

更新时间

标题

2018-06-17

Spring AOP 源码分析系列文章导读

2018-06-20

Spring AOP 源码分析 - 筛选合适的通知器

2018-06-20

Spring AOP 源码分析 - 创建代理对象

2018-06-22

Spring AOP 源码分析 - 拦截器链的执行过程

Ⅲ. MVC

更新时间

标题

2018-06-29

Spring MVC 原理探秘 - 一个请求的旅行过程

2018-06-30

Spring MVC 原理探秘 - 容器的创建过程

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-07-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Latex学习笔记(十)新模板的使用
最近有学长做了个新的MCMlatex模板 点击此处可以下载:下载. 主要内容如下: %% 美赛模板:正文部分 \documentclass[12pt]{article} % 官方要求字号不小于 12 号,此处选择 12 号字体 % 本模板不需要填写年份,以当前电脑时间自动生成 % 请在以下的方括号中填写队伍控制号 \usepackage[1234567]{easymcm} % 载入 EasyMCM 模板文件 \problem{A} % 请在此处填写题号 \usepackage{mathptmx}
zstar
2022/06/14
6840
LaTeX详细教程+技巧总结[通俗易懂]
若想学习Markdown,请参见我的另一篇博客:Markdown详细教程+技巧总结 。 若想直接学习LaTeX数学公式,请参见我的另一篇博客:LaTeX数学公式-详细教程 。
全栈程序员站长
2022/08/29
17.5K0
LaTeX详细教程+技巧总结[通俗易懂]
使用 LaTeX 进行论文写作
最近几个月一直在忙着跑实验,写论文,博客确实也是好久没有更新了,乘着最近论文搞得差不多了,碰巧也是在排版,来记录一下使用 LaTeX 进行论文写作的一些东西。
EmoryHuang
2023/03/12
2.6K0
使用 LaTeX 进行论文写作
LaTeX简单常用方法笔记,附模板
标题:\title{标题} 作者:\author{作者} 学号:\studentid{123} 正文:
小锋学长生活大爆炸
2021/06/11
1.6K0
LaTeX简单常用方法笔记,附模板
妈蛋,这玩意还真不得不会!
因为现在的期刊论文,尤其是计算机类的,越来越多是有统一格式要求的,并且有模板提供,这个模板就是以.tex结尾的文件包。Latex相对于word对数学公式更友好,格式更漂亮规范,处处体现了科研人员的严谨与认真。开始使用Latex到爱上Latex的过程,形容一下,就像“榴莲宝们”第一次吃到榴莲之后欲罢不能的全过程。
谭庆波
2019/05/14
1.1K0
妈蛋,这玩意还真不得不会!
Latex论文表格画法
\begin{table}[htbp] 表示表格的开始。中括号中的 htbp 表示的是表格的浮动格式。当然这个基本参数不仅仅只是对表格有用。需要注意的是,一般使用 [htb] 这样的组合,这样组合的意思就是Latex会尽量满足排在前面的浮动格式,就是 h-t-b 这个顺序,让排版的效果尽量好。         [h] 表示将表格放在当前位置。         [t] 表示将表格放置在页面的顶部。         [b] 表示将表格放置在页面的底部。         [p] 将表格放置在一只允许有浮动对象的页面上。     \caption{my table} 表示表格的标题,该设置可以放在 \begin{tabular} \end{tabular} 环境的前后,使得表格的标题显示在表格的上面或下面。\label{table1} 表示表格名字,用于正文中引用表格。     若要插入跨栏图表, 可以用浮动环境 table* 。\begin{table}[htbp] 变成 \begin{table*}[htbp] ,\end{table} 变成 \end{table*} 。     \begin{tabular}[位置]{列} 和 \begin{tabular*}{宽度}[位置]{列} 设置表格环境参数格式。         \begin{tabular}{|c|c|c|} 。一个 c 表示有一列,格式为居中显示,这是列必选参数。通过添加 | 来表示是否需要绘制竖线。|| 表示画二条紧相邻的竖直线。             l 表示该列左对齐。             c 表示该列居中对齐。             r 表示该列右对齐。         如果只需要某几列的宽度发生改变,可以使用 p{宽度} (以 cm 为单位或以 pt 为单位或 0.2\textwidth)来代替 c 参数,但是表格中的文字是默认左对齐的。因此此时可以添加 p{宽度}<{\centering} 来改变文本对齐方式,但此时需要添加包 \usepackage{array} 。在这里 \centering 参数可以被 \raggedleft 和 \raggedright 替换,分别表示为左对齐和右对齐。         也可以使用 tabular* (\begin{tabular*}{宽度}[位置]{列})环境参数,如上的 {宽度} 可以设置为 {10cm},表示整个表格的宽度为 10cm。但由于设置了表格的整体宽度,为了使表格对齐,需要使用表达式 @{\extracolsep{\fill}} ,但画正式表格一般 不推荐 使用这种表格方式(比较复杂,感觉一般用于画类似三线表格的图表中),可以通过命令调整整个表格的缩放。         \begin{tabular}[位置]{cc}。[位置] 中的参数是位置可选参数,该参数表示表格相对于外部文本行基线的位置,又称为垂直定位参数。一般为默认不设置,表示表格按照外部文本行的基线垂直居中。t表示表格顶部与当前外部文本行的基线重合。b 表示表格底部与当前外部文本行的基线重合。     可用 \setlength{\tabcolsep}{1pt} 来调整表格的列间距离 (十分推荐) 。     可用 \renewcommand\arraystretch{1.5} 来调整表格行间距,意思是将每一行的高度变为原来的1.5倍 (十分推荐) 。     如果表格太大,可以使用 \scalebox{1.5} 来对表格进行缩放,意思是将表格的大小变为原来的1.5倍 (十分推荐),使用的时候需要添加包 \usepackage{graphicx} 。
狼啸风云
2020/05/29
11K0
【Latex】2021数模国赛模板使用
本文用于排版时快速复制需要的内容框架。 所用模板为2021Latex国赛模板 插入图片 width根据需要改. \begin{figure}[H] \centering \includegraphics[width=8cm]{../../所用图片/12.png} \caption{问题一流程图} \end{figure} 连续实心点 \begin{itemize} \item \item \item \end{itemize} 插入公式 \begin{equation} \end{equat
zstar
2022/06/14
5640
LaTeX详细安装步骤和简明教程
配置TeXLive和TeXstudio。TeXLive是编译器为Latex提供运行所需的环境;TeXstudio编辑器,提供操作界面,需要先安装好TeXLive之后,TeXstudio才能使用。
全栈程序员站长
2022/08/30
4K0
LaTeX详细安装步骤和简明教程
Latex 论文elsevier,手把手如何用Latex写论文
这几天在开始写论文,准备发的是elsevier,这个网站的instruction有问题,下载的东西基本上好多的错误,所以我就写博客记录。
林德熙
2018/09/19
11.5K0
Latex 论文elsevier,手把手如何用Latex写论文
LaTeX简单常用方法笔记
API参考手册: http://www.personal.ceu.hu/tex/latex.htm
小锋学长生活大爆炸
2020/08/25
1.2K0
LaTeX简单常用方法笔记
Latex论文写作小技巧记录,不断更新
如果是IEEElatex模板,使用“equation”块,格式如下,会自动设置编号:
小锋学长生活大爆炸
2022/09/20
1.5K0
Latex论文写作小技巧记录,不断更新
MATLAB 与 C 语言的混合编程实战之辛普森积分法、自适应辛普森积分
题目大意是让你用c系语言实现辛普森积分法对定积分的粗略估计,所谓辛普森积分法即为:
glm233
2020/09/28
2K0
MATLAB 与 C 语言的混合编程实战之辛普森积分法、自适应辛普森积分
LaTeX基础操作
一个简单的LaTeX文档通常包括导言区(preamble)和正文区(document body),导言区定义文档的类型、使用的宏包等
esse LL
2024/03/12
3280
How to add subfigure in Latex
In research articles, we need to add subfigures often. To create subfigure in latex, you can use both \begin{minipage}...\end{minipage} and \begin{subfigure}...\end{subfigure} block to insert subfigures or sub-images. Subfigurs are generally inserted horizontally in one or multiple rows. Here, some example codes with output screenshots are provided in the following.
marsggbo
2019/03/27
1.6K0
How to add subfigure in Latex
通俗易懂的Latex教程文档
本篇文档可以搭配视频讲解使用。 讲解视频: https://player.bilibili.com/player.html?aid=933470753&page=1 通俗易懂的Latex教程(附数学
zstar
2022/06/14
2.5K0
通俗易懂的Latex教程文档
还在手写LaTeX表格?你可能需要这款神器
既然你点进来看了,说明你也遇到了类似的问题,也经历过手写和调试LaTeX表格的痛苦,现在就让我们解决它。
博士的沙漏
2020/09/03
3.5K0
还在手写LaTeX表格?你可能需要这款神器
LaTex 排版 (2):表格
aTeX 提供了许多工具来创建和定制表格,在本系列中,我们将使用 tabular 和 tabularx 环境来创建和定制表。
用户1880875
2021/09/09
1.5K0
latex中如何画表格_时态结构总结表格
在写论文的时候我们常常会用到三线表,三线表的基本语法就是下面这个样子的。在插入三线表的时候,在引言区加入\usepackage{booktabs} 如果是在双栏的环境里,如果我们的表格比较大,我们一般需要在表格的环境中加星号, 如果是表格只占一栏,这个时候我们就不需要加星号,我们假设我们使用表格的情况是占双栏的。三线表的精华就是那三根线了啦,用的命令就是 \toprule ,\midrule,\bottomrule 这三个命令。就是上中下,然后就是与rule 的结合。
全栈程序员站长
2022/09/22
1.8K0
latex中如何画表格_时态结构总结表格
Latex 三线表 横线竖线短横线
以这个图为例: 样式复现 导言区先添加: \usepackage{tabu} % 表格插入 \usepackage{multirow} % 一般用以设计表格,将所行合并 \usepackage{multicol} % 合并多列 \usepackage{multirow} % 合并多行 \usepackage{float} % 图片浮
小锋学长生活大爆炸
2022/09/29
4.1K0
Latex 三线表 横线竖线短横线
latex图表教程(scienhub平台支持)
LaTeX 中插入图表通常需要使用 \includegraphics 命令,该命令属于 graphicx 宏包。以下是一个简单的 LaTeX 图表教程:
用户4821680
2024/03/28
3180
相关推荐
Latex学习笔记(十)新模板的使用
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档