从 Servlet 到 DispatcherServlet(SpringMvc 容器的创建)

DispatcherServlet 的继承体系

SpringMvc 是一个具有 Spring 容器(ApplicationContext)的 Servlet。其中,HttpServlet 属于 JDK 的内容,从 HttpServletBean 开始,便属于 Spring 体系中的内容。

  • HttpServletBean:XXXBean 是 Spring 框架和其它框架整合时使用,这里将 JDK 中的 Servlet 作为一个框架。
  • Aware 接口:由 Spring 的 BeanPostProcessor 进行调用,通过 setXXX()为 Spring 容器中的 Bean 组件提供功能。例如,当组件希望能够获得整个 Spring 容器时,可以实现 ApplicationContextAware 接口。
    • ApplicationContextAware:告诉 Spring,需要 ApplicationContext 容器
    • EnvironmentAware:告诉 Spring,需要 Environment(包括配置文件和环境变量)
  • Capable 接口:告诉 Spring,可以提供什么东西。如果 Spring 需要这个东西,会通过 getXXX()来获取。
    • EnvironmentCapable:告诉 Spring,可以提供 Environment 配置信息

DispatcherServlet 的继承体系

HttpServletBean 源码解析

重写 GenericServlet 的 init()方法,并提供 initServletBean()方法作为新的扩展点,以取代 GenericServlet 提供的 init()扩展点。
注意:init()方法使用了 final 关键字进行修饰,因此子类无法重载 init()方法。这样便从 Servlet 的规范向 Spring 的规范靠近。

init 流程

public abstract class HttpServletBean 
	extends HttpServlet 
	implements EnvironmentCapable, EnvironmentAware {

    // 重写GenericServlet中的无参init()方法
	@Override
	public final void init() throws ServletException {
        // 从ServletConfig到PropertyValues
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), 
                                                             this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
                // this实际会代表DispatcherServlet
                // BeanWrapper用来操作DispatcherServlet中的属性
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = 
                	new ServletContextResourceLoader(getServletContext());
                
				bw.registerCustomEditor(Resource.class, 
                                        new ResourceEditor(resourceLoader, 
                                                           getEnvironment()));
				initBeanWrapper(bw);
                
                // 将PropertyValues设置到DispatchServlet中
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}
        
        // 提供给子类的扩展点,子类可以接着已有的逻辑进行扩展,
        // 而不用去重写init()方法,然后把这段内容拷贝过去
		initServletBean();
	}
        
	protected void initServletBean() throws ServletException {
	}
}

内部类 ServletConfigPropertyValues

键值对(key-value)在不同的框架中由不同的对象进行封装,在 Servlet 中保存在 ServletConfig 的 initParameter 中,在 Spring 中封装成对象 PropertyValue。
ServletConfigPropertyValues 这个类的作用便是将保存在 ServletConfig 中的配置项信息添加到 PropertyValues 中,并检查这些 initParameter 是否满足 requiredProperties 集合的全部要求,如果不满足则抛出异常。

private static class ServletConfigPropertyValues extends MutablePropertyValues {

    public ServletConfigPropertyValues(ServletConfig config, 
                                       Set<String> requiredProperties) throws ServletException {

        Set<String> missingProps = CollectionUtils.isEmpty(requiredProperties) ?
                 null : new HashSet<>(requiredProperties);

        Enumeration<String> paramNames = config.getInitParameterNames();
        while (paramNames.hasMoreElements()) {
            String property = paramNames.nextElement();
            Object value = config.getInitParameter(property);
            addPropertyValue(new PropertyValue(property, value));
            if (missingProps != null) {
                missingProps.remove(property);
            }
        }

        // Fail if we are still missing properties.
        if (!CollectionUtils.isEmpty(missingProps)) {
            // throw ... 
    }
}

BeanWrapper 是什么?怎么用?

BeanWrapper 是 Spring 提供的用来操作 JavaBean 属性的工具,使用 BeanWrapper 可以直接修改一个对象的属性,例如

public class BeanWrapperDemo {
    public static void main(String[] args) {
        User user = new User(1, "root", 18);
        System.out.println("user = " + user);

        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user);
        // 获取 JavaBean 中的属性值,借助 Spring 的类型系统进行类型强转?
        Integer userId = (Integer) bw.getPropertyValue("userId");
        System.out.println("userId = " + userId);
        String nickname = (String) bw.getPropertyValue("nickname");
        System.out.println("nickname = " + nickname);

