做视频网站把视频放在哪里,做那网站好,中细软网站建设,南宁企业网站设计1、Spring整合SpringMVC
特性#xff1a;
说到Spring整合SpringMVC唯一的体现就是父子容器#xff1a;
通常我们会设置父容器#xff08;Spring#xff09;管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean .子容器可以访问父容器的Bean, 父容器无法访…1、Spring整合SpringMVC
特性
说到Spring整合SpringMVC唯一的体现就是父子容器
通常我们会设置父容器Spring管理Service、Dao层的Bean, 子容器(SpringMVC)管理Controller的Bean .子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。
实现
相信大家在SSM框架整合的时候都曾在web.xml配置过这段
!--spring 基于web应用的启动--
listenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class
/listener
!--全局参数spring配置文件--
context-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-core.xml/param-value
/context-param
!--前端调度器servlet--
servletservlet-namedispatcherServlet/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!--设置配置文件的路径--init-paramparam-namecontextConfigLocation/param-nameparam-valueclasspath:spring-mvc.xml/param-value
/init-param!--设置启动即加载--load-on-startup1/load-on-startup
/servlet
servlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern
但是它的作用是什么知道吗 有人可能只知道DispatcherServlet叫前端控制器是SpringMVC处理前端请求的一个核心调度器
那它为什么能处理请求处理之前做了什么准备工作呢又是怎么和Spring结合起来的呢
为什么有了DispatcherServlet还要个ContextLoaderListener 配一个不行吗干嘛要配俩啊
看完本文你就会有答案 还有人可能会觉得 我现在都用SpringBoot开发 哪还要配这玩意....... 这就是典型的SpringBoot使用后遗症SpringBoot降低了使用难度但是从某种程度来说也让初级的程序员变得更加小白把实现原理都隐藏起来了而我们只管用一旦涉及扩展就束手无策。
那当然我们今天不讲SpringBoot,我们今天用贴近SpringBoot的方式来讲SpringMVC。也就是零配置零xml的放式来说明SpringMVC的原理 此方式作为我们本文重点介绍也是很多人缺失的一种方式 其实早在Spring3就已经提供 只不过我们直到SpringBoot才使用该方式进行自动配置 这也是很多人从xml调到SpringBoot不适应的原因 因为你缺失了这个版本。 所以我们以这种方式作为源码切入点既可以理解到XML的方式又能兼顾到SpringBoot的方式 。 2、零配置SpringMVC实现方式
那没有配置就需要省略掉web.xml 怎么省略呢
在Servlet3.0提供的规范文档中可以找到2种方式
注解的方式
WebServletWebFilterWebListener
但是这种方式不利于扩展 并且如果编写在jar包中tomcat是无法感知到的。
SPI的方式
在Serlvet3-1的规范手册中就提供了一种更加易于扩展可用于共享库可插拔的一种方式参见8.2.4 也就是让你在应用META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer ——即SPI规范
SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口 不要被这个名字唬到了 其实很好理解的一个东西
其实就是根据Servlet厂商服务提供商提供要求的一个接口 在固定的目录META-INF/services放上以接口全类名 为命名的文件 文件中放入接口的实现的全类名该类由我们自己实现按照这种约定的方式即SPI规范服务提供商会调用文件中实现类的方法 从而完成扩展。
ok 那我们知道了SPI是什么我们是不是可以在Web应用中在Servlet的SPI放入对应的接口文件 放入实现类 通过ServletContext就可以动态注册三大组件以Servlet注册为例
public class TulingSpringServletContainerInitializer extends SpringServletContainerInitializer {Overridepublic void onStartup(SetClass? webAppInitializerClasses, ServletContext servletContext) throws ServletException {// 通过servletContext动态添加ServletservletContext.addServlet(spiServlet, new HttpServlet() {Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write(spiServlet--doGet);}}).addMapping(/spiServlet.do);} 当然在SpringMVC中 这个接口文件和实现类都把我们实现好了甚至ContextLoaderListener和DispatcherServlet都帮我们注册好了我们只要让他生效来看看他是怎么做的
3、实现基于SPI规范的SpringMVC
TulingStarterInitializer
此类继承AbstractAnnotationConfigDispatcherServletInitializer 这是个啥 待会我们讲原理来介绍getRootConfigClasses 提供父容器的配置类getServletConfigClasses 提供子容器的配置类getServletMappings 设置DispatcherServlet的映射
public class TulingStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*** 方法实现说明:IOC 父容器的启动类* author:xsls* date:2019/7/31 22:12*/Overrideprotected Class?[] getRootConfigClasses() {return new Class[]{RootConfig.class};}/*** 方法实现说明 IOC子容器配置 web容器配置* author:xsls* date:2019/7/31 22:12*/Overrideprotected Class?[] getServletConfigClasses() {return new Class[]{WebAppConfig.class};}/*** 方法实现说明* author:xsls* return: 我们前端控制器DispatcherServlet的拦截路径* exception:* date:2019/7/31 22:16*/Overrideprotected String[] getServletMappings() {return new String[]{/};
RootConfig
父容器的配置类 以前的spring.xml扫描的包排除掉Controller
Configuration
ComponentScan(basePackages com.tuling,excludeFilters {ComponentScan.Filter(type FilterType.ANNOTATION,value{RestController.class,Controller.class}),ComponentScan.Filter(type ASSIGNABLE_TYPE,value WebAppConfig.class ),
})
public class RootConfig {}
WebAppConfig
子容器的配置类 以前的spring-mvc.xml扫描的包包含掉Controller
Configuration
ComponentScan(basePackages {com.tuling},includeFilters {ComponentScan.Filter(type FilterType.ANNOTATION,value {RestController.class, Controller.class})
},useDefaultFilters false)
EnableWebMvc // ≈mvc:annotation-driven/
public class WebAppConfig implements WebMvcConfigurer{/*** 配置拦截器* return*/Beanpublic TulingInterceptor tulingInterceptor() {return new TulingInterceptor();}/*** 文件上传下载的组件* return*/Beanpublic MultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver new CommonsMultipartResolver();multipartResolver.setDefaultEncoding(UTF-8);multipartResolver.setMaxUploadSize(1024*1024*10);return multipartResolver;}/*** 注册处理国际化资源的组件* return*/
/* Beanpublic AcceptHeaderLocaleResolver localeResolver() {AcceptHeaderLocaleResolver acceptHeaderLocaleResolver new AcceptHeaderLocaleResolver();return acceptHeaderLocaleResolver;}*/Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tulingInterceptor()).addPathPatterns(/*);}/*** 方法实现说明:配置试图解析器* author:xsls* exception:* date:2019/8/6 16:23*/Beanpublic InternalResourceViewResolver internalResourceViewResolver() {InternalResourceViewResolver viewResolver new InternalResourceViewResolver();viewResolver.setSuffix(.jsp);viewResolver.setPrefix(/WEB-INF/jsp/);return viewResolver;}Overridepublic void configureMessageConverters(ListHttpMessageConverter? converters) {converters.add(new MappingJackson2HttpMessageConverter());}
自己去添加个Controller进行测试
OK 现在可以访问你的SpringMVC了
4、SPI的方式SpringMVC启动原理
接着我们来看看SPI方式的原理是什么
SpringMVC 大致可以分为 启动 和请求 2大部分 所以我们本文先研究启动部分
流程图 源码流程
外置Tomcat启动的时候通过SPI 找到我们应用中的/META-INF/service/javax.servlet.ServletContainerInitializer 调用SpringServletContainerInitializer.onStartUp() 调用onStartUp()前会先找到HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类传入到OnStartup的webAppInitializerClasses参数中并传入Servlet上下文对象。重点关注这组类他们组成了父子容器 找到所有WebApplicationInitializer的实现类后 不是接口、不是抽象则通过反射进行实例化所以你会发现内部实现类都是抽象的你想让其起作用我们必须添加一个自定义实现类在下文提供我的自定义实现类调用所有上一步实例化后的对象的onStartup方法 1. 首先来到AbstractDispatcherServletInitializer#onStartup再执行super.onStartup(servletContext);
Override
public void onStartup(ServletContext servletContext) throws ServletException {//实例化我们的spring root上下文super.onStartup(servletContext);//注册我们的DispatcherServlet 创建我们spring web 上下文对象registerDispatcherServlet(servletContext); 创建父容器——ContextLoaderListener
2.父类AbstractContextLoaderInitializer#onStartup执行registerContextLoaderListener(servletContext);
createRootApplicationContext()该方法中会创建父容器 该方法是抽象方法实现类是AbstractAnnotationConfigDispatcherServletInitializer 调用getRootConfigClasses();方法获取父容器配置类此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 创建父容器注册配置类 会创建ContextLoaderListener并通过ServletContext注册 看完大家是不是感觉跟我们XML的配置ContextLoaderListener对上了 创建子容器——DispatcherServlet
3.回到AbstractDispatcherServletInitializer#onStartup再执行registerDispatcherServlet(servletContext); registerDispatcherServlet方法说明
调用createServletApplicationContext创建子容器 该方法是抽象方法实现类是AbstractAnnotationConfigDispatcherServletInitializer 创建子容器下图很明显不多介绍调用抽象方法getServletConfigClasses();获得配置类此抽象方法在我们自定义的子类中实现提供我们自定义的配置类 配置类除了可以通过ApplicationContext()构造函数的方式传入 也可以通过这种方式动态添加不知道了吧~ 调用createDispatcherServlet(servletAppContext);创建DispatcherServlet设置启动时加载registration.setLoadOnStartup(1);调用抽象方法设置映射路径getServletMappings()此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径
看完大家是不是感觉跟我们XML的配置DispatcherServlet对上了 4. 初始化ContextLoaderListener ContextLoaderListener加载过程比较简单
外置tomcat会帮我们调用ContextLoaderListener#contextInitialized 进行初始化
xml的方式下会判断容器为空时创建父容器在里面会调用父容器的refresh方法加载将父容器存入到Servlet域中供子容器使用 5. 初始化DispatcherServlet 可以看到流程比ContextLoaderListener流程更多
外置tomcat会帮我们调用DispatcherServlet#init() 进行初始化---重点关注initWebApplicationContext方法
getWebApplicationContext(getServletContext())获得父容器从之前的Servlet域中拿到cwac.setParent(rootContext);给子容器设置父容器调用configureAndRefreshWebApplicationContext(cwac); 注册一个监听器该监听会初始化springmvc所需信息 ContextRefreshedEvent可以看到该监听器监听的是容器refreshed事件 会在finishRefresh中发布刷新容器 当执行refresh 即加载ioc容器 完了会调用finishRefresh():
publishEvent(new ContextRefreshedEvent(this));发布ContextRefreshedEvent事件触发上面的ContextRefreshListener监听器
----FrameworkServlet.this.onApplicationEvent(event);
--------onRefresh(event.getApplicationContext());
--------------initStrategies(context);
protected void initStrategies(ApplicationContext context) {//初始化我们web上下文对象的 用于文件上传下载的解析器对象initMultipartResolver(context);//初始化我们web上下文对象用于处理国际化资源的initLocaleResolver(context);//主题解析器对象初始化initThemeResolver(context);//初始化我们的HandlerMappinginitHandlerMappings(context);//实例化我们的HandlerAdaptersinitHandlerAdapters(context);//实例化我们处理器异常解析器对象initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);//给DispatcherSerlvet的ViewResolvers处理器initViewResolvers(context);initFlashMapManager(context);
这里面的每一个方法不用太细看 就是给SpringMVC准备初始化的数据 为后续SpringMVC处理请求做准备
基本都是从容器中拿到已经配置的BeanRequestMappingHandlerMapping、RequestMappingHandlerAdapter、HandlerExceptionResolver 放到dispatcherServlet中做准备: ...
但是这些Bean又是从哪来的呢 来来来 回到我们的WebAppConfig
我们使用的一个EnableWebMvc
导入了DelegatingWebMvcConfigurationImport(DelegatingWebMvcConfiguration.class)DelegatingWebMvcConfiguration的父类就配置了这些Bean而且我告诉你SpringBoot也是用的这种方式 总结
Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象 同时创建父子容器 分别创建在ContextLoaderListener初始化时创建父容器设置配置类在DispatcherServlet初始化时创建子容器 即2个ApplicationContext实例设置配置类Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法 执行容器refresh进行加载在子容器加载时 创建SpringMVC所需的Bean和预准备的数据(通过配置类EnableWebMvc配置DelegatingWebMvcConfiguration——可实现WebMvcConfigurer进行定制扩展 RequestMappingHandlerMapping它会处理RequestMapping 注解RequestMappingHandlerAdapter则是处理请求的适配器确定调用哪个类的哪个方法并且构造方法参数返回值。HandlerExceptionResolver 错误视图解析器addDefaultHttpMessageConverters 添加默认的消息转换器解析json、解析xml等....子容器需要注入父容器的Bean时比如Controller中需要Autowired Service的Bean; 会先从子容器中找没找到会去父容器中找 详情见AbstractBeanFactory#doGetBean方法 /** * 一般情况下,只有Spring 和SpringMvc整合的时才会有父子容器的概念, * 作用* 比如我们的Controller中注入Service的时候发现我们依赖的是一个引用对象那么他就会调用getBean去把service找出来* 但是当前所在的容器是web子容器那么就会在这里的 先去父容器找*/
BeanFactory parentBeanFactory getParentBeanFactory();
//若存在父工厂,且当前的bean工厂不存在当前的bean定义,那么bean定义是存在于父beanFacotry中
if (parentBeanFactory ! null !containsBeanDefinition(beanName)) {//获取bean的原始名称String nameToLookup originalBeanName(name);//若为 AbstractBeanFactory 类型委托父类处理if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args ! null) {// 委托给构造函数 getBean() 处理return (T) parentBeanFactory.getBean(nameToLookup, args);}else {// 没有 args委托给标准的 getBean() 处理return parentBeanFactory.getBean(nameToLookup, requiredType);}