Spring framework advanced spring v2 0

Posted by peppeto on Sun, 02 Jan 2022 15:47:05 +0100

1. The essence of IOC

Map container: the essence of IOC container is a map

ApplicationContext context: holds a reference to BeanFactory

Bean factory factory: responsible for obtaining beans from containers

BeanDefinitionReader parser: responsible for parsing all configuration files

Definition meta information configuration: xml, yml, annotation and properties. In order to adapt to different configuration files, Spring uses the top-level design BeanDefinition to save the configuration information.

Bean instance: Reflection instantiation bean. The source of this bean may be a native bean or a proxy bean. No matter what kind of bean, it will be decorated by the BeanWrapper (decorator mode), which holds the reference of the bean.

BeanWrapper decorator: it is cached in the IOC container and holds the reference of the Bean

Call the getBean() method through ApplicationContext to obtain various beanfactories. Read the configuration file information through the BeanDefinitionReader and create the Bean according to the BeanDefinition. The obtained Bean, whether it is a native object or a proxy object, is handed over to the BeanWrapper for decoration. Therefore, the BeanWrapper object is obtained by the getBean() method.

2. From Servlet to ApplicationContext

In the previous article, a simple Spring framework process was implemented based on MyDispatchServlet:

  1. Read configuration file
  2. Scan files according to package address
  3. Initialize the IOC container, instantiate the Bean, and load it into the IOC
  4. Dependency injection
  5. Match the URL and method and store it in HandlerMapping

Steps 1.4 are both IOC and DI operations, which need to be transformed into ApplicationContext.

The new steps are:

  1. Read the configuration file and scan the files under the corresponding package
  2. Get all configuration information
  3. Save all configuration information
  4. Load all non deferred beans

The method of loading beans is the core method of Spring framework, get Bean method. The steps are as follows:

  1. Get configuration information according to BeanName
  2. Instantiate Instance
  3. Encapsulate an Instance into a BeanWrapper
  4. Dependency injection on Instance
  5. Load Instance into IOC

3. Combination with Spring source code

In order to realize the function, some analysis needs to be carried out in combination with the Spring source code.

BeanFactory is the core. It has a key method, getBean.

ApplicationContext class and DefaultListableBeanFactory class inherit BeanFactory.

The function of DefaultListableBeanFactory is to save all configuration information: beanDefinitionMap, three-level cache (i.e. the ultimate cache of IOC): factoryBeanInstanceCache, class backup of all beans: factoryBeanObjectCache, and implement the get Bean method

ApplicationContext holds a reference to DefaultListableBeanFactory, so you can complete the method by using some data of DefaultListableBeanFactory.

BeanDefinitionReader class is specially used to read configuration files and scan files under packages.

BeanDefinition is used to save the name of the bean: factoryBeanName, and the full class name of the bean: beanClassName

BeanWrapper is used to encapsulate the instantiated bean and finally save it to IOC, including instance object: wrappedInstance and class object: wrappedClass.

Knowing these main classes, you can start coding.

4. Implementation process

4.1. ApplicationContext

4.1. 1. Object factory

/**
 * Create factory for object
 */
public interface MyBeanFactory {

    Object getBean(Class beanClass);

    Object getBean(String beanName);
}

4.1. 2. Read the configuration file and scan the package

In the constructor of ApplicationContext, perform the creation of BeanDefinitionReader to load the configuration file

public class MyApplicationContext implements MyBeanFactory {

    //Hold reference
    private MyDefaultListableBeanFactory registry = new MyDefaultListableBeanFactory();

    //Profile reader
    private MyBeanDefinitionReader reader = null;

    public MyApplicationContext(String... configLocations) {
        //1. Load profile
        reader = new MyBeanDefinitionReader(configLocations);
    }
}

The BeanDefinitionReader class has Properties and registrybeanclases. These two containers do not need to be repeated. They have appeared in the previous article