        HashMap<String, Object> map = new HashMap<>();
        map.put("userId", 10);
        map.put("nickname", "admin");
        // 故意添加一个不匹配的字段
        map.put("name", "error");
        PropertyValues propertyValues = new MutablePropertyValues(map);

        // User对象并没有name属性,如果不添加第二个参数,那么会报错
        bw.setPropertyValues(propertyValues, true);
        System.out.println("user = " + user);
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
    private Integer userId;
    private String nickname;
    private Integer age;
}

FrameworkServlet 源码解析

从父类 HttpServletBean 的 init()可以看出,子类的 init()无法重写,因此方法扩展点就是 HttpServletBean 中的 initServletBean()

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

	public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
	public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
	private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
	
    @Override
	protected final void initServletBean() throws ServletException {
        // 核心,初始化SpringMvc容器
        this.webApplicationContext = initWebApplicationContext();
        // 留给子类的扩展点
        initFrameworkServlet();
	}

	protected WebApplicationContext initWebApplicationContext() {
        // 获取Spring根容器
        // 在前面某个时间点,会将WebApplicationContext对象设置到ServletContext的属性中(attributes)
        // 以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为key,所以这里从 ServletContext 中取值即可
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
        
		if (this.webApplicationContext != null) {
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		if (wac == null) {
            // 一般情况下会进入到这里来创建
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
            // 当ContextRefreshedEvent事件没有触发时触发onRefresh方法,
            // 上面对wac赋值的三种情况中,只有第二种情况会进入到该方法中
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
            // 将ApplicationContext保存到ServletContext中
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

   protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
		//要调用重载的其它方法,这里只能强转(重载是编译时多态)
       return createWebApplicationContext((ApplicationContext) parent);
	}
    
    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            // throw ...
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
}

WebApplicationContext 接口

public interface WebApplicationContext extends ApplicationContext {
    
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
	String SCOPE_REQUEST = "request";
	String SCOPE_SESSION = "session";
	String SCOPE_APPLICATION = "application";
	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";


	@Nullable
	ServletContext getServletContext();
}

ConfigurableWebApplicationContext 接口

  • ServletContext
  • ServletConfig
  • Namespace
  • ConfigLocations
public interface ConfigurableWebApplicationContext 
	extends WebApplicationContext, 
			ConfigurableApplicationContext {

	String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() + ":";
	String SERVLET_CONFIG_BEAN_NAME = "servletConfig";

	// WebApplicationContext接口中有getServletContext()方法
	void setServletContext(@Nullable ServletContext servletContext);

	void setServletConfig(@Nullable ServletConfig servletConfig);
	ServletConfig getServletConfig();

	void setNamespace(@Nullable String namespace);
	String getNamespace();

	void setConfigLocation(String configLocation);
	void setConfigLocations(String... configLocations);
	String[] getConfigLocations();
}

DispatcherServlet 源码解析

按照和前面相同的分析,DispatcherServlet 应该使用 initFrameworkServlet()来进行初始化方法的扩展,但是实际却并没有使用,使用了 onRefresh()进行扩展。(为什么这么设计?)

public class DispatcherServlet extends FrameworkServlet {
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/609903.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ALV 排序、汇总

目录 前言 实战 汇总 分类汇总 排序 分类汇总分隔方式&#xff08;仅适用于LIST ALV&#xff09; 完整代码&#xff1a; 前言 在SAP ABAP ALV中&#xff0c;排序和汇总是两个关键特性&#xff0c;用于组织和分析数据显示。 排序 排序功能允许用户根据一个或多个…

深入理解指针(4)

目录 1. 字符指针变量2. 数组指针变量2.1 数组指针变量是什么&#xff1f;2.2 数组指针变量怎么初始化 3. ⼆维数组传参的本质4. 函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使⽤4.3 两段有趣的代码4.3.1 typedef 关键字 5. 函数指针数组6. 转移表 1. 字符指针变量 …

gorm-sharding分表插件升级版

代码地址&#xff1a; GitHub - 137/gorm-sharding: Sharding 是一个高性能的 Gorm 分表中间件。它基于 Conn 层做 SQL 拦截、AST 解析、分表路由、自增主键填充&#xff0c;带来的额外开销极小。对开发者友好、透明&#xff0c;使用上与普通 SQL、Gorm 查询无差别.解决了原生s…

探秘主播们的直播美颜SDK:深度学习算法原理

直播美颜技术作为直播行业中的一项重要技术&#xff0c;广受大家关注。本文将深入探讨主播们常用的直播美颜SDK背后的深度学习算法原理&#xff0c;揭秘其神奇之处。 一、什么是直播美颜SDK&#xff1f; 直播美颜SDK是一种应用程序接口&#xff0c;通过嵌入到直播软件中&…

根据数据写动态生成折线图

根据数据格式 dataGoz: [{"xAxis": ["2017-3-1", "2017-3-15", "2017-4-1", "2017-4-12", "2017-5-21", "2017-6-5", "2017-8-12", "2017-9-1", "2017-10-11"],"y…

C++函数模板可变参数如何一次性解包?

零、问题 如下代码中&#xff0c;调用func1的时候&#xff0c;只能递归一次取到一个值&#xff0c;有没有什么方法像func2中那样&#xff0c;一次把所有的值都拿出来呢&#xff1f; ​ 回答 二元操作 (binary operator) 」 &#xff1a;需要两个操作数的操作&#xff0c;比如…

【全开源】JAVA国际版多语言语聊大厅语音聊天APP系统源码

JAVA国际版多语言语聊大厅小程序 随着全球化的加速和互联网技术的飞速发展&#xff0c;人们越来越需要一种能够跨越语言和文化障碍的交流方式。JAVA国际版多语言语聊大厅小程序应运而生&#xff0c;它以其独特的功能和全球化的设计理念&#xff0c;为全球用户提供了一个无障碍…

5.12母亲节营销攻略:TikTok助力出海品牌赢得用户心

母亲节&#xff0c;作为一个全球性的节日&#xff0c;不仅是表达对母亲的感激之情的时刻&#xff0c;也是品牌们展示创意、赢得用户心的黄金机会。2024母亲节将至&#xff0c;如何利用TikTok在母亲节这一特殊时刻进行营销&#xff0c;赢得用户的心&#xff0c;成为出海品牌必须…

私域流量优化:如何利用 AIPL 模型洞察客户生命周期价值

在当今这个数字化时代&#xff0c;商业战场的硝烟从未如此浓烈。随着互联网红利的逐渐消退&#xff0c;公域流量的成本水涨船高&#xff0c;企业间对于有限用户资源的争夺已进入白热化阶段。每一次点击、每一个曝光背后&#xff0c;都是企业不得不承担的高昂代价。在此背景下&a…

【Web】CTFSHOW月饼杯 题解(全)

目录 web1_此夜圆 web2_故人心 web3_莫负婵娟 web1_此夜圆 拿到源码&#xff0c;一眼字符串逃逸 本地测一测&#xff0c;成功弹出计算器 <?phpclass a {public $uname;public $password;public function __wakeup(){system(calc);} }function filter($string){retur…

报名 | AIGC技术分享峰会苏州场来啦!

IGC是近年来人工智能技术迅速发展的一个重要领域。从早期的简单字符生成到现在可以撰写复杂文章、生成高清图片甚至编写代码&#xff0c;AIGC技术的发展突飞猛进&#xff0c;不仅在文学创作、艺术设计、游戏开发和软件编程等领域展现出惊人的潜能&#xff0c;也对各行业提供了前…

项目管理在软件工程中的实践方法

软件工程是一个复杂的过程&#xff0c;涉及到需求分析、设计、编码、测试和维护等多个阶段。有效的项目管理对于确保软件项目成功至关重要。以下是结合附件内容&#xff0c;关于项目管理在软件工程中实践的一些方法。 1. 明确项目愿景和目标 在项目启动之初&#xff0c;项目经…

SpringBoot+logback实现日志记录写入文件

前言 在实际的开发过程中&#xff0c;日志记录有着极其重要的作用&#xff0c;它帮助我们实现更高效的故障排查与调试、更及时的监控和性能优化、更全面的业务分析与决策支持…那么我们如何在SpringBoot项目中实现日志的个性化定制&#xff0c;以满足其他特殊需求呢&#xff1f…

2024年5月6日优雅草蜻蜓API大数据服务中心v2.0.3更新

v2.0.3更新 2024年5月6日优雅草蜻蜓API大数据服务中心v2.0.3更新-修复改版后搜索框漏掉的bug-增加搜索框 提示&#xff1a;优雅草大数据中心已经 上线137天 稳定运行 1181555 次 累积调用 目前大数据中心用户呈现增长趋势&#xff0c;目标2024年11月底突破1亿次调用&#xf…

大语言模型的后处理

后处理的输入 常规意义上的大模型处理流程 import torch from transformers import LlamaForCausalLM, LlamaTokenizer# 加载模型和tokenizer model LlamaForCausalLM.from_pretrained("decapoda-research/llama-7b-hf") tokenizer LlamaTokenizer.from_pretrain…

这个 TypeScript 技巧会让你大吃一惊

从字符串数组中提取自定义类型 “在 TypeScript 的世界里&#xff0c;自定义类型从字符串数组中显现&#xff0c;就像隐藏的宝石。” TypeScript 是一个操纵现有数据和发展良好实践的神奇工具。 今天&#xff0c;我们将探索如何以正确的方式从字符串数组中提取全名&#xff0c…

MPAndroidChart 详细使用 - BarChart

chart下面的方法 getDescription().setEnabled(boolean enabled);//设置描述是否显示 setPinchZoom(boolean enabled);//设置x轴和y轴能否同时缩放。默认是否 setScaleEnabled(boolean enabled);//是否支持缩放 setScaleXEnabled(boolean enabled);//启用/禁用x轴上的缩放 setS…

Vue2中子组件调用父组件的方法,父组件调用子组件的方法,父子组件互相传值和方法调用

&#x1f49f; 上一篇文章 组件之间的多种通信方式&#xff0c;一文彻底搞懂组件通信&#xff01;​​​​​​​ &#x1f4dd; 系列专栏 vue从基础到起飞 目录 一、父组件调用子组件的方法 二、子组件调用父组件的方法 1、使用this.$emit()向父组件触发一个事件,父组件监听…

HTTPS 原理和 TLS 握手机制

HTTPS的概述与重要性 在当今数字化时代&#xff0c;网络安全问题日益凸显&#xff0c;数据在传输过程中的安全性备受关注。HTTPS 作为一种重要的网络通信协议&#xff0c;为数据的传输提供了强有力的安全保障。它是在 HTTP 的基础上发展而来&#xff0c;通过引入数据加密机制&a…

RazorSQL for Mac:强大而全面的数据库管理工具

RazorSQL for Mac是一款功能强大、操作简便的数据库管理工具。它专为Mac用户设计&#xff0c;支持连接超过30种不同类型的数据库&#xff0c;包括MySQL、Oracle、PostgreSQL等&#xff0c;为用户提供了全面的数据库管理解决方案。 RazorSQL具有强大的数据库浏览功能&#xff0c…