欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java源码 - SpringMVC(1)之初始组件

程序员文章站 2022-11-23 10:22:56
SpringMVC的本质是一个Servlet建议看SpringMVC源码时,对文章目录1. 环境搭建(maven)1. 环境搭建(maven)...

SpringMVC的本质是一个Servlet
建议看SpringMVC源码时,对Servlet和Tomcat要有一定的了解
看懂注释很重要
之前的一篇

1. 环境搭建(maven)

项目结构如下:
java源码 - SpringMVC(1)之初始组件

1.1 导入pom

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

如果使用tomcat部署项目,那么记得pom打war包;
java源码 - SpringMVC(1)之初始组件

1.2 配置文件

配置一个Spring MVC只需要三步:
①在web.xml中配置Servlet;
②创建Spring MVC的xml配置文件;
③创建Controller和view。

过程如下,拷贝即可:

  1. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">

    <!-- 配置 DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置 DispatcherServlet 的一个初始化参数: 配置 SpringMVC 配置文件的位置和名称 -->
        <!--
            实际上也可以不通过 contextConfigLocation 来配置 SpringMVC 的配置文件, 而使用默认的.
            默认的配置文件为: /WEB-INF/<servlet-name>-servlet.xml
        -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- 加载时创建 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- dispatcherServlet可以应答所有请求 -->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
  1. springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 配置自动扫描的包 配置以后才能使用Spring的注解-->
    <context:component-scan base-package="com.lyq.mvc"></context:component-scan>

    <!-- 配置视图解析器: 如何把 handler 方法返回值解析为实际的物理视图 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>


</beans>
  1. controller和view
@Controller
public class HelloController {

    @RequestMapping("/hello")
    @ResponseBody
    public String hello() {
        return "hello";
    }

    @RequestMapping("/success")
    public String success() {
        return "success";
    }

}
<html>
<body>
<h2>success</h2>

</body>
</html>

  1. idea配置tomcat

java源码 - SpringMVC(1)之初始组件java源码 - SpringMVC(1)之初始组件
我修改了项目的默认index,index.jsp,目的是为了打开项目就能跳转;

<html>
<body>
<h2>Hello World!</h2>
<a href="success">chenggong</a>
</body>
</html>

随后启动就搭建好了项目;

2. SpringMVC的整体结构

在IDEA下shift两下,输入SpringMVC的入口类:DispatcherServlet
Ctrl+Shift+Alt+U生成类图:
java源码 - SpringMVC(1)之初始组件
GenericServlet和HttpServlet在java中,属于java规范的接口,剩下的三个类HttpServletBean、FrameworkServlet和DispatcherServlet是SpringMVC中的。

2.1 HttpServletBean

java源码 - SpringMVC(1)之初始组件
EnvironmentCapable:表明SpringMVC框架可以提供环境,所谓环境大概是指一些配置文件,配置属性,系统变量,环境变量等;
Spring需要环境就调用这个接口的方法即可拿到环境;
java源码 - SpringMVC(1)之初始组件
EnvironmentAware:同ApplicationContextAware,需要spring的环境;

接下来做个小测试:我们需要的Environment到底是个什么东西?

HelloController 实现EnvironmentAware 接口;

@Controller
public class HelloController implements EnvironmentAware {

