菜瓜:今天聽到個名詞「父子容器」,百度了一下,感受概念有點空洞,這是什麼核武器?html
水稻:你說的是SpringMvc和Spring吧,其實只是一個概念而已,用來將兩個容器作隔離,起到解耦的做用,其中子容器能夠拿到父容器的bean,父容器拿不到子容器的。可是SpringBoot出來以後這個概念基本就被淡化掉,沒有太大意義,SpringBoot中只有一個容器了。java
菜瓜:能不能給個demo?web
水稻:能夠。因爲如今SpringBoot已經大行其道,Mvc你可能接觸的少,甚至沒接觸過。spring
package com.vip.qc.mvc; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleListener; import org.apache.catalina.startup.Tomcat; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /** * 參考: * https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet * <p> * 嵌入tomcat,由Tomcat發起對Spring容器的初始化調用過程 * <p> * - 啓動過程 * * - Servlet規範,Servlet容器在啓動以後會SPI加載META-INF/services目錄下的實現類並調用其onStartup方法 * * - Spring遵循規範實現了ServletContainerInitializer接口。該接口在執行時會收集WebApplicationInitializer接口實現類並循環調用其onStartup方法 * * - 其中AbstractDispatcherServletInitializer * * * - 將spring上下文放入ContextLoaderListener監聽器,該監聽會發起對refresh方法的調用 * * * - 註冊dispatcherServlet,後續會由tomcat調用HttpServletBean的init方法,完成子容器的refresh調用 * * * * @author QuCheng on 2020/6/28. */ public class SpringWebStart { public static void main(String[] args) { Tomcat tomcat = new Tomcat(); try {
// 此處須要取一個目錄 Context context = tomcat.addContext("/", System.getProperty("java.io.tmp")); context.addLifecycleListener((LifecycleListener) Class.forName(tomcat.getHost().getConfigClass()).newInstance()); tomcat.setPort(8081); tomcat.start(); tomcat.getServer().await(); } catch (LifecycleException | ClassNotFoundException | IllegalAccessException | InstantiationException e) { e.printStackTrace(); } } static class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { private final static String PACKAGE_PATH = "com.vip.qc.mvc.controller"; private final static String PACKAGE_PATH_CHILD = "com.vip.qc.mvc.service"; @Override protected String[] getServletMappings() { return new String[]{"/"}; } @Override protected Class<?>[] getRootConfigClasses() { // spring 父容器 return new Class[]{AppConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { // servlet 子容器 return new Class[]{ServletConfig.class}; } @Configuration @ComponentScan(value = PACKAGE_PATH_CHILD, excludeFilters = @ComponentScan.Filter(classes = Controller.class)) static class AppConfig { } @Configuration @ComponentScan(value = PACKAGE_PATH, includeFilters = @ComponentScan.Filter(classes = Controller.class)) static class ServletConfig { } } }
package com.vip.qc.mvc.controller; import com.vip.qc.mvc.service.ServiceChild; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; /** * @author QuCheng on 2020/6/28. */ @Controller public class ControllerT implements ApplicationContextAware { @Resource private ServiceChild child; @RequestMapping("/hello") @ResponseBody public String containter() { child.getParent(); System.out.println("parentContainer"); return "containter"; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("子容器" + applicationContext); System.out.println("子容器中獲取父容器bean" + applicationContext.getBean(ServiceChild.class)); } } package com.vip.qc.mvc.service; import com.vip.qc.mvc.controller.ControllerT; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Service; /** * @author QuCheng on 2020/6/28. */ @Service public class ServiceChild implements ApplicationContextAware { // @Resource private ControllerT controllerT; public void getParent() { System.out.println(controllerT); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("父容器" + applicationContext); try { System.out.println("父容器中獲取子容器bean" + applicationContext.getBean(ControllerT.class)); } catch (NoSuchBeanDefinitionException e) { System.out.println("找不到子容器的bean"); } } }
// 調用SpringWebStart的main方法啓動-會有以下打印
父容器Root WebApplicationContext, started on Sun Jun 28 22:03:52 CST 2020
找不到子容器的bean
子容器WebApplicationContext for namespace 'dispatcher-servlet', started on Sun Jun 28 22:03:58 CST 2020, parent: Root WebApplicationContext
子容器中獲取父容器beancom.vip.qc.mvc.service.ServiceChild@4acfc43a
菜瓜:嗯,效果看到了,能不能講一下啓動過程apache
水稻:稍等,我去下載源碼。上面代碼演示中已經提早說明了,父子容器的加載是Tomcat依據Servlet規範發起調用完成的瀏覽器
@Override public void onStartup(ServletContext servletContext) throws ServletException { //① 建立Spring父容器上下文-對象放入ContextLoadListener,後續調起完成初始化, super.onStartup(servletContext); //② 建立DispatcherServlet對象,後續會由tomcat調用其init方法,完成子容器的初始化工做 registerDispatcherServlet(servletContext); } // ①進來 protected void registerContextLoaderListener(ServletContext servletContext) { // 此處會回調咱們啓動類的getRootConfigClasses()方法 - 父容器配置 WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); istener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } else { logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context"); } } // ②進來 protected void registerDispatcherServlet(ServletContext servletContext) { 。。。 // 此處會回調咱們啓動類的getServletConfigClasses()方法 - 子容器配置 WebApplicationContext servletAppContext = createServletApplicationContext(); 。。。 // 初始化的dispatcherServlet,會加入Tomcat容器中-後續調用 // FrameworkServlet#initServletBean()會完成上下文初始化工做 FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); 。。。 }
菜瓜:這樣容器就能夠用了嗎?tomcat
水稻:是的,這樣就能夠直接在瀏覽器上面訪問http://localhost:8081/hello,不過這是一個最簡陋的web項目mvc
菜瓜:懂了,最簡陋是什麼意思app
水稻:若是咱們想加一些常見的Web功能,譬如說攔截器,過濾器啥的。能夠經過@EnableWebMvc註解自定義一些功能ide
package com.vip.qc.mvc; import com.vip.qc.mvc.interceptor.MyInterceptor1; import com.vip.qc.mvc.interceptor.MyInterceptor2; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * @author QuCheng on 2020/6/28. */ @Configuration @EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { @Resource private MyInterceptor1 interceptor1; @Resource private MyInterceptor2 interceptor2; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor1).addPathPatterns("/interceptor/**"); registry.addInterceptor(interceptor2).addPathPatterns("/interceptor/**"); } } package com.vip.qc.mvc.interceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author QuCheng on 2020/6/28. */ @Configuration public class MyInterceptor1 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("嘻嘻 我是攔截器1 pre"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("嘻嘻 我是攔截器1 post"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("嘻嘻 我是攔截器1 after"); } } package com.vip.qc.mvc.interceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author QuCheng on 2020/6/28. */ @Configuration public class MyInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("嘻嘻 我是攔截器2 pre"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("嘻嘻 我是攔截器2 post"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("嘻嘻 我是攔截器2 after"); } }
菜瓜:我知道,這裏還有個Mvc請求調用流程和這個攔截器有關。並且這個攔截器不是MethodInterceptor(切面)
水稻:沒錯,說到這裏順便複習一下Mvc的請求過程
1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 2 。。。 3 processedRequest = checkMultipart(request); 4 multipartRequestParsed = (processedRequest != request); 5 // 1.返回一個持有methodHandler(按照URL匹配得出的被調用bean對象以及目標方法)調用鏈(攔截器鏈)對象 6 mappedHandler = getHandler(processedRequest); 7 。。。 8 // 2.按照咱們如今寫代碼的方式,只會用到HandlerMethod,其餘三種基本不會用 9 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 10 。。。 11 // 3.前置過濾器 - 順序調用 12 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 13 return; 14 } 15 // 4.Actually invoke the handler. 16 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 17 。。。 18 applyDefaultViewName(processedRequest, mv); 19 // 5.後置過濾器 - 逆序調用 20 mappedHandler.applyPostHandle(processedRequest, response, mv); 21 。。。 22 // 6.處理試圖 - 內部render 23 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 24 } 25 catch (Exception ex) { 26 // 異常處理 27 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 28 } 29 // 異常處理 30 catch (Throwable err) { 31 triggerAfterCompletion(processedRequest, response, mappedHandler, 32 new NestedServletException("Handler processing failed", err)); 33 } 34 。。。
菜瓜:這個以前看過很多,百度一大堆,不過仍是源碼親切
總結: