01-spring-手写spring-demo实现基础的功能

发布于:2025-08-12 ⋅ 阅读:(13) ⋅ 点赞:(0)

1 目标

自定义BonnieController BonnieRequestMapping、BonnieService、BonnieAutowired等注解实现一个简单的springmvc的功能。从浏览器访问url,然后请求到对应的接口,以及service等。

2 流程说明

3、代码获取途径

以下是下载地址以及分支: 如果需要自己开发,可以基于20250808-base分支,这个分支仅搭建好了基础的框架

地址 分支
 https://gitee.com/huyanqiu6666/customize-spring.git 20250808-springV1.0

4 重点代码说明

4.1 入口web.xml

在web.xml中配置了servlet接收所有请求,调用它的doGet或者doPost方法;服务启动的时候调用DispatcherServlet.init方法,在这里可以做初始化阶段的所有操作。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
        <display-name>Bonnie Web Application</display-name>

        <servlet>
                <servlet-name>bonnieMvc</servlet-name>
                <servlet-class>com.bonnie.servlet.DispatcherServlet</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>bonnieMvc</servlet-name>
                <url-pattern>/*</url-pattern>
        </servlet-mapping>
</web-app>

4.2 获取servlet配置init-param信息

// 1、加载配置文件
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
   /**
     * 加载配置文件 - application.properties
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        // 加载配置文件流
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextPropertiesConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

4.3 扫描相关类

/**
 * 所有类的全路径集合
 */
private List<String> classNameList = new ArrayList<>();
String scanPackage = contextPropertiesConfig.get("componentScan").toString();