    private Environment environment = null;
    @RequestMapping("/getEnvironment")
    public String getEnvironment() {
        String[] activeProfiles = environment.getActiveProfiles();
        String[] profiles = environment.getDefaultProfiles();

        return profiles.toString()+activeProfiles.toString();
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

查看调试器:
java源码 - SpringMVC(1)之初始组件
java源码 - SpringMVC(1)之初始组件

从图中可以看到ServletConfigPropertySource的source的类型是StandardWrapperFacade,也就是Tomcat里定义的ServletConfig类型,所以ServletConfigPropertySource封装的就是ServletConfig。
在web.xml中定义的contextConfigLocation可以在config下的parameters里看到,这里还可以看到name以及parent等属性。
ServletContextPropertySource中保存的是ServletContext;
java源码 - SpringMVC(1)之初始组件

其他的,System…Property的读取的是本电脑的环境变量,JNDI没有用到所以没有;

如果有Tomcat与Servlet的源码阅读经验,我们可以知道:Servlet创建时可以直接调用无参数的init方法。

@Override
	public final void init() throws ServletException {

		// 从init参数设置bean属性。
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				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()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		//让子类做它们喜欢的任何初始化。即一个钩子函数
		initServletBean();
	}

可以看到,在HttpServletBean的init中,首先将Servlet中配置的参数使用BeanWrapper设置到DispatcherServle的相关属性,然后调用模板方法initServletBean,子类就通过这个方法初始化。

关于BeanWrapper的介绍
BeanWrapper是Spring提供的一个用来操作JavaBean属性的工具,使用它可以直接修改一个对象的属性,即一个封装了的java反射框架;

2.2 FrameworkServlet

用于Spring web框架的基本servlet。为集成提供了一个基于javabean的整体解决方案中的Spring应用程序上下文。
java源码 - SpringMVC(1)之初始组件
简而言之,我的这个框架需要使用Spring的环境,那么我就得实现ApplicationContextAware通过这个接口里的:

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

setApplicationContext获取到Spring的应用上下文;
而继承HttpServletBean,原因是:子类必须实现来处理请求。因为这个扩展
而不是直接HttpServlet, bean属性是自动映射到它。子类可以重写定义初始化。

从HttpServletBean中可知,FrameworkServlet的初始化入口方法应该是initServletBean,因为这是一个模板方法设计思想,父类留了一个钩子函数:
所以我们可以很轻易的就找到initServletBean():

/**
	*覆盖{@link HttpServletBean}的方法,在任何bean属性之后调用
	*创建这个servlet的WebApplicationContext。
	 */

java源码 - SpringMVC(1)之初始组件
核心代码:初始化WebApplicationContext,初始化FrameworkServlet,而且initFrameworkServlet方法是模板方法,子类可以覆盖然后在里面做一些初始化的工作,但子类并没有使用它。
接下来看initWebApplicationContext:

/**初始化并发布这个servlet的WebApplicationContext。
*
委托{@link #createWebApplicationContext}进行实际创建
*上下文。可以在子类中重写。
*/
	protected WebApplicationContext initWebApplicationContext() {
		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()) {
					// 上下文还没有被刷新——>提供诸如此类的服务
					// 设置父上下文,设置应用程序上下文id,等等
					if (cwac.getParent() == null) {
						// 注入上下文实例时没有设置显式父对象>
						// 根应用程序上下文(如果有的话);可能是空)作为父
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// 在构造时没有注入上下文实例——看看是否有
			// 已在servlet上下文中注册。如果存在,它是假设的
			// 父上下文(如果有的话)已经设置,并且
			//用户已执行任何初始化,如设置上下文id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// 没有为这个servlet定义上下文实例->创建一个本地实例
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// 上下文不是带有refresh的ConfigurableApplicationContext
			// 在构建时注入的支持或上下文已经被注入
			// 刷新->手动触发初始onRefresh。
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			//将上下文作为servlet上下文属性发布。
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

initWebApplicationContext方法做了三件事

  • 获取spring的根容器rootContext。
  • 设置webApplicationContext并根据情况调用onRefresh方法。
  • 将webApplicationContext设置到ServletContext中。

2.3. DispatcherServlet

onRefresh方法是DispatcherServlet的入口方法。

org.springframework.web.servlet.FrameworkServlet
/**
	*模板方法,可以重写该方法以添加特定于servlet的刷新工作。
	*在成功刷新上下文后调用。
	*
	该实现为空。
	* @param上下文当前WebApplicationContext
	* @see # refresh ()
	* /
	protected void onRefresh(ApplicationContext context) {
		// For subclasses: do nothing by default.
	}
org.springframework.web.servlet.DispatcherServlet
/**
	 * 这个实现调用{@link #initStrategies}。
	 */
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
	/ * *
	初始化servlet使用的策略对象。
	为了进一步初始化策略对象,>可能会在子类中被重写。
* /
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

独立出initStrategies方法的原因:
其实这主要是分层的原因,onRefresh是用来刷新容器的,initStrategies用来初始化一些策略组件。如果把initStrategies里面的代码直接写到onRefresh里面,对于程序的运行也没有影响,不过这样一来,如果在onRefresh中想再添加别的功能,就会没有将其单独写一个方法出来逻辑清晰,不过这并不是最重要的,更重要的是,如果在别的地方也需要调用initStrategies方法(如需要修改一些策略后进行热部署),但initStrategies没独立出来,就只能调用onRefresh,那样在onRefresh增加了新功能的时候就麻烦了。另外单独将initStrategies写出来还可以被子类覆盖,使用新的模式进行初始化。

initStrategies的具体内容非常简单,就是初始化的9个组件。
初始化很简单:
首先通过context.getBean在容器里面按注册时的名称或类型(这里指“localeResolver”名称或者LocaleResolver.class类型)进行查找,所以在Spring MVC的配置文件中只需要配置相应类型的组件,容器就可以自动找到。如果找不到就调用getDefaultStrategy按照类型获取默认的组件。
默认组件存在一个文件:
java源码 - SpringMVC(1)之初始组件
java源码 - SpringMVC(1)之初始组件
一共定义了8个组件,处理上传组件Multi-partResolver是没有默认配置的,这也很容易理解,并不是每个应用都需要上传功能,即使需要上传也不一定就要使用MultipartResolver,所以MultipartResolver不需要默认配置。另外HandlerMapping、HandlerAdapter和HandlerExceptionResolver都配置了多个,其实View-Resolver也可以有多个,只是默认的配置只有一个。

本文地址:https://blog.csdn.net/littlewhitevg/article/details/107265103