springmvc与spring的整合简单介绍
本文源码基于spring-framework-5.3.10。mvc是spring源码中的一个子模块!本文重点是无xml方式的整合springmvc。Spring整合SpringMVC唯一的体现就是父子容器。
springmvc的父子容器
springmvc的父子容器.png
父容器(Spring)管理Service、Dao层的Bean,子容器(SpringMVC)管理Controller的Bean。子容器可以访问父容器的Bean,父容器无法访问子容器的Bean。
spring整合springmvc的思路(xml方式)
一般会在web.xml中配置下面的代码。核心是一个ContextLoaderListener(加载spring容器)、一个DispatcherServlet(加载springmvc容器)。
!--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/servletservlet-mappingservlet-namedispatcherServlet/servlet-nameurl-pattern//url-pattern/servlet-mapping
零配置SpringMVC实现方式的基础:SPI
SPI:我们叫他服务接口扩展,(ServiceProviderInterface)直译服务提供商接口。其实就是根据Servlet厂商(服务提供商)提供要求的一个接口,在固定的目录(META-INF/services)放上以接口全类名为命名的文件,文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法,从而完成扩展。在往简单说:定义一个借口,把他的实现的全限定类名配置到一个配置文件,然后通过JDK8提供的方法进行调用。
publicclassTest{publicstaticvoidmain(String[]args){//得到你配置的所有实现了IUserDao的接口ServiceLoaderIUserDaodaos=ServiceLoader.load(IUserDao.class);//循环调用每个接口for(IUserDaodao:daos){dao.save();}}}
springmvc与spi的关系:在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式,参见8.2.4:
Serlvet3-1的规范8.2.4.png简单说就是放一个SPI规范:你在应用META-INF/services路径下放一个javax.servlet.ServletContainerInitailizer,里面配置具体的实现的全限定类名!容器(Tomcat)就会去调用
零配置SpringMVC实现方式的基础:HandlesTypes
通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup方法作为参数传入。在SpringServletContainerInitializer的类上配置了
HandlesTypes(WebApplicationInitializer.class)tomcat会自动找到程序中所有的实现了WebApplicationInitializer接口的实现类。然后把找到的所有的WebApplicationInitializer实现类传入onStartUp方法的webAppInitializerClasses参数上!SPI的方式SpringMVC启动原理
外置Tomcat启动的时候通过SPI的方式找到我们应用中的/META-INF/service/javax.servlet.ServletContainerInitializer。里面会配置org.springframework.web.SpringServletContainerInitializer。所以会调用SpringServletContainerInitializer.onStartUp()方法。调用onStartUp()前会先找到
HandlesTypes(WebApplicationInitializer.class)所有实现了WebApplicationInitializer的类,传入到OnStartup的webAppInitializerClasses参数中,并传入Servlet上下文对象。publicvoidonStartup(
NullableSetClass?webAppInitializerClasses,ServletContextservletContext)throwsServletException{//得到一个空的数组,里面存放实例化好的WebApplicationInitializer对象ListWebApplicationInitializerinitializers=Collections.emptyList();//找到有WebApplicationInitializer的实现类if(webAppInitializerClasses!=null){//创建一个有长度的数组,长度和找到的WebApplicationInitializer实现类数量一致initializers=newArrayList(webAppInitializerClasses.size());for(Class?waiClass:webAppInitializerClasses){//接口和抽象类servlet容器也会给我们,但是我们不要//排除接口和容器if(!waiClass.isInterface()!Modifier.isAbstract(waiClass.getModifiers())WebApplicationInitializer.class.isAssignableFrom(waiClass)){try{//实例化,然后添加到集合中initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());}catch(Throwableex){thrownewServletException("FailedtoinstantiateWebApplicationInitializerclass",ex);}}}}//没有实例化的WebApplicationInitializer对象,直接返回。。if(initializers.isEmpty()){servletContext.log("NoSpringWebApplicationInitializertypesdetectedonclasspath");return;}servletContext.log(initializers.size()+"SpringWebApplicationInitializersdetectedonclasspath");AnnotationAwareOrderComparator.sort(initializers);//调用initializer.onStartup进行扩展for(WebApplicationInitializerinitializer:initializers){initializer.onStartup(servletContext);}}spring整合springmvc的思路(无xml方式)
直接看默认是没有一个类(不是接口,不是抽象类的)去实现了WebApplicationInitializer接口,所以需要我们自己去实现
/***ZhangweiStarterInitializer需要在META-INF/services/javax.servlet.ServletContainerInitailizer进行配置!*AbstractAnnotationConfigDispatcherServletInitializer他的父类是WebApplicationInitializer*/publicclassZhangweiStarterInitializerextendsAbstractAnnotationConfigDispatcherServletInitializer{/***方法实现说明:IOC父容器的启动类*/
OverrideprotectedClass?[]getRootConfigClasses(){returnnewClass[]{RootConfig.class};}/***方法实现说明IOC子容器配置web容器配置*/OverrideprotectedClass?[]getServletConfigClasses(){returnnewClass[]{WebAppConfig.class};}/***方法实现说明*return:我们前端控制器DispatcherServlet的拦截路径*/OverrideprotectedString[]getServletMappings(){returnnewString[]{"/"};}}/***IOC根容器,不扫描Controller的注解!*/ConfigurationComponentScan(basePackages="