private void doComponentScan(String scanPackage) {
        try {
            /**
             * scanPackage = com.bonnie, 转化为com/bonnie
             */
            String scanPackagePath = scanPackage.replaceAll("\\.", "/");
            URL url = this.getClass().getClassLoader().getResource("/" + scanPackagePath);
            File[] fileArray = new File(url.getFile()).listFiles();
            for (File file : fileArray) {
                // 如果是目录,则递归调用doComponentScan
                if (file.isDirectory()) {
                    String newPackage = scanPackage + "." +  file.getName();
                    doComponentScan(newPackage);
                } else {
                    if (!file.getName().endsWith(".class")) {
                        continue;
                    }
                    // 类的全路径
                    String classFullName = scanPackage + "." + file.getName().replace(".class", "");
                    System.out.println("扫描的classFullName:" + classFullName);
                    classNameList.add(classFullName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

4.4 将类放入到IOC容器中

/**
 * 简化IOC容器
 */
private Map<String,Object> iocMap = new ConcurrentHashMap<>();
 /**
     * 初始化扫描到的所有类,放到到IOC容器中
     */
    private void doInstance() {
        if (CollectionUtils.isEmpty(classNameList)) {
            return;
        }
        try {
            for (String className : classNameList) {
                Class<?> clazz = Class.forName(className);
                /**
                 * 只有被自定义注解的类才可以被加载到IOC容器中
                 */

                if (clazz.isAnnotationPresent(BonnieController.class)) {
                    IocLoadUtils.loadController(clazz, iocMap);
                    System.out.println("ioc 扫描到: " + clazz.getName());
                } else if (clazz.isAnnotationPresent(BonnieService.class)) {
                    IocLoadUtils.loadService(clazz, iocMap);
                    System.out.println("ioc 扫描到: " + clazz.getName());
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
package com.bonnie.utils;

import com.bonnie.annotations.BonnieService;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

public class IocLoadUtils {

    public static void loadController(Class<?> clazz, Map<String, Object> iocMap) throws InstantiationException, IllegalAccessException {
        Object object = clazz.newInstance();
        String beanName = toLowerFirstCase(clazz.getSimpleName());
        iocMap.put(beanName, object);
    }

    public static void loadService(Class<?> clazz, Map<String, Object> iocMap) throws InstantiationException, IllegalAccessException {
        BonnieService bonnieService = clazz.getAnnotation(BonnieService.class);
        String beanName = bonnieService.value().trim();
        if (StringUtils.isEmpty(beanName)) {
            beanName = toLowerFirstCase(clazz.getSimpleName());
        }
        Object object = clazz.newInstance();
        iocMap.put(beanName, object);

        // 根据类型自动赋值 接口
        for (Class<?> classInterface : clazz.getInterfaces()) {
            if (iocMap.containsKey(classInterface.getName())) {
                throw new RuntimeException(classInterface.getName() + " 已经在IOCMap中");
            }
            iocMap.put(classInterface.getName(), object);
        }
    }

    private static String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        //之所以加,是因为大小写字母的ASCII码相差32,
        // 而且大写字母的ASCII码要小于小写字母的ASCII码
        //在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
        chars[0] += 32;
        return String.valueOf(chars);
    }

}

4.5 依赖注入

/**
     * 依赖注入
     */
    private void doBonnieAutowired() {
        if (iocMap.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entries = iocMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            Object beanInstance = entry.getValue();

            // 获取类中的所有字段(成员变量)
            Field[] declaredFields = beanInstance.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (!field.isAnnotationPresent(BonnieAutowired.class)) {
                    continue;
                }

                // 如果指定bean名称,则使用指定的,否则使用类的名称
                String autowiredBeanName = field.getAnnotation(BonnieAutowired.class).value().trim();
                if (StringUtils.isEmpty(autowiredBeanName)) {
                    autowiredBeanName = field.getType().getName();
                }
                if (!iocMap.containsKey(autowiredBeanName)) {
                    throw new RuntimeException("Ioc容器中不存在类,无法依赖注入:" + autowiredBeanName);
                }

                // 开启暴力访问
                field.setAccessible(true);

                try {
                    // 用反射机制,动态给字段赋值
                    field.set(beanInstance, iocMap.get(autowiredBeanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }
    }

4.6 初始化handlerMapping

/**
 * requestHandler集合
 */
private List<Handler> requestHandlerMapping = new ArrayList<Handler>();
/**
     * 初始化HandlerMapping
     */
    private void initHandlerMapping() {
        if (iocMap.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entries = iocMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            Class<?> beanClazz = entry.getValue().getClass();
            // 只扫描被注解修饰的类BonnieController
            if (!beanClazz.isAnnotationPresent(BonnieController.class)) {
                continue;
            }

            String baseUrl = StringUtils.EMPTY;
            if (beanClazz.isAnnotationPresent(BonnieRequestMapping.class)) {
                BonnieRequestMapping requestMapping = beanClazz.getAnnotation(BonnieRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            // 获取所有的public方法
            Method[] methods = beanClazz.getMethods();
            for (Method method : methods) {
                BonnieRequestMapping methodRequestMapping = method.getAnnotation(BonnieRequestMapping.class);
                if (Objects.isNull(methodRequestMapping)) {
                    // 先不抛出异常
                    continue;
                }

                String regex = ("/" + baseUrl + "/" + methodRequestMapping.value())
                        .replaceAll("/+","/");
                Pattern pattern = Pattern.compile(regex);
                System.out.println("扫描到的请求url: " + pattern);
                Handler handler = new Handler();
                handler.setPattern(pattern);
                handler.setController(entry.getValue());
                handler.setMethod(method);
                handler.setParamTypes(method.getParameterTypes());
                handler.putParamIndexMapping(method);
                requestHandlerMapping.add(handler);
            }
        }

    }

4.7 运行阶段

doGet方法使用doPost方法

  @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();
        }
    }
 /**
     * 处理请求:
     *  1、解析请求的url获取对应的handler
     *  2、解析请求的入参,参数列表,参数类型 以及request response
     *  3、反射调用获取结果
     *  4、返回对应的结果到前端
     * @param req
     * @param resp
     * @throws Exception
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 获取对应的handler
        Handler handler = RequestHandlerMappingUtils.getHandler(req, requestHandlerMapping);
        if (Objects.isNull(handler)) {
            resp.getWriter().write("404 Not Found!!!");
            return;
        }

        // 方法的形参列表
        Class<?>[] paramTypes = handler.getParamTypes();
        // 定义参数列表
        Object [] paramValues = new Object[paramTypes.length];

        Map<String,String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","")
                    .replaceAll("\\s",",");
            Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
            if (!paramIndexMapping.containsKey(param.getKey())) {
                continue;
            }
            int index = handler.getParamIndexMapping().get(param.getKey());
            paramValues[index] = convert(paramTypes[index],value);
        }
        if(handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if(handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }

        // 反射调用,获取结果
        Object returnValue = handler.getMethod().invoke(handler.getController(),paramValues);
        if (returnValue == null || returnValue instanceof Void){
            return;
        }
        resp.getWriter().write(returnValue.toString());
    }

    private Object convert(Class<?> paramType, String value) {
        //如果是int
        if(Integer.class == paramType){
            return Integer.valueOf(value);
        }
        else if(Double.class == paramType){
            return Double.valueOf(value);
        }
        else if (Long.class == paramType) {
            return Long.parseLong(value);
        }
        //如果还有double或者其他类型,继续加if
        // 可以使用策略模式了
        return value;
    }
package com.bonnie.utils;

import com.bonnie.entity.Handler;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.regex.Matcher;

public class RequestHandlerMappingUtils {

    /**
     * 获取请求对应的handler
     *
     * @param req
     * @param requestHandlerMapping
     * @return
     */
    public static Handler getHandler(HttpServletRequest req, List<Handler> requestHandlerMapping) {
        // 绝对路径
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 相对路径
        String url = requestURI.replaceAll(contextPath, "").replaceAll("/+", "/");
        for (Handler handler : requestHandlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }

}

5 运行结果

5.1 测试类

package com.bonnie.controller;

import com.bonnie.annotations.BonnieAutowired;
import com.bonnie.annotations.BonnieController;
import com.bonnie.annotations.BonnieRequestMapping;
import com.bonnie.annotations.BonnieRequestParam;
import com.bonnie.service.UserService;

@BonnieController
@BonnieRequestMapping("/user")
public class UserController {

    @BonnieAutowired
    private UserService userService;

    @BonnieRequestMapping("/user")
    public String findById(@BonnieRequestParam("id") Long id) {
        return userService.findById(id);
    }

}
package com.bonnie.service;

import com.bonnie.annotations.BonnieService;

import java.text.SimpleDateFormat;
import java.util.Date;

@BonnieService
public class UserServiceImpl implements UserService{

    @Override
    public String findById(Long id) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String now = simpleDateFormat.format(new Date());
        return "userId:" + id + " Date: "+ now;
    }

}

5.2 启动

启动部分日志

扫描的classFullName:com.bonnie.annotations.BonnieAutowired
扫描的classFullName:com.bonnie.annotations.BonnieController
扫描的classFullName:com.bonnie.annotations.BonnieRequestMapping
扫描的classFullName:com.bonnie.annotations.BonnieRequestParam
扫描的classFullName:com.bonnie.annotations.BonnieService
扫描的classFullName:com.bonnie.controller.UserController
扫描的classFullName:com.bonnie.entity.Handler
扫描的classFullName:com.bonnie.service.UserService
扫描的classFullName:com.bonnie.service.UserServiceImpl
扫描的classFullName:com.bonnie.servlet.DispatcherServlet
扫描的classFullName:com.bonnie.servlet.MyServlet
扫描的classFullName:com.bonnie.utils.IocLoadUtils
扫描的classFullName:com.bonnie.utils.RequestHandlerMappingUtils
ioc 扫描到: com.bonnie.controller.UserController
ioc 扫描到: com.bonnie.service.UserServiceImpl
扫描到的请求url: /user/user
Bonnie Spring framework is init.
11-Aug-2025 15:01:38.596 信息 [main] org.apache.catalina.startup.HostConfig.deployDescriptor 部署描述符[C:\Users\L14\.SmartTomcat\customize-spring\customize-spring\conf\Catalina\localhost\customize-spring.xml]的部署已在[2,704]ms内完成
11-Aug-2025 15:01:38.618 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
11-Aug-2025 15:01:38.873 信息 [main] org.apache.catalina.startup.Catalina.start [3255]毫秒后服务器启动
http://localhost:8080/customize-spring
 

5.3 运行结果