In our daily work, we often use Spring, Spring Boot, Spring Cloud, Struts, Mybatis, Hibernate and other open source frameworks. With the birth of these frameworks, the usual development workload is becoming more and more relaxed. With Spring Boot, we can create a new Web project in minutes.

Remember when I first started working, I was still writing Web projects with servlets, writing database connection pools by myself, and using native JDBC to operate databases. Back to the theme of this article, today I will give you an insight into how Spring works by writing the Spring framework by hand. The code covered in this article is only used to help you understand Spring. It will not be used online.

The project structure

Framework part implementation

  1. To distinguish between the framework part of the code and the business part of the code, we separate the two parts into separate packagescom.mars.democom.mars.frameworkTo scan only the business code later.
  2. I’m writing the Spring framework myself, so I won’t introduce any Spring project-related packages.
  3. Since it is a Web project, all we need to introduceservlet-apiPackage, for compiler use only, all configuredscopeprovided.

Create a New Servlet

Start by creating a new HttpServlet implementation class, MarsDispatcherServlet, to receive requests.

public class MarsDispatcherServlet extends HttpServlet { @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 { //6. Override public void init(ServletConfig config) throws ServletException {}Copy the code

Configure 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 > < the display - the name > Spring Mvc Education</display-name> <servlet> <servlet-name>marsmvc</servlet-name> <servlet-class>com.mars.framework.servlet.MarsDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>marsmvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>Copy the code

  1. First, a servlet is configured with the name marsmvc and the class full path iscom.mars.framework.servlet.MarsDispatcherServlet.
  2. The initialization parameter name and value are set (the value here is the configuration file for the entire project).
  3. configurationload-on-startupFlags whether the container loads the servlet at startup (instantiates and calls its init() method).
  4. configurationservlet-mapping, forward all requests to this servlet for processing.

Configure the application. The properties

scanPackage=com.mars.demoCopy the code

This is easier to understand, only one thing is configured, which means the package to scan, and then we get this value to load the container.

Define the annotations we commonly use

  1. MarsAutowired
  2. MarsController
  3. MarsRequestMapping
  4. MarsRequestParam
  5. MarsService

Here are just two of them, the rest are pretty much the same, so if you need the source code, go to my repository fork.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsController {
    String value() default "";
}Copy the code

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsRequestMapping {
    String value() default "";
}Copy the code

Enrich Servlet functionality

Start by listing all the things that the framework should do when initialized

  1. Loading a Configuration File
  2. Scan all associated classes
  3. Initialize all associated classes and store them in an IOC container
  4. Perform dependency injection (assign @autowired annotated fields)
  5. Construct HandlerMapping to associate urls with methods

Let’s go through the steps above

@Override public void init(ServletConfig config) throws ServletException { System.out.println("==================="); //1. Load the configuration file doLoadConfig(config.getinitParameter ("contextConfigLocation")); / / 2. Scan all associated class doScanner (contextConfig. GetProperty (" scanPackage ")); //3. Initialize all associated classes and store them in the IOC container doInstance(); //4. Perform dependency injection (assign @autowired to fields) doAutowired(); //Spring and core functions are complete IOC, DI //5. InitHandlerMapping (); System.out.println("Mars MVC framework initialized"); }Copy the code

Loading a Configuration File

private Properties contextConfig = new Properties(); private void doLoadConfig(String location) { InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location); try { contextConfig.load(inputStream); } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream ! = null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); }}}}Copy the code

Scan all associated classes

