日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Spring Web零xml配置原理以及父子容器關系詳解

瀏覽:156日期:2023-08-18 14:55:12

前言

在使用Spring和SpringMVC的老版本進行開發時,我們需要配置很多的xml文件,非常的繁瑣,總是讓用戶自行選擇配置也是非常不好的。基于約定大于配置的規定,Spring提供了很多注解幫助我們簡化了大量的xml配置;但是在使用SpringMVC時,我們還會使用到WEB-INF/web.xml,但實際上我們是完全可以使用Java類來取代xml配置的,這也是后來SpringBoott的實現原理。本篇就來看看Spring是如何實現完全的零XML配置。

正文

先來看一下原始的web.xml配置:

<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd' ><web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value> <!--加載spring配置--> classpath:spring.xml </param-value> </context-param> <context-param> <param-name>webAppRootKey</param-name> <param-value>ServicePlatform.root</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> <!--<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>--> </listener> <servlet> <servlet-name>spring-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--springmvc的配置文件--> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-dispatcher.xml</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping></web-app>

這里各個配置的作用簡單說下,context-param是加載我們主的sping.xml配置,比如一些bean的配置和開啟注解掃描等;listener是配置監聽器,Tomcat啟動會觸發監聽器調用;servlet則是配置我們自定義的Servlet實現,比如DispatcherServlet。還有其它很多配置就不一一說明了,在這里主要看到記住context-param和servlet配置,這是SpringIOC父子容器的體現。

在之前的I文章中講過IOC容器是以父子關系組織的,但估計大部分人都不能理解,除了看到復雜的繼承體系,并沒有看到父容器作用的體現,稍后來分析。

了解了配置,我們就需要思考如何替換掉這些繁瑣的配置。實際上Tomcat提供了一個規范,有一個ServletContainerInitializer接口:

public interface ServletContainerInitializer { void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;}

Tomcat啟動時會調用該接口實現類的onStartup方法,這個方法有兩個參數,第二個不用說,主要是第一個參數什么?從哪里來?另外我們自定義的實現類又怎么讓Tomcat調用呢?

首先解答最后一個問題,這里也是利用SPI來實現的,因此我們實現了該接口后,還需要在META-INF.services下配置。其次,這里傳入的第一個參數也是我們自定義的擴展接口的實現類,我們可以通過我們自定義的接口實現很多需要在啟動時做的事,比如加載Servlet,但是Tomcat又是怎么知道我們自定義的接口是哪個呢?

這就需要用到@HandlesTypes注解,該注解就是標注在ServletContainerInitializer的實現類上,其值就是我們擴展的接口,這樣Tomcat就知道需要傳入哪個接口實現類到這個onStartup方法了。

來看一個簡單的實現:

@HandlesTypes(LoadServlet.class)public class MyServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException { Iterator var4; if (set != null) { var4 = set.iterator(); while (var4.hasNext()) {Class<?> clazz = (Class<?>) var4.next();if (!clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()) && LoadServlet.class.isAssignableFrom(clazz)) { try { ((LoadServlet) clazz.newInstance()).loadOnstarp(servletContext); } catch (Exception e) { e.printStackTrace(); }} } } }}public interface LoadServlet { void loadOnstarp(ServletContext servletContext);}public class LoadServletImpl implements LoadServlet { @Override public void loadOnstarp(ServletContext servletContext) { ServletRegistration.Dynamic initServlet = servletContext.addServlet('initServlet', 'org.springframework.web.servlet.DispatcherServlet'); initServlet.setLoadOnStartup(1); initServlet.addMapping('/init'); }}

這就是Tomcat給我們提供的規范,通過這個規范我們就能實現Spring的零xml配置啟動,直接來看Spring是如何做的。根據上面所說我們可以在spring-web工程下找到META-INF/services/javax.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 = new LinkedList<>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... 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); } }}

核心的實現就是WebApplicationInitializer,先看看其繼承體系

Spring Web零xml配置原理以及父子容器關系詳解

AbstractReactiveWebInitializer不用管,主要看另外一邊,但是都是抽象類,也就是說真的實例也是由我們自己實現,但需要我們實現什么呢?我們一般直接繼承AbstractAnnotationConfigDispatcherServletInitializer類,有四個抽象方法需要我們實現:

//父容器 @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[]{SpringContainer.class}; } //SpringMVC配置子容器 @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[]{MvcContainer.class}; } //獲取DispatcherServlet的映射信息 @Override protected String[] getServletMappings() { return new String[]{'/'}; } // filter配置 @Override protected Filter[] getServletFilters() { MyFilter myFilter = new MyFilter(); CorsFilter corsFilter = new CorsFilter(); return new Filter[]{myFilter,corsFilter}; }

這里主要注意getRootConfigClasses和getServletConfigClasses方法,分別加載父、子容器:

