本文最后更新于:2024年4月22日 下午
搭建springmvc源码环境。准备工作与前置工作分析
前置基础:了解SPI,Service Provider Interface,服务提供者接口 jdk提供给“服务提供厂商”或者“插件开发者”使用的接口,不需要修改原来作为接口的jar的情况下,将原来实现的那个jar替换为另外一种实现的jar即可。
使用规范:
定义服务的通用接口,针对通用的服务接口,提供具体的实现类。
在jar包的META-INF/services/目录中,新建一个文件,文件名为 接口的”全限定名”。 文件内容为该接口的具体实现类的”全限定名”。
将spi所在jar放在主程序的classpath中
服务调用方用java.util.ServiceLoader,用服务接口为参数,去动态加载具体的实现类到JVM中。
模块搭建 1、新增springmvcsource-test
2、引入相关依赖 springmvcsource-test 的build.gradle dependencies下加入依赖
2.1 源码webmvc implementation ( project ( ":spring-webmvc" ) )
2.2 servlet依赖 implementation group: 'javax.servlet' , name: 'javax.servlet-api' , version: '4.0.1'
ps:这里是implementation ,这样lib才会打入war,从而Tomcat启动springmvc相关才会生效(非常重要),这里卡了我大半天。
不然访问时候总是404 找不到路径
2.3 gradle 补充知识点 目前Gradle版本支持的依赖配置有:implementation、api、compileOnly、runtimeOnly和annotationProcessor,已经废弃的配置有:compile、provided、apk、providedCompile。此外依赖配置还可以加一些配置项,例如AndroidTestImplementation、debugApi等等。
常用的是implementation、api、compileOnly三个依赖配置,含义如下:
implementation 与compile对应,会添加依赖到编译路径,并且会将依赖打包到输出(aar或apk),但是在编译时不会将依赖的实现暴露给其他module,也就是只有在运行时其他module才能访问这个依赖中的实现。使用这个配置,可以显著提升构建时间,因为它可以减少重新编译的module的数量。建议,尽量使用这个依赖配置。
api 与compile对应,功能完全一样,会添加依赖到编译路径,并且会将依赖打包到输出(aar或apk),与implementation不同,这个依赖可以传递,其他module无论在编译时和运行时都可以访问这个依赖的实现,也就是会泄漏一些不应该不使用的实现。举个例子,A依赖B,B依赖C,如果都是使用api配置的话,A可以直接使用C中的类(编译时和运行时),而如果是使用implementation配置的话,在编译时,A是无法访问C中的类的。
compileOnly 与provided对应,Gradle把依赖加到编译路径,编译时使用,不会打包到输出(aar或apk)。这可以减少输出的体积,在只在编译时需要,在运行时可选的情况,很有用。
runtimeOnly 与apk对应,gradle添加依赖只打包到APK,运行时使用,但不会添加到编译路径。这个没有使用过。
annotationProcessor 与compile对应,用于注解处理器的依赖配置。
3、编码 package cn. hyqup. web ;
import cn. hyqup. web. config. AppConfig ;
import org. springframework. web. WebApplicationInitializer ;
import org. springframework. web. context. support. AnnotationConfigWebApplicationContext ;
import org. springframework. web. servlet. DispatcherServlet ;
import javax. servlet. ServletContext ;
import javax. servlet. ServletException ;
import javax. servlet. ServletRegistration ;
public class AppStarter implements WebApplicationInitializer {
@Override
public void onStartup ( ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext ( ) ;
context. register ( AppConfig . class ) ;
DispatcherServlet servlet = new DispatcherServlet ( context) ;
ServletRegistration. Dynamic registration = servletContext. addServlet ( "app" , servlet) ;
registration. setLoadOnStartup ( 1 ) ;
registration. addMapping ( "/" ) ;
}
}
WebApplicationInitializer需要servlet3.0以上 ,Tomcat6.0以上 (6.0以上支持servlet3.0)
上面的配置相当于web.xml配置了,上述代码采用api形式植入
< web-app>
< listener>
< listener-class> org.springframework.web.context.ContextLoaderListener</ listener-class>
</ listener>
< context-param>
< param-name> contextConfigLocation</ param-name>
< param-value> /WEB-INF/app-context.xml</ param-value>
</ context-param>
< servlet>
< servlet-name> app</ servlet-name>
< servlet-class> org.springframework.web.servlet.DispatcherServlet</ servlet-class>
< init-param>
< param-name> contextConfigLocation</ param-name>
< param-value> </ param-value>
</ init-param>
< load-on-startup> 1</ load-on-startup>
</ servlet>
< servlet-mapping>
< servlet-name> app</ servlet-name>
< url-pattern> /app/*</ url-pattern>
</ servlet-mapping>
</ web-app>
package cn. hyqup. web. config ;
import org. springframework. context. annotation. ComponentScan ;
import org. springframework. context. annotation. Configuration ;
@ComponentScan ( "cn.hyqup.web" )
@Configuration
public class AppConfig {
}
说明:配置了AppStarter,相当于Tomcat一启动就加载
创建了容器,spring基本功能完成
注册了一个根servlet:DispatcherServlet,后面所有的请求都由 DispatcherServlet 处理
package cn. hyqup. web. controller ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
public class HelloController {
@GetMapping ( "/say" )
public String say ( ) {
return "Hello SpringMVC" ;
}
}
4、部署访问 http://localhost:8080/webmvc/say
原理剖析
SpringMVC基于SPI启动了web容器,servlet定义ServletContainerInitializer。
主流程分析启动web容器 servlet定义ServletContainerInitializer,
@HandlesTypes ( WebApplicationInitializer . class )
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup ( @Nullable Set < Class < ? > > webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List < WebApplicationInitializer > initializers = Collections . emptyList ( ) ;
if ( webAppInitializerClasses != null ) {
initializers = new ArrayList < > ( webAppInitializerClasses. size ( ) ) ;
for ( Class < ? > waiClass : webAppInitializerClasses) {
if ( ! waiClass. isInterface ( ) && ! Modifier . isAbstract ( waiClass. getModifiers ( ) ) &&
WebApplicationInitializer . class . isAssignableFrom ( waiClass) ) {
try {
initializers. add ( ( WebApplicationInitializer )
ReflectionUtils . accessibleConstructor ( waiClass) . newInstance ( ) ) ;
}
catch ( Throwable ex) {
throw new ServletException ( "Failed to instantiate WebApplicationInitializer class" , ex) ;
}
}
}
}
if ( initializers. isEmpty ( ) ) {
servletContext. log ( "No Spring WebApplicationInitializer types detected on classpath" ) ;
return ;
}
servletContext. log ( initializers. size ( ) + " Spring WebApplicationInitializers detected on classpath" ) ;
AnnotationAwareOrderComparator . sort ( initializers) ;
for ( WebApplicationInitializer initializer : initializers) {
initializer. onStartup ( servletContext) ;
}
}
}
HandlesTypes 感兴趣的类WebApplicationInitializer,会去找到所有实现了WebApplicationInitializer的类,我们这里AppStarter就是实现了WebApplicationInitializer,启动时候执行WebApplicationInitializer.onStartup
接下来逻辑:
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext ( ) ;
context. register ( AppConfig . class ) ;
DispatcherServlet servlet = new DispatcherServlet ( context) ;
ServletRegistration. Dynamic registration = servletContext. addServlet ( "app" , servlet) ;
registration. setLoadOnStartup ( 1 ) ;
registration. addMapping ( "/" ) ;
准备一个空的webioc容器,准备一个DispatcherServlet,并将ioc容器传过去,并注册到ServletContext(Tomcat)中去。DispatcherServlet本质上也是一个servlet,所以servlet在执行init的时候就会将该ioc容器执行spring相关的refresh()逻辑将容器刷新。具体初始化逻辑会在HttpServletBean init重写,init有个抽象方法initServletBean,FrameworkServlet来实现initServletBean
@Override
protected final void initServletBean ( ) throws ServletException {
getServletContext ( ) . log ( "Initializing Spring " + getClass ( ) . getSimpleName ( ) + " '" + getServletName ( ) + "'" ) ;
if ( logger. isInfoEnabled ( ) ) {
logger. info ( "Initializing Servlet '" + getServletName ( ) + "'" ) ;
}
long startTime = System . currentTimeMillis ( ) ;
try {
this . webApplicationContext = initWebApplicationContext ( ) ;
initFrameworkServlet ( ) ;
}
catch ( ServletException | RuntimeException ex) {
logger. error ( "Context initialization failed" , ex) ;
throw ex;
}
if ( logger. isDebugEnabled ( ) ) {
String value = this . enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data" ;
logger. debug ( "enableLoggingRequestDetails='" + this . enableLoggingRequestDetails +
"': request parameters and headers will be " + value) ;
}
if ( logger. isInfoEnabled ( ) ) {
logger. info ( "Completed initialization in " + ( System . currentTimeMillis ( ) - startTime) + " ms" ) ;
}
}
初始化代码
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 ( ) ) {
if ( cwac. getParent ( ) == null ) {
cwac. setParent ( rootContext) ;
}
configureAndRefreshWebApplicationContext ( cwac) ;
}
}
}
if ( wac == null ) {
wac = findWebApplicationContext ( ) ;
}
if ( wac == null ) {
wac = createWebApplicationContext ( rootContext) ;
}
if ( ! this . refreshEventReceived) {
synchronized ( this . onRefreshMonitor) {
onRefresh ( wac) ;
}
}
if ( this . publishContext) {
String attrName = getServletContextAttributeName ( ) ;
getServletContext ( ) . setAttribute ( attrName, wac) ;
}
return wac;
}
父子容器的概念引入 以前xml配置springMVC的时候步骤,需要在web.xml中配置
1、在web.xml配置ContextLoadListener,指定Spring配置文件位置
2、在web.xml配置DispatcherServlet,指定SpringMVC配置文件的位置
3、以上操作就会产生父子容器
父容器:Root Spring配置文件进行包扫描并保存的组件的容器
子容器:SpringMVC配置文件进行包扫描并保存的所有组件的容器
cwac.setParent(rootContext);好处是容器之间的隔离,类似于Java中类加载的双亲委派模型
基于两个事件回调启动了Spring和SpringMVC 第一个父容器相关 AbstractAnnotationConfigDispatcherServletInitializer
AbstractContextLoaderInitializer 注册ContextLoaderListener,web应用启动以后(Tomcat加载应用以后)会执行ContextLoaderListener里面contextInitialized的逻辑,监听器机制,servlet的标准
@Override
public void onStartup ( ServletContext servletContext) throws ServletException {
registerContextLoaderListener ( servletContext) ;
}
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
createRootApplicationContext由孙子类 AbstractAnnotationConfigDispatcherServletInitializer 实现
protected WebApplicationContext createRootApplicationContext ( ) {
Class < ? > [ ] configClasses = getRootConfigClasses ( ) ;
if ( ! ObjectUtils . isEmpty ( configClasses) ) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext ( ) ;
context. register ( configClasses) ;
return context;
}
else {
return null ;
}
}
而 getRootConfigClasses 就是获取自子类的配置文件,也就是我们所说父子容器中父容器Spring组件相关的配置类
第二个子容器相关 第二个我们发现 AbstractAnnotationConfigDispatcherServletInitializer 有一个getRootConfigClasses 同时也有一个getRootConfigClasses,获取和servlet相关的。追溯发现
AbstractDispatcherServletInitializer
protected void registerDispatcherServlet ( ServletContext servletContext) {
String servletName = getServletName ( ) ;
Assert . hasLength ( servletName, "getServletName() must not return null or empty" ) ;
WebApplicationContext servletAppContext = createServletApplicationContext ( ) ;
Assert . notNull ( servletAppContext, "createServletApplicationContext() must not return null" ) ;
FrameworkServlet dispatcherServlet = createDispatcherServlet ( servletAppContext) ;
Assert . notNull ( dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null" ) ;
dispatcherServlet. setContextInitializers ( getServletApplicationContextInitializers ( ) ) ;
ServletRegistration. Dynamic registration = servletContext. addServlet ( servletName, dispatcherServlet) ;
if ( registration == null ) {
throw new IllegalStateException ( "Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name." ) ;
}
registration. setLoadOnStartup ( 1 ) ;
registration. addMapping ( getServletMappings ( ) ) ;
registration. setAsyncSupported ( isAsyncSupported ( ) ) ;
Filter [ ] filters = getServletFilters ( ) ;
if ( ! ObjectUtils . isEmpty ( filters) ) {
for ( Filter filter : filters) {
registerServletFilter ( servletContext, filter) ;
}
}
customizeRegistration ( registration) ;
}
createServletApplicationContext 又是由孙子类 AbstractAnnotationConfigDispatcherServletInitializer实现
@Override
protected WebApplicationContext createServletApplicationContext ( ) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext ( ) ;
Class < ? > [ ] configClasses = getServletConfigClasses ( ) ;
if ( ! ObjectUtils . isEmpty ( configClasses) ) {
context. register ( configClasses) ;
}
return context;
}
demo演示 @ComponentScan ( value = "cn.hyqup.web" , excludeFilters = {
@ComponentScan.Filter ( type = FilterType . ANNOTATION , value = Controller . class )
} )
@Configuration
public class SpringConfig {
}
@ComponentScan ( value = "cn.hyqup.web" , includeFilters = {
@ComponentScan.Filter ( type = FilterType . ANNOTATION , value = Controller . class )
} , useDefaultFilters = false )
@Configuration
public class SpringMVCConfig {
}
public class QuickAppStater extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class < ? > [ ] getRootConfigClasses ( ) {
return new Class < ? > [ ] { SpringConfig . class } ;
}
@Override
protected Class < ? > [ ] getServletConfigClasses ( ) {
return new Class < ? > [ ] { SpringMVCConfig . class } ;
}
@Override
protected String [ ] getServletMappings ( ) {
return new String [ ] { "/" } ;
}
}
继承了AbstractAnnotationConfigDispatcherServletInitializer,实现了快速启动spring容器和springmvc容器
整体流程分析 代码阶段
Tomcat启动后会扫描所有的WebApplicationInitializer执行onStartup方法,会扫描到我们所写的QuickAppStater,执行onStartup,会执行super.onStartup
会执行到AbstractDispatcherServletInitializer的方法首先执行父类AbstractContextLoaderInitializer去执行registerContextLoaderListener,会根据spring的配置类创建一个空容器AnnotationConfigWebApplicationContext,注解版本的。此时容器还未刷新,没有功能
AbstractDispatcherServletInitializer其次会执行registerDispatcherServlet注册DispatcherServlet,根据web相关的配置类AnnotationConfigWebApplicationContext,第二个容器创建出来
流程分析
首先按照代码阶段会将我们的父子容器创建出来,也就是两个AnnotationConfigWebApplicationContext,
(父容器)web启动完成的时候,Tomcat触发监听器启动根容器,也就是ContextLoaderListener里面的contextInitialized进行initWebApplicationContext初始化,也就是会执行到refresh()逻辑装配Spring相关的容器(比如AOP、事务、IOC、自动装配(包括了service层、dao层))
(子容器)由于DispatcherServlet本质上就是一个servlet,所以tomcat启动之后回调用init方法初始化,这个时候就是执行到对DispatcherServlet里面的AnnotationConfigWebApplicationContext进行refresh装配web相关的SpringMVC相关的容器(比如@Controller相关的)
注意:由于设计上是子容器刷新的时候父容器已经刷新完毕,并且将父容器设置到子容器对象中,所以我们在controller(子容器)装配service(父容器)正常,而service装配controller就是不支持的