public class MyBeanDefinitionReader {

    //Profile information
    private Properties contextConfig = new Properties();
    //Register file name collection
    private List<String> registryBeanClasses = new ArrayList<String>();

    public MyBeanDefinitionReader(String... locations) {
        //1. Load profile
        doLoadConfig(locations[0]);
        //2. Scan related classes
        doScanner(contextConfig.getProperty("scanPackage"));
    }
}

The implementation of these two methods is no different from that in the previous article.

private void doLoadConfig(String configLocation) {
    InputStream stream = this.getClass().getClassLoader().getResourceAsStream(configLocation.replaceAll("classpath:", ""));
    try {
        contextConfig.load(stream);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

private void doScanner(String scanPackage) {
    URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
    File aFile = new File(url.getFile());
    for (File file : aFile.listFiles()) {
        String fileName = file.getName();
        if (file.isDirectory()) {
            doScanner(scanPackage + "." + fileName);
        }
        if (!fileName.endsWith(".class")) {
            continue;
        }
        String className = scanPackage + "." + fileName.replaceAll(".class", "");
        registryBeanClasses.add(className);
    }
}

4.1. 3. Get all configuration information

First, you need to create a configuration class BeanDefinition, which has

bean Name: factoryBeanName

The full class name of the bean: beanClassName

Whether to delay loading: getLazyInit()

public class MyBeanDefinition {

    //beanName
    private String factoryBeanName;
    //Full class name
    private String beanClassName;

    public String getFactoryBeanName() {
        return factoryBeanName;
    }

    public void setFactoryBeanName(String factoryBeanName) {
        this.factoryBeanName = factoryBeanName;
    }

    public String getBeanClassName() {
        return beanClassName;
    }

    public void setBeanClassName(String beanClassName) {
        this.beanClassName = beanClassName;
    }

    //Whether to delay loading
    public boolean isLazyInit() {
        return false;
    }
}

In the ApplicationContext file, add a step:

public class MyApplicationContext implements MyBeanFactory {

    //Hold reference
    private MyDefaultListableBeanFactory registry = new MyDefaultListableBeanFactory();

    //Profile reader
    private MyBeanDefinitionReader reader = null;

    public MyApplicationContext(String... configLocations) {
        //1. Load profile
        reader = new MyBeanDefinitionReader(configLocations);
        try {
            //2. Parse the configuration file and encapsulate the configuration information into BeanDefinition objects
            List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

It can be found that the method loadBeanDefinition here is similar to the instantiation process in the previous chapter, except that the bean name and full bean name of the bean object are saved this time.

public List<MyBeanDefinition> loadBeanDefinitions() {
    List<MyBeanDefinition> result = new ArrayList<MyBeanDefinition>();
    try {
        for (String beanName : registryBeanClasses) {
            Class<?> aClass = Class.forName(beanName);
            if (aClass.isInterface()) {
                continue;
            }
            result.add(createBeanDefinition(toLowerFirstCase(aClass.getSimpleName()), aClass.getName()));
            for (Class<?> anInterface : aClass.getInterfaces()) {
                result.add(createBeanDefinition(anInterface.getName(), aClass.getName()));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}

private MyBeanDefinition createBeanDefinition(String factoryBeanName, String beanClassName) {
    MyBeanDefinition beanDefinition = new MyBeanDefinition();
    beanDefinition.setFactoryBeanName(factoryBeanName);
    beanDefinition.setBeanClassName(beanClassName);
    return beanDefinition;
}

private String toLowerFirstCase(String simpleName) {
    char[] chars = simpleName.toCharArray();
    chars[0] += 32;
    return String.valueOf(chars);
}

4.1. 4. Save configuration information to DefaultListableBeanDefinition

Continue adding steps in ApplicationContext:

public class MyApplicationContext implements MyBeanFactory {

    //Hold reference
    private MyDefaultListableBeanFactory registry = new MyDefaultListableBeanFactory();

    //Profile reader
    private MyBeanDefinitionReader reader = null;

    public MyApplicationContext(String... configLocations) {
        //1. Load profile
        reader = new MyBeanDefinitionReader(configLocations);
        try {
            //2. Parse the configuration file and encapsulate the configuration information into BeanDefinition objects
            List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
            //3. Cache all configuration information
            this.registry.doRegistryBeanDefinition(beanDefinitions);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The method doRegistryBeanDefinition is simply understood to register the configuration information, that is, transfer the BeanDefinition list to the BeanDefinitionMap, with the key as factoryBeanName and the value as BeanDefinition.

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    //Save all configuration information
    public Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<String, MyBeanDefinition>();

    //The third level cache is also the ultimate cache
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();

    public void doRegistryBeanDefinition(List<MyBeanDefinition> beanDefinitions) throws Exception {
        for (MyBeanDefinition beanDefinition : beanDefinitions) {
            if (beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("Already exists" + beanDefinition.getFactoryBeanName());
            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }
}

4.1. 5. Load non delayed Bean

In ApplicationContext, continue to add steps:

public class MyApplicationContext implements MyBeanFactory {

    //Hold reference
    private MyDefaultListableBeanFactory registry = new MyDefaultListableBeanFactory();

    //Profile reader
    private MyBeanDefinitionReader reader = null;

    public MyApplicationContext(String... configLocations) {
        //1. Load profile
        reader = new MyBeanDefinitionReader(configLocations);
        try {
            //2. Parse the configuration file and encapsulate the configuration information into BeanDefinition objects
            List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
            //3. Cache all configuration information
            this.registry.doRegistryBeanDefinition(beanDefinitions);
            //4. Load all non deferred loaded bean s
            doLoadInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

According to the configuration file information obtained from the DefaultListableBeanDefinition, the bean can be loaded in a circular manner, and the method used is also getBean. The getBean here can use the ApplicationContext method because it inherits the BeanFactory.

However, DefaultListableBeanDefinition also inherits BeanFactory, which also contains three-level cache and Bean instance saving, and ApplicationContext holds a reference to DefaultListableBeanDefinition. Therefore, getBean in Spring source code is implemented by DefaultListableBeanDefinition, and ApplicationContext only needs to reference its method.

    private void doLoadInstance() {
        //Loop call getBean method
        for (Map.Entry<String, MyBeanDefinition> entry : this.registry.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            //Whether to delay loading. If not, load immediately
            if (!entry.getValue().isLazyInit()) {
                getBean(beanName);
            }
        }
    }

4.2. DefaultListableBeanDefinition

In fact, only the implementation of getBean is left in the DefaultListableBeanDefinition part.

Write according to the above steps

4.2. 1. Get configuration information

Steps for DefaultListableBeanDefinition class:

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    //Save all configuration information
    public Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<String, MyBeanDefinition>();

    //The third level cache is also the ultimate cache
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
    
    @Override
    public Object getBean(String beanName) {
        //1. Get the corresponding beanDefinition configuration information first
        MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        return null;
    }
}

4.2. 2. Instantiation class

To add the DefaultListableBeanDefinition class:

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    //Save all configuration information
    public Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<String, MyBeanDefinition>();

    //The third level cache is also the ultimate cache
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
    
    @Override
    public Object getBean(String beanName) {
        //1. Get the corresponding beanDefinition configuration information first
        MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //2. Reflect instanced objects
        Object instance = instantiateBean(beanName, beanDefinition);
        return null;
    }
}

Method instantiateBean calls reflection to instantiate, and saves the instantiated content to factoryBeanObjectCache

private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
    String beanClassName = beanDefinition.getBeanClassName();
    Object instance = null;
    try {
        Class<?> aClass = Class.forName(beanClassName);
        instance = aClass.newInstance();
        //If it is a proxy object, the AOP proxy is triggered

        this.factoryBeanObjectCache.put(beanName, instance);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return instance;
}

4.2. 3. Encapsulating BeanWrapper

To add the DefaultListableBeanDefinition class:

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    //Save all configuration information
    public Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<String, MyBeanDefinition>();

    //The third level cache is also the ultimate cache
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
    
    @Override
    public Object getBean(String beanName) {
        //1. Get the corresponding beanDefinition configuration information first
        MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //2. Reflect instanced objects
        Object instance = instantiateBean(beanName, beanDefinition);
        //3. Encapsulate the returned object as a BeanWrapper
        MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
        return null;
    }
}

To create a BeanWrapper, two properties are required

Instantiated class: wrappedInstance

Class of instantiated class: wrappedClass

public class MyBeanWrapper {

    private Object wrappedInstance;
    private Class<?> wrappedClass;

    public MyBeanWrapper(Object instance) {
        this.wrappedInstance = instance;
        this.wrappedClass = instance.getClass();
    }

    public Object getWrappedInstance() {
        return this.wrappedInstance;
    }

    public Class<?> getWrappedClass() {
        return this.wrappedClass;
    }
}

Just initialize the constructor

4.2. 4. Dependency injection

To add the DefaultListableBeanDefinition class:

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    //Save all configuration information
    public Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<String, MyBeanDefinition>();

    //The third level cache is also the ultimate cache
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
    
    @Override
    public Object getBean(String beanName) {
        //1. Get the corresponding beanDefinition configuration information first
        MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //2. Reflect instanced objects
        Object instance = instantiateBean(beanName, beanDefinition);
        //3. Encapsulate the returned object as a BeanWrapper
        MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
        //4. Perform dependency injection
        populateBean(beanName, beanDefinition, beanWrapper);
        return null;
    }
}

The method populateBean generates beans. This method is not very different from the previous dependency injection, so there is no need to repeat it

private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
    Object instance = beanWrapper.getWrappedInstance();
    Class<?> clazz = instance.getClass();
    if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
        return;
    }
    for (Field field : clazz.getDeclaredFields()) {
        if (!field.isAnnotationPresent(MyAutowired.class)) {
            continue;
        }
        MyAutowired autowired = field.getAnnotation(MyAutowired.class);
        String autowiredBeanName = field.getType().getName();
        if (!"".equals(autowired.value())) {
            autowiredBeanName = autowired.value().trim();
        }
        field.setAccessible(true);
        try {
            if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                continue;
            }
            field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

4.2. 5. IOC mount

To add the DefaultListableBeanDefinition class:

public class MyDefaultListableBeanFactory implements MyBeanFactory {

    //Save all configuration information
    public Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<String, MyBeanDefinition>();

    //The third level cache is also the ultimate cache
    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<String, MyBeanWrapper>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<String, Object>();
    
    @Override
    public Object getBean(String beanName) {
        //1. Get the corresponding beanDefinition configuration information first
        MyBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //2. Reflect instanced objects
        Object instance = instantiateBean(beanName, beanDefinition);
        //3. Encapsulate the returned object as a BeanWrapper
        MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
        //4. Perform dependency injection
        populateBean(beanName, beanDefinition, beanWrapper);
        //5. Save to IOC container
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);
        return beanWrapper.getWrappedInstance();
    }
}

The step here is to put the final encapsulation result into the IOC, that is, the three-level cache factoryBeanInstanceCache.

4.3. Part of DispatchServlet

After completing the construction of ApplicationContext, you can remove those unnecessary steps and replace them with ApplicationContext.

public class MyDispatchServlet extends HttpServlet {

    private Map<String, Method> handlerMapping = new HashMap<String, Method>();
    private MyApplicationContext myApplicationContext = null;

    @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 {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        myApplicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
        doHandlerMapping();
    }
}

Without the previous IOC container, many judgments will lose their basis, so you need to get the content about the container from the ApplicationContext

Increase the method getBeanDefinitionCount to obtain the length of the configuration file

Add the method getBeanDefinitionNames to obtain the key of the configuration file, that is, the name of the bean

public class MyApplicationContext implements MyBeanFactory {

    //Hold reference
    private MyDefaultListableBeanFactory registry = new MyDefaultListableBeanFactory();

    //Profile reader
    private MyBeanDefinitionReader reader = null;

    public MyApplicationContext(String... configLocations) {
        //1. Load profile
        reader = new MyBeanDefinitionReader(configLocations);
        try {
            //2. Parse the configuration file and encapsulate the configuration information into BeanDefinition objects
            List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
            //3. Cache all configuration information
            this.registry.doRegistryBeanDefinition(beanDefinitions);
            //4. Load all non deferred loaded bean s
            doLoadInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doLoadInstance() {
        //Loop call getBean method
        for (Map.Entry<String, MyBeanDefinition> entry : this.registry.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            //Whether to delay loading. If not, load immediately
            if (!entry.getValue().isLazyInit()) {
                getBean(beanName);
            }
        }
    }

    @Override
    public Object getBean(Class beanClass) {
        return this.getBean(beanClass.getName());
    }

    @Override
    public Object getBean(String beanName) {
        return this.registry.getBean(beanName);
    }

    public int getBeanDefinitionCount() {
        return this.registry.beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return this.registry.beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

So the rest of the judgment has a basis

public class MyDispatchServlet extends HttpServlet {

    private Map<String, Method> handlerMapping = new HashMap<String, Method>();
    private MyApplicationContext myApplicationContext = null;

    @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 {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        myApplicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));
        doHandlerMapping();
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws InvocationTargetException, IllegalAccessException, IOException {
        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 = handlerMapping.get(url);
        Map<String, Integer> paramMapping = new HashMap<String, Integer>();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof MyRequestParam) {
                    String value = ((MyRequestParam) annotation).value();
                    if (!"".equals(value)) {
                        paramMapping.put(value, i);
                    }
                }
            }
        }
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            if (HttpServletRequest.class == parameterType || HttpServletResponse.class == parameterType) {
                paramMapping.put(parameterType.getName(), i);
            }
        }
        Object[] paramValues = new Object[paramMapping.size()];
        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", "");
            if (!paramMapping.containsKey(param.getKey())) {
                continue;
            }
            paramValues[paramMapping.get(param.getKey())] = value;
        }

        if (paramMapping.containsKey(HttpServletRequest.class.getName())) {
            int index = paramMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if (paramMapping.containsKey(HttpServletResponse.class.getName())) {
            int index = paramMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }

        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        Object obj = myApplicationContext.getBean(beanName);
        method.invoke(obj, paramValues);
    }

    private void doHandlerMapping() {
        if (myApplicationContext.getBeanDefinitionCount() == 0) {
            return;
        }
        try {
            for (String beanName : myApplicationContext.getBeanDefinitionNames()) {
                Object instance = myApplicationContext.getBean(beanName);
                Class aClass = instance.getClass();
                if (!aClass.isAnnotationPresent(MyController.class)) {
                    continue;
                }
                String url = "";
                if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping myRequestMapping = (MyRequestMapping) aClass.getAnnotation(MyRequestMapping.class);
                    url = "/" + myRequestMapping.value();
                }
                for (Method method : aClass.getMethods()) {
                    if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                        continue;
                    }
                    MyRequestMapping myRequestMapping = (MyRequestMapping) method.getAnnotation(MyRequestMapping.class);
                    handlerMapping.put((url + "/" + myRequestMapping.value()).replaceAll("/+", "/"), method);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String toLowerFirstCase(String fileName) {
        char[] chars = fileName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}

When all is complete, execute and access: http://localhost:8080/user/name?name=lily

Topics: Java Spring Back-end source code