@ComponentScan(value = 'com.dark',excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})})public class SpringContainer {}@ComponentScan(value = 'com.dark',includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})},useDefaultFilters = false)public class MvcContainer {}

看到這兩個類上的注解應該不陌生了吧,父容器掃描裝載了所有不帶@Controller注解的類,子容器則相反,但需要對象時首先從當前容器中找,如果沒有則從父容器中獲取,為什么要這么設計呢?

直接放到一個容器中不行么?先思考下, 稍后解答。回到onStartup方法中,直接回調用到AbstractDispatcherServletInitializer類:

public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); //注冊DispatcherServlet registerDispatcherServlet(servletContext); }

先是調用父類:

public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { //創建spring上下文,注冊了SpringContainer WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { //創建監聽器 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } }

然后調用createRootApplicationContext創建父容器:

protected WebApplicationContext createRootApplicationContext() { Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(configClasses); return context; } else { return null; } }

可以看到就是創建了一個AnnotationConfigWebApplicationContext對象,并將我們的配置類SpringContainer注冊了進去。接著創建Tomcat啟動加載監聽器ContextLoaderListener,該監聽器有一個contextInitialized方法,會在Tomcat啟動時調用。

public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } */ public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { long startTime = System.currentTimeMillis(); try { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } return this.context; } }

可以看到就是去初始化容器,這個和之前分析xml解析是一樣的,主要注意這里封裝了ServletContext對象,并將父容器設置到了該對象中。

父容器創建完成后自然就是子容器的創建,來到registerDispatcherServlet方法:

protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); Assert.hasLength(servletName, 'getServletName() must not return null or empty'); //創建springmvc的上下文,注冊了MvcContainer類 WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, 'createServletApplicationContext() must not return null'); //創建DispatcherServlet 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.'); } /* * 如果該元素的值為負數或者沒有設置,則容器會當Servlet被請求時再加載。 如果值為正整數或者0時,表示容器在應用啟動時就加載并初始化這個servlet, 值越小,servlet的優先級越高,就越先被加載 * */ 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); } protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); Class<?>[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); } return context; }

這里也是創建了一個AnnotationConfigWebApplicationContext對象,不同的只是這里注冊的配置類就是我們的Servlet配置了。然后創建了DispatcherServlet對象,并將上下文對象設置了進去。

看到這你可能會疑惑,既然父子容器創建的都是相同類的對象,何來的父子容器之說?

別急,這個在初始化該上文時就明白了。但是這里的初始化入口在哪呢?沒有看到任何監聽器的創建和調用。

實際上這里的上下文對象初始化是在Servlet初始化時實現的,即init方法,直接來到HttpServletBean的init方法(分析SpringMVC源碼時講過):

