Handwritten Spring mvc framework
1. Dependency Management Tools
Maven,Gradle
Advantage:
- Automated Management Dependency (jar package)
- Resolve Dependency Conflicts
- Packaging (mvn clean package)
Characteristic:
-
Contract greater than configuration
For example, src is the agreed code directory
- The same project can have many modules, each built separately
Plug-in mechanism
Gradle has advantages over Maven:
- Use json configuration
- No installation required
2. Integrating tomcat with code
Introducing jar packages ( Tomcat Embed Core)
-
Code:
public class TomcatServer { private Tomcat tomcat; private String[] args; public TomcatServer(String[] args) { this.args = args; } public void startServer() throws LifecycleException { tomcat = new Tomcat(); tomcat.setPort(6699); tomcat.start(); Context context = new StandardContext(); context.setPath(""); context.addLifecycleListener(new Tomcat.FixContextListener()); DispatcherServlet servlet = new DispatcherServlet(); Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true); // '/': map all URLs context.addServletMappingDecoded("/", "dispatcherServlet"); tomcat.getHost().addChild(context); Thread awaitThread = new Thread("tomcat_await_thread"){ @Override public void run() { //Internal class calls instance member variables of external class //This line of code is blocking, is it supposed to keep tomcat off? TomcatServer.this.tomcat.getServer().await(); } }; awaitThread.setDaemon(false); awaitThread.start(); } }
3. Process of one request
-
Front-end requests are sent to the port tomcat listens on via the http protocol, tomcat encapsulates the request into a ServletRequest object via decoding, deserialization, and so on, and then calls the service() of Dispatcher Servlet
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { for (MappingHandler mappingHandler : HandlerManager.mappingHandlerList) { try { if (mappingHandler.handle(req, res)) { return; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
You can see that there is only one operation in the code: traversing the mappingHandlerList
-
Match the request url to find the corresponding function to process
The mappingHandler List holds the mappingHandler, which stores information such as the request path and the corresponding execution function.
(ps: Because there are very few path s, the code does not use a map, but traverses directly)
Let's look at the process of building a mappingHandler List, which first iterates through the classes annotated @controller, then the corresponding methods for @RequestMapping, and finally establishes the MappingHandler object and adds it to the list
public class HandlerManager { public static List<MappingHandler> mappingHandlerList = new ArrayList<>(); //This static method is called when the project is initialized public static void resolveMappingHandler(List<Class<?>> classList) { for (Class<?> cls : classList) { if (cls.isAnnotationPresent(Controller.class)) { parseHandlerFromController(cls); } } } private static void parseHandlerFromController(Class<?> cls) { Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { //Find functions with @RequestMapping added if (!method.isAnnotationPresent(RequestMapping.class)) { continue; } String uri = method.getDeclaredAnnotation(RequestMapping.class).value(); List<String> paramNameList = new ArrayList<>(); for (Parameter parameter : method.getParameters()) { if (parameter.isAnnotationPresent(RequestParam.class)) { paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value()); } } String[] params = paramNameList.toArray(new String[paramNameList.size()]); MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params); HandlerManager.mappingHandlerList.add(mappingHandler); } } }
Code defined by the @Controller,@RequestMapping,@RequestParam annotation is temporarily omitted.
Okay, the request comes to the mappingHandler:
public class MappingHandler { private String uri; private Method method; private Class<?> controller; private String[] args; //This is the list of parameters with @RequestParam added public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException { String requestUri = ((HttpServletRequest) req).getRequestURI(); if (!uri.equals(requestUri)) { return false; } Object[] parameters = new Object[args.length]; for (int i=0;i<args.length;i++) { parameters[i] = req.getParameter(args[i]); } Object ctl = BeanFactory.getBean(controller); Object response = method.invoke(ctl, parameters); //Write the returned results to the ServletResponse object res.getWriter().println(response.toString()); return true; } MappingHandler(String uri, Method method, Class<?> cls, String[] args) { this.uri = uri; this.method = method; this.controller = cls; this.args = args; } }
The process ends when the ServletResponse object that has written the data is Byted to the front end
IV. Initialization of Projects
Why should I end up with initialization, which seems better understood?
Recall that when we use spring, we all have an Application class, which is the familiar thing below:
public class Application { public static void main(String[] args) { MiniApplication.run(Application.class, args); } }
Let's take a look at MiniApplication.run(Application.class, args)
public static void run(Class<?> cls, String[] args) { System.out.println("Hello mini-spring!"); TomcatServer tomcatServer = new TomcatServer(args); try { //1. Start the server tomcatServer.startServer(); //2. Scan class files in src List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName()); //3. Initialize BeanFactory BeanFactory.initBean(classList); //4. Set up a mappingHandlerList HandlerManager.resolveMappingHandler(classList); //5. Print Class Information classList.forEach(it-> System.out.println(it.getName())); } catch (Exception e) { e.printStackTrace(); } }
The second step needs to be elaborated:
-
First of all, let's note that the parameter we passed in is cls.getPackage().getName()
That is, only the class files under the package will be scanned, that is, why do we keep the Application.java file outermost
public class ClassScanner { public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException { List<Class<?>> classList = new ArrayList<>(); String path = packageName.replace(".", "/"); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Enumeration<URL> resources = classLoader.getResources(path); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); if (resource.getProtocol().contains("jar")) { JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection(); String jarFilePath = jarURLConnection.getJarFile().getName(); classList.addAll(getClassesFromJar(jarFilePath, path)); }else { // todo } } return classList; } private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException { List<Class<?>> classes = new ArrayList<>(); JarFile jarFile = new JarFile(jarFilePath); Enumeration<JarEntry> jarEntries = jarFile.entries(); while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); String entryName = jarEntry.getName();// com/mooc/zbs/test/Test.class if (entryName.startsWith(path) && entryName.endsWith(".class")) { String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6); classes.add(Class.forName(classFullName)); } } return classes; } }
The ability to access file resources with the help of classLoader
The specific code has not been read carefully, let's not say.