DispatchServlet parsing of Spring MVC
Spring MVC sequence diagram
The parent class of DispatchServlet is FrameworkServlet, and the parent class of FrameworkServlet is HttpServletBean
Core process:
-
Find the init() method of DispatchServlet and locate the init() method of the parent HttpServletBean of its parent class
- Locate the location of the configuration file and load the configuration information
- initServletBean call
-
The parent FrameworkServlet of DispatchServlet implements the initServletBean method,
- Complete the initialization context of initWebApplicationContext(), including binding the location of configuration file and binding the relationship between parent and child containers
- After initialization, onRefresh(wac) is called. method
-
The onRefresh() method is implemented by the subclass DispatchServlet method
- Complete the initialization strategy and mainly realize the nine components of spring MVC
- The nine components are obtained from the ioc container by name or class
1. It is known from the above that the init method of DispatchServlet is started, but there is no init method. However, according to the source code, the init method is the init method of HttpServletBean, the parent class of DispatchServlet
public final void init() throws ServletException { PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { // Locate resources BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); // Load configuration information ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment())); this.initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException var4) { if (this.logger.isErrorEnabled()) { this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4); } throw var4; } } this.initServletBean(); } protected void initServletBean() throws ServletException { }
2. The initServletBean method that really completes the action of initializing the container is implemented by the parent FrameworkServlet
protected final void initServletBean() throws ServletException { this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { // Initialization information of webapplicationcontext method this.webApplicationContext = this.initWebApplicationContext(); this.initFrameworkServlet(); } catch (ServletException var5) { this.logger.error("Context initialization failed", var5); throw var5; } catch (RuntimeException var6) { this.logger.error("Context initialization failed", var6); throw var6; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms"); } } // Initialize context information protected WebApplicationContext initWebApplicationContext() { // Get the parent container WebApplicationContext from ServletContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); // Declare child containers WebApplicationContext wac = null; // Establish an association between parent and child containers 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); } this.configureAndRefreshWebApplicationContext(cwac); } } } // Find out whether the reference of the web container exists from the ServletContext, and create a default empty ioc container if (wac == null) { wac = this.findWebApplicationContext(); } // Assign a value to the ioc container created in the previous step if (wac == null) { wac = this.createWebApplicationContext(rootContext); } // Trigger onRefresh method if (!this.refreshEventReceived) { synchronized(this.onRefreshMonitor) { this.onRefresh(wac); } } if (this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); } return wac; } // Find whether the reference of the web container exists from the ServletContext @Nullable protected WebApplicationContext findWebApplicationContext() { String attrName = this.getContextAttribute(); if (attrName == null) { return null; } else { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } else { return wac; } } } //Assign a value to the created ioc container protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<?> contextClass = this.getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); wac.setParent(parent); // Binding profile String configLocation = this.getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } // Refresh context information this.configureAndRefreshWebApplicationContext(wac); return wac; } } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName()); } } wac.setServletContext(this.getServletContext()); wac.setServletConfig(this.getServletConfig()); wac.setNamespace(this.getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig()); } // Empty method this.postProcessWebApplicationContext(wac); // Implementation of some implementers this.applyInitializers(wac); // Refresh wac.refresh(); } // Implemented by subclasses protected void onRefresh(ApplicationContext context) { }
The onRefresh() method is implemented by the subclass DispatchServlet method
protected void onRefresh(ApplicationContext context) { // Initialization policy this.initStrategies(context); } protected void initStrategies(ApplicationContext context) { //1. Multi file upload component this.initMultipartResolver(context); private void initMultipartResolver(ApplicationContext context) { // It saves a lot of code for printing exception logs try { // Get the uploaded component of the configuration file from the configuration file this.multipartResolver = (MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class); } catch (NoSuchBeanDefinitionException var3) { this.multipartResolver = null; } } // 2. Initialize locales this.initLocaleResolver(context); private void initLocaleResolver(ApplicationContext context) { try { // It is also obtained from the ioc container this.localeResolver = (LocaleResolver)context.getBean("localeResolver", LocaleResolver.class); } catch (NoSuchBeanDefinitionException var3) { this.localeResolver = (LocaleResolver)this.getDefaultStrategy(context, LocaleResolver.class); } } // 3. Initialize template processor this.initThemeResolver(context); private void initThemeResolver(ApplicationContext context) { try { // It is also obtained from the ioc container this.themeResolver = (ThemeResolver)context.getBean("themeResolver", ThemeResolver.class); } catch (NoSuchBeanDefinitionException var3) { this.themeResolver = (ThemeResolver)this.getDefaultStrategy(context, ThemeResolver.class); } } } // 4. Initialize handlerMapping this.initHandlerMappings(context); private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // Whether to load all HandlerMapping. The default is true. When loading all, get the class of HandlerMapping type from the bean container if (this.detectAllHandlerMappings) { Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { // If you only need to load one, you can get it by name HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException var3) { } } // If there are no handlerMappings, try to get from the default policy if (this.handlerMappings == null) { this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class); } } // 5. Initialize parameter adapter this.initHandlerAdapters(context); private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; // Whether to load all HandlerAdapters. The default is true. When loading all, get the class of HandlerMapping type from the bean container if (this.detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { // If you only need to load one, you can get it by name HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException var3) { } } // If there are no handlerAdapters, try to get from the default policy if (this.handlerAdapters == null) { this.handlerAdapters = this.getDefaultStrategies(context, HandlerAdapter.class); } } // 6. Initialize the exception interceptor. The specific implementation is the same logic as the initialization parameter adapter above this.initHandlerExceptionResolvers(context); // 7. Initialize the view preprocessor. The specific implementation is the same as the initialization parameter adapter above this.initRequestToViewNameTranslator(context); // 8. Initialize the view converter. The specific implementation is the same logic as the initialization parameter adapter above this.initViewResolvers(context); // 9. Initialize FlashMap Manager this.initFlashMapManager(context); private void initFlashMapManager(ApplicationContext context) { try { //It is also obtained from the ioc container this.flashMapManager = (FlashMapManager)context.getBean("flashMapManager", FlashMapManager.class); } catch (NoSuchBeanDefinitionException var3) { this.flashMapManager = (FlashMapManager)this.getDefaultStrategy(context, FlashMapManager.class); } } }
Custom DispatchServlet
1. Core principles of Spring
Dispatcher servlet is the core function class implemented by Spring
<servlet> <servlet-name>service_dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/services/service_dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
(1) Version 1.0
This side implements a simple gpdispatcher servlet to simulate the core function classes implemented by Spring
Rewriting the init() method of the HttpServlet of the parent class to initialize the container mainly does the following things:
- Obtain the configuration file and load the configuration file
- Get the package name of the configured scan, and then put all classes in the package into the map of the ioc container
- Traverse the map and process different types of classes. Put the methods in the Controller into the class, and directly instantiate the services into the class
- Traverse the map to determine whether there are fields marked with autored. If there are, assign values to the fields through reflection (mainly from the ioc container)
public class GPDispatcherServlet extends HttpServlet { //ioc container private Map<String,Object> mapping = new HashMap<String, Object>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req,resp);} @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { // Call the doDispatch method. After the request comes, the method will be called to implement the core logic doDispatch(req,resp); } catch (Exception e) { resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace())); } } private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { String url = req.getRequestURI(); String contextPath = req.getContextPath(); url = url.replace(contextPath, "").replaceAll("/+", "/"); if(!this.mapping.containsKey(url)){resp.getWriter().write("404 Not Found!!");return;} Method method = (Method) this.mapping.get(url); Map<String,String[]> params = req.getParameterMap(); method.invoke(this.mapping.get(method.getDeclaringClass().getName()),new Object[]{req,resp,params.get("name")[0]}); } @Override public void init(ServletConfig config) throws ServletException { InputStream is = null; try{ Properties configContext = new Properties(); // 1. Get the configuration file and load it is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation")); configContext.load(is); // Get scanned package String scanPackage = configContext.getProperty("scanPackage"); // 2. Get the package name of the configured scan, and then put all classes in the package into the map of the ioc container doScanner(scanPackage); for (String className : mapping.keySet()) { if(!className.contains(".")){continue;} Class<?> clazz = Class.forName(className); if(clazz.isAnnotationPresent(GPController.class)){ mapping.put(className,clazz.newInstance()); String baseUrl = ""; if (clazz.isAnnotationPresent(GPRequestMapping.class)) { GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class); baseUrl = requestMapping.value(); } Method[] methods = clazz.getMethods(); for (Method method : methods) { if (!method.isAnnotationPresent(GPRequestMapping.class)) { continue; } GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class); String url = (baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/"); mapping.put(url, method); System.out.println("Mapped " + url + "," + method); } }else if(clazz.isAnnotationPresent(GPService.class)){ GPService service = clazz.getAnnotation(GPService.class); String beanName = service.value(); if("".equals(beanName)){beanName = clazz.getName();} Object instance = clazz.newInstance(); mapping.put(beanName,instance); for (Class<?> i : clazz.getInterfaces()) { mapping.put(i.getName(),instance); } }else {continue;} } for (Object object : mapping.values()) { if(object == null){continue;} Class clazz = object.getClass(); if(clazz.isAnnotationPresent(GPController.class)){ Field [] fields = clazz.getDeclaredFields(); for (Field field : fields) { if(!field.isAnnotationPresent(GPAutowired.class)){continue; } GPAutowired autowired = field.getAnnotation(GPAutowired.class); String beanName = autowired.value(); if("".equals(beanName)){beanName = field.getType().getName();} field.setAccessible(true); try { field.set(mapping.get(clazz.getName()),mapping.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } catch (Exception e) { }finally { if(is != null){ try {is.close();} catch (IOException e) { e.printStackTrace(); } } } System.out.print("GP MVC Framework is init"); } // Scan the package and put the classes in the package in mapping private void doScanner(String scanPackage) { URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.","/")); File classDir = new File(url.getFile()); for (File file : classDir.listFiles()) { if(file.isDirectory()){ doScanner(scanPackage + "." + file.getName());}else { if(!file.getName().endsWith(".class")){continue;} String clazzName = (scanPackage + "." + file.getName().replace(".class","")); mapping.put(clazzName,null); } } } }
(2) Version 2.0
- Load the configuration file doLoadConfig(config.getInitParameter("contextConfigLocation");
- Scan related class doScanner(contextConfig.getProperty("scanPackage");
- Initialize the instances of all related classes and put them into the IOC container doInstance();
- Complete dependency injection doAutowired();
- Initialize handlermapping();
package com.gupaoedu.mvcframework.v2.servlet; import com.gupaoedu.mvcframework.annotation.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.util.*; public class GPDispatcherServlet extends HttpServlet { //Store application Configuration contents of properties private Properties contextConfig = new Properties(); //Store all scanned classes private List<String> classNames = new ArrayList<String>(); //IOC container that holds all instantiated objects //Registered singleton mode private Map<String,Object> ioc = new HashMap<String,Object>(); //Save the correspondence of all Mapping in Contrller private Map<String,Method> handlerMapping = new HashMap<String,Method>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //Dispatch and distribute tasks try { //Delegation mode doDispatch(req,resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 Excetion Detail:" +Arrays.toString(e.getStackTrace())); } } // private void doDispatch(HttpServletRequest req, HttpServletResponse resp)throws Exception { // // String url = req.getRequestURI(); // String contextPath = req.getContextPath(); // url = url.replaceAll(contextPath,"").replaceAll("/+","/"); // // if(!this.handlerMapping.containsKey(url)){ // resp.getWriter().write("404 Not Found!!"); // return; // } // // Method method = this.handlerMapping.get(url); // //The first parameter: the instance where the method is located // //The second parameter: the argument required when calling // // Map<String,String[]> params = req.getParameterMap(); // // //Opportunistic way // String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName()); // method.invoke(ioc.get(beanName),new Object[]{req,resp,params.get("name")[0]}); // //System.out.println(method); // } private void doDispatch(HttpServletRequest req, HttpServletResponse resp)throws Exception { String url = req.getRequestURI(); String contextPath = req.getContextPath(); url = url.replaceAll(contextPath,"").replaceAll("/+","/"); if(!this.handlerMapping.containsKey(url)){ resp.getWriter().write("404 Not Found!!"); return; } Method method = this.handlerMapping.get(url); //The first parameter: the instance where the method is located //The second parameter: the argument required when calling Map<String,String[]> params = req.getParameterMap(); //Gets the formal parameter list of the method Class<?> [] parameterTypes = method.getParameterTypes(); //Save the url parameter list of the request Map<String,String[]> parameterMap = req.getParameterMap(); //The location where the assignment parameters are saved Object [] paramValues = new Object[parameterTypes.length]; //Dynamic assignment according to parameter position for (int i = 0; i < parameterTypes.length; i ++){ Class parameterType = parameterTypes[i]; if(parameterType == HttpServletRequest.class){ paramValues[i] = req; continue; }else if(parameterType == HttpServletResponse.class){ paramValues[i] = resp; continue; }else if(parameterType == String.class){ //Annotated parameters in the extraction method Annotation[] [] pa = method.getParameterAnnotations(); for (int j = 0; j < pa.length ; j ++) { for(Annotation a : pa[i]){ if(a instanceof GPRequestParam){ String paramName = ((GPRequestParam) a).value(); if(!"".equals(paramName.trim())){ String value = Arrays.toString(parameterMap.get(paramName)) .replaceAll("\\[|\\]","") .replaceAll("\\s",","); paramValues[i] = value; } } } } } } //Opportunistic way //Get the class where the method is located through reflection. After getting the class, you still get the name of the class //Then call toLowerFirstCase to get beanName String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName()); method.invoke(ioc.get(beanName),new Object[]{req,resp,params.get("name")[0]}); } @Override public void init(ServletConfig config) throws ServletException { //Template mode //1. Load profile doLoadConfig(config.getInitParameter("contextConfigLocation")); //2. Scan related classes doScanner(contextConfig.getProperty("scanPackage")); //3. Initialize the instances of all related classes and put them into the IOC container doInstance(); //4. Complete dependency injection doAutowired(); //5. Initialize HandlerMapping initHandlerMapping(); System.out.println("GP Spring framework is init."); } private void initHandlerMapping() { if(ioc.isEmpty()){ return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if(!clazz.isAnnotationPresent(GPController.class)){ continue; } String baseUrl = ""; //Get url configuration of Controller if(clazz.isAnnotationPresent(GPRequestMapping.class)){ GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class); baseUrl = requestMapping.value(); } //Get url configuration of Method Method[] methods = clazz.getMethods(); for (Method method : methods) { //Those without RequestMapping annotation are ignored directly if(!method.isAnnotationPresent(GPRequestMapping.class)){ continue; } //Mapping URL GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class); ///demo/query //(//demo//query) String url = ("/" + baseUrl + "/" + requestMapping.value()) .replaceAll("/+", "/"); handlerMapping.put(url,method); System.out.println("Mapped " + url + "," + method); } } } private void doAutowired() { if(ioc.isEmpty()){ return; } for (Map.Entry<String, Object> entry : ioc.entrySet()) { //Get all the properties in the instance object Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if(!field.isAnnotationPresent(GPAutowired.class)){ continue; } GPAutowired autowired = field.getAnnotation(GPAutowired.class); String beanName = autowired.value().trim(); if("".equals(beanName)){ beanName = field.getType().getName(); } //Whether you like it or not, kiss hard field.setAccessible(true); //Set access to private properties try { //Execute injection action field.set(entry.getValue(), ioc.get(beanName)); } catch (Exception e) { e.printStackTrace(); continue ; } } } } //Control reversal process //Factory mode private void doInstance() { if(classNames.isEmpty()){return;} try { for (String className : classNames) { Class<?> clazz = Class.forName(className); if(clazz.isAnnotationPresent(GPController.class)) { Object instance = clazz.newInstance(); String beanName = toLowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); }else if(clazz.isAnnotationPresent(GPService.class)){ //1. The default class name is lowercase String beanName = toLowerFirstCase(clazz.getSimpleName()); //2. Custom naming GPService service = clazz.getAnnotation(GPService.class); if(!"".equals(service.value())){ beanName = service.value(); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); //3. Inject implementation classes according to types in an opportunistic way for (Class<?> i : clazz.getInterfaces()) { if(ioc.containsKey(i.getName())){ throw new Exception("The beanName is exists!!"); } ioc.put(i.getName(),instance); } }else { continue; } } }catch (Exception e){ e.printStackTrace(); } } private String toLowerFirstCase(String simpleName) { char [] chars = simpleName.toCharArray(); chars[0] += 32; return String.valueOf(chars); } private void doScanner(String scanPackage) { //All classes under the package are scanned in URL url = this.getClass().getClassLoader() .getResource("/" + scanPackage.replaceAll("\\.","/")); File classPath = new File(url.getFile()); for (File file : classPath.listFiles()) { if(file.isDirectory()){ doScanner(scanPackage + "." + file.getName()); }else { if(!file.getName().endsWith(".class")){ continue; } String className = (scanPackage + "." + file.getName()).replace(".class",""); classNames.add(className); } } } private void doLoadConfig(String contextConfigLocation) { InputStream fis = null; try { fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); //1. Read configuration file contextConfig.load(fis); }catch(Exception e){ e.printStackTrace(); }finally{ try { if(null != fis){fis.close();} } catch (IOException e) { e.printStackTrace(); } } } }