public final void init() throws ServletException { ...省略 // Let subclasses do whatever initialization they like. initServletBean(); } protected final void initServletBean() throws ServletException { try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } } protected WebApplicationContext initWebApplicationContext() { //這里會從servletContext中獲取到父容器,就是通過監聽器加載的容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it 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) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }

看到這里想你也應該明白了,首先從ServletContext中拿到父容器,然后設置到當前容器的parent中,實現了父子容器的組織,而這樣設計好處我想也是很清楚的,子容器目前裝載的都是MVC的配置和Bean,簡單點說就是Controller,父容器中都是Service,Controller是依賴于Service的,如果不構建這樣的層級關系并優先實例化父容器,你怎么實現Controller層的依賴注入成功呢?

總結

本篇結合之前的文章,分析了SpringMVC零XML配置的實現原理,也補充了之前未分析到父子容器關系,讓我們能從細節上更加全面的理解SpringIOC的實現原理,相信看完本篇對于SpringBoot的實現你也會有自己的想法。希望能給大家一個參考,也希望大家多多支持好吧啦網。

標簽: Spring
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
好吊一区二区三区| 综合国产在线| 亚洲精品美女| 亚洲免费福利一区| 亚洲日本欧美| 欧美一级二区| 国产欧美亚洲精品a| 日韩欧美中文在线观看| 亚洲精品在线a| 日韩精品视频中文字幕| 欧美精品观看| 日韩福利视频网| 欧美亚洲一区二区三区| 免费在线亚洲| 日韩在线观看| 好吊一区二区三区| 一区二区国产在线| 国产伦精品一区二区三区视频| 麻豆精品新av中文字幕| 国产成人精品999在线观看| 久久uomeier| 欧美日韩激情| 在线精品亚洲| 国产伦理一区| 蜜桃成人精品| 日韩另类视频| 日韩午夜av在线| 先锋影音国产一区| 国产探花一区| 精品一区二区三区在线观看视频| sm捆绑调教国产免费网站在线观看| 亚洲91久久| 先锋影音国产一区| 国产亚洲久久| 日本在线啊啊| 制服诱惑一区二区| 欧美一区网站| 综合日韩av| 亚洲专区欧美专区| 国产精品a级| 国产精品91一区二区三区| 亚洲欧洲午夜| 国产亚洲第一伦理第一区| 首页国产精品| 日韩中文欧美在线| 免费亚洲婷婷| 午夜欧美精品| 国产欧美亚洲精品a| 91精品国产乱码久久久久久久| 久热精品在线| 日本精品黄色| 亚洲一区二区三区高清| 久久99影视| 黄色免费成人| 国产精品视频一区二区三区四蜜臂| 三级小说欧洲区亚洲区| 中文视频一区| 欧美aa一级| 国产亚洲久久| 樱桃成人精品视频在线播放| 国产日韩欧美中文在线| 国产在线|日韩| 日韩精品亚洲专区在线观看| 中文在线免费视频| 综合激情网...| 成人日韩在线| 国产日韩免费| 久久都是精品| 色88888久久久久久影院| 欧美综合社区国产| av不卡在线看| 久久女人天堂| 亚洲精品自拍| 精精国产xxxx视频在线野外| 日韩av资源网| 一区在线免费观看| 久草免费在线视频| 国产欧美一区| 中文一区一区三区免费在线观 | 亚洲一级少妇| 亚洲精品乱码| 九九综合在线| 国产精品久久久久久久久妇女| 日韩一区二区三区在线看| 亚洲福利免费| 精品国产a一区二区三区v免费| 在线观看亚洲精品福利片| 日韩视频网站在线观看| 国产精品亲子伦av一区二区三区| 亚洲一卡久久| 亚洲视频综合| 国产精品a级| 人人精品久久| 综合国产视频| 另类亚洲自拍| 国产韩日影视精品| 樱桃视频成人在线观看| 国产欧美日韩视频在线 | 久久99蜜桃| 日本欧美久久久久免费播放网| 午夜欧美精品| 欧美91视频| 成人羞羞在线观看网站| 欧美激情视频一区二区三区免费 | 久久天堂精品| 亚洲深夜福利在线观看| 国产精品88久久久久久| 视频二区不卡| 快播电影网址老女人久久| 久久不卡日韩美女| 国产乱人伦精品一区| 97se亚洲| 久久国产婷婷国产香蕉| 久久精品99国产精品日本| 日韩在线观看中文字幕| 爽好久久久欧美精品| 不卡在线一区二区| 好吊视频一区二区三区四区| 久久精品动漫| 宅男在线一区| 五月精品视频| 国产一区日韩一区| 午夜久久免费观看| 亚洲激情中文| 在线成人动漫av| av一区二区高清| 欧美日韩视频一区二区三区| 亚洲黄页一区| 亚洲免费婷婷| 蜜桃视频一区二区三区| 日韩一区精品| 国产剧情在线观看一区| 久久gogo国模啪啪裸体| 精品五月天堂| 日韩欧美精品综合| 久久影视一区| 99在线观看免费视频精品观看| 国产精品美女| 日韩欧美激情| 亚洲精品观看| 欧美国产中文高清| 国产a久久精品一区二区三区| 91亚洲成人| 久久一级电影| 中文一区一区三区免费在线观| 日韩国产精品久久久| 久久gogo国模啪啪裸体| 国产白浆在线免费观看| 在线视频观看日韩| 蜜桃91丨九色丨蝌蚪91桃色| 青青草91视频| 91亚洲国产| 欧美日韩精品一本二本三本| 综合激情在线| 欧美一级全黄| 黑森林国产精品av| 亚洲激情欧美| 国产欧美久久一区二区三区| 国产成人免费视频网站视频社区| 欧美日韩国产v| 日av在线不卡| 国产精品2023| 久久精品国语| 蜜桃av一区二区在线观看| 国产剧情一区| 亚洲午夜精品久久久久久app| 丝袜a∨在线一区二区三区不卡| 国产精品一页| 欧美日韩一区二区综合| 视频一区在线播放| 久久99高清| 欧美网站在线| 久久99久久人婷婷精品综合| 99精品综合| 欧美日韩a区| 99视频精品全部免费在线视频| 亚洲精品字幕| 久久毛片亚洲| 中文字幕乱码亚洲无线精品一区| 国产福利一区二区精品秒拍| 激情欧美国产欧美| 清纯唯美亚洲综合一区| 日韩三区免费| 日本亚洲不卡| 国产人成精品一区二区三| 91欧美国产| 日本在线观看不卡视频| 国产欧洲在线| 男女性色大片免费观看一区二区 | 免费看av不卡| 亚洲欧洲日韩精品在线| 日韩久久精品| 综合激情婷婷| 日韩在线短视频| 国产欧美二区| 在线亚洲国产精品网站| 国产一区二区三区精品在线观看| 亚洲综合电影一区二区三区| 国产精品二区不卡| 欧美一区二区三区久久|