Private void doScanner(String basePackage) {// Obtain the URL of the package to be scanned. Url URL = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/")); File dir = new File(url.getFile()); For (File File: Dir.listfiles ()){if(file.isdirectory ()){// Recursive scan doScanner(basePackage + "." + file.getName()); } else { String className = basePackage + "." + file.getName().replace(".class", ""); classNames.add(className); System.out.println(className); }}}Copy the code

Initialize all associated classes and store them in an IOC container

private void doInstance() { if(classNames.isEmpty()) return; for(String className: classNames) { try { Class<? > clazz = Class.forName(className); if(clazz.isAnnotationPresent(MarsController.class)) { Object instance = clazz.newInstance(); String beanName = lowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); } else if (clazz.isAnnotationPresent(MarsService.class)) { MarsService service = clazz.getAnnotation(MarsService.class);  String beanName = service.value(); //2. if("".equals(beanName.trim())) { //1. BeanName = lowerFirstCase(clazz.getSimplename ()); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); //3. Automatic type matching (e.g., assigning implementation classes to interfaces) Class<? > [] interfaces = clazz.getInterfaces(); for(Class<? > inter: interfaces) { ioc.put(inter.getName(), instance); } } } catch (Exception e) { e.printStackTrace(); Private String lowerFirstCase(String STR) {char[] chars = str.tochararray (); char[] chars = str.tochararray (); chars[0] += 32; return String.valueOf(chars); }Copy the code

Perform dependency injection (assign @autowired annotated fields)

private void doAutowired() { if(ioc.isEmpty()) return; for(Map.Entry<String, Object> entry: Ioc.entryset ()) {ioc.entryset ()) {ioc.entryset ()) {ioc.entryset ()) {ioc.entrySet()) {ioc.entrySet()) {ioc.entrySet() entry.getValue().getClass().getDeclaredFields(); For (Field Field: fields) {// Check if @autowired is added if(! field.isAnnotationPresent(MarsAutowired.class)) continue; MarsAutowired autowired = field.getAnnotation(MarsAutowired.class); String beanName = autowired.value(); if("".equals(beanName)) { beanName = field.getType().getName(); } // If the field is private, force access to field.setaccessible (true); try { field.set(entry.getValue(), ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); }}}}Copy the code

Construct HandlerMapping to associate urls with methods

private void initHandlerMapping() { if(ioc.isEmpty()) return; for(Map.Entry<String, Object> entry : ioc.entrySet()) { Class<? > clazz = entry.getValue().getClass(); if(! clazz.isAnnotationPresent(MarsController.class)) continue; String baseUrl = ""; if(clazz.isAnnotationPresent(MarsRequestMapping.class)) { MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class); baseUrl = requestMapping.value(); } Method[] methods = clazz.getMethods(); for(Method method : methods) { if(! method.isAnnotationPresent(MarsRequestMapping.class)) continue; MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class); String regex = requestMapping.value(); regex = (baseUrl + regex).replaceAll("/+", "/"); Pattern pattern = Pattern.compile(regex); handlerMapping.add(new Handler(entry.getValue(), method, pattern)); System.out.println("Mapping: " + regex + "," + method.getName()); }}}Copy the code

Write business code

Create a new Controller

@MarsController @MarsRequestMapping("/demo") public class DemoApi { @MarsAutowired private DemoService demoService; @MarsRequestMapping("/query") public void query(HttpServletRequest req, HttpServletResponse resp, @MarsRequestParam("name") String name) { System.out.println("name: " + name); String result = demoService.get(name); try{ resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MarsRequestMapping("/add") public void add(HttpServletRequest req, HttpServletResponse resp, @MarsRequestParam("a") Integer a, @MarsRequestParam("b") Integer b) { try { resp.getWriter().write(String.format("%d+%d=%d", a, b, (a+b))); } catch (IOException e) { e.printStackTrace(); }}}Copy the code

Provides two interfaces, one that returns the description of the response by request name, and the other that adds the two Integers of the request and returns.

Create a Service

public interface DemoService { String get(String name); } @MarsService public class DemoServiceImpl implements DemoService { public String get(String name) { return String.format("My name is %s.", name); }}Copy the code

Add the Jetty plugin

Our project runs in Jetty, so add related plug-ins and configuration:

<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> < version > 7.1.6. V20100715 < / version > < configuration > < stopPort > 9988 < / stopPort > < stopKey > foo < / stopKey > <scanIntervalSeconds>5</scanIntervalSeconds> <connectors> <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector"> <port>8080</port> <maxIdleTime>60000</maxIdleTime>  </connector> </connectors> <webAppConfig> <contextPath>/</contextPath> </webAppConfig> </configuration> </plugin>Copy the code

run

Click Jetty: Run to run the project

Browser visit: http://localhost:8080/demo/query? name=Mars

Browser visit: http://localhost:8080/demo/add? a=10&b=20

The warehouse address

Welcome to my personal blog

Concern public number: JAVA class at 9:30, here is a group of excellent program ape, join us, discuss technology together, common progress! Reply to “Materials” for the latest information on 2T industry!