手写一个Spring框架?

本文档记录了从创建maven项目开始,逐步构建一个简易Spring框架的过程,包括配置阶段、初始化阶段和运行阶段。作者通过自定义DispatcherServlet、注解、配置文件等方式,实现了IOC、DI和MVC的基本功能,帮助读者更好地理解Spring框架的设计思想。
摘要由CSDN通过智能技术生成


昨晚听了咕泡学院tom老师手写Spring框架的直播课,今天自己也来完成一下,构建一个简易的Spring框架,以助于更好的理解Spring的 IOC、DI、MVC的设计思想。

一、 构建思路

在这里插入图片描述
创建一个maven项目:

二、 配置阶段

  1. 引入pom依赖
    我们只引入servlet-api依赖
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
  1. 自定义DispatcherServlet类,重写 init()、doPost()、doGet()方法
public class MyDispatcherServlet extends HttpServlet {

    public MyDispatcherServlet (){
        super();
    }

    /**
     * 初始化:加载配置文件
     */
    @Override
    public void init() throws ServletException {

    }

    @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 {
        super.doPost(req, resp);
    }
}
  1. 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>Chen Web Application</display-name>

    <servlet>
        <servlet-name>Chenmvc</servlet-name>
        <servlet-class>com.chen.servlet.MyDispatcherServlet</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>Chenmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>
  1. 编写 application.properties
scanPackage=com.chen.demo
  1. 自定义注解
  • Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
    
    String value() default "";
    
}
  • RequstMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    
    String value() default "";
    
}
  • Service
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
    
    String value() default "";
    
}
  • Autowired
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
    
    String value()default "";
    
}
  • RequestParam
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
    
    String value() default "";
    
}
  1. 编写controller和service层,并使用我们自定义的注解
@MyController
@MyRequestMapping("/demo")
public class DemoController {

    @MyAutowired
    private MyDemoService demoService;

    @MyRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,
                      @MyRequestParam("name") String name){
        String s = demoService.get(name);
        try {
            resp.getWriter().write(s);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

业务层:

public interface MyDemoService {
    
    String get(String name);
    
}
@MyService
public class DemoService implements MyDemoService {

    public String get(String name) {
        return "My name is:" + name;
    }
}

三、 初始化阶段

public class MyDispatcherServlet extends HttpServlet {

    // 和web.xml中的param-name一致
    private static final String LOCATION = "contextConfigLocation";

    // 保存所有的配置信息
    private Properties properties = new Properties();

    // 保存所有被扫描到的相关类名
    private List<String> classNames = new ArrayList<String>();

    // 初始化所有Bean
    private Map<String,Object> ioc = new HashMap<String, Object>();

    // 保存所有的url和方法,创建映射关系
    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    public MyDispatcherServlet (){ super(); }

    /**
     * 初始化:加载配置文件
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1. 加载配置文件
        doLoadConfig(config.getInitParameter(LOCATION));

        // 2. 扫描所有相关的类
        doScanner(properties.getProperty("scanPackage"));

        // 3. 初始化所有相关类的实例,保存至ioc容器中
        doInstance();

        // 4. 依赖注入
        doAutowired();

        // 5. 构造HandlerMapping
        initHandlerMapping();

        // 6. 等待请求,匹配url,调用doGet();或者doPost();方法

        // 提示信息
        System.out.println("Chen mvcframework is init...");
    }

    /**
     *  将RequestMapping中配置的信息和Method进行关联,并保存这些关系。
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) { return; }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(MyController.class)) {continue;}
            String baseUrl = "";
            // 获取controller的url配置
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping requestMapping = aClass.getAnnotation(MyRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            // 获取Method的url配置
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                // 如果没有RequestMapping直接跳过
                if (!method.isAnnotationPresent(MyRequestMapping.class)) { continue; }
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value())
                        .replaceAll("/+","/");
                handlerMapping.put(url,method);
                System.out.println("mapped" + url + method);
            }
        }
    }

    /**
     * 将初始化到ioc容器里的类,需要赋值的字段进行赋值
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {return;}

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (! field.isAnnotationPresent(MyAutowired.class)) {continue;}

                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    beanName = field.getType().getName();
                }
                // 开启私有属性访问权限
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    /**
     * 初始化所有相关的类,并放入到IOC容器之中;
     * IOC容器的key默认是类名首字母小写;
     * 如果是自己设置类名,则优先使用自定义的。
     */
    private void doInstance() {
        if (classNames.size() == 0) {return;}
        try {
             for (String className : classNames) {
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(MyController.class)) {
                    // 默认将首字母小写作为 beanName
                    String beanName = firstLetterLowercase(aClass.getSimpleName());
                    ioc.put(beanName,aClass.newInstance());
                } else if (aClass.isAnnotationPresent(MyService.class)) {
                    MyService service = aClass.getAnnotation(MyService.class);
                    String beanName = service.value();
                    // 如果用户自己设置的名字,直接用就行
                    if (!"".equals(beanName.trim())) {
                        ioc.put(beanName,aClass.newInstance());
                        continue;
                    }
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> i : interfaces) {
                        ioc.put(i.getName(),aClass.newInstance());
                    }
                }  else {
                    continue;
                }
            }
         } catch (Exception e) {
            e.printStackTrace();
         }
    }

    /**
     * 处理类名首字母小写的方法
     */
    private String firstLetterLowercase(String s){
        char[] chars = s.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doScanner(String packageName) {
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            // 如果是文件夹,则递归获取
            if (file.isDirectory()){
                doScanner(packageName + "." + file.getName());
            } else {
                classNames.add(packageName + "." + file.getName().replace(".class","").trim());
            }
        }
    }

    private void doLoadConfig(String location) {
        InputStream is = null;
        // 通过反射获取文件路径,读取配置文件
        try {
            is = this.getClass().getClassLoader().getResourceAsStream(location);
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null){is.close();}
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @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) {
            resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace())
            .replaceAll("\\[|\\]","").replaceAll(",\\s","\r\n"));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        if (this.handlerMapping.isEmpty()) {return;}

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");

        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Found!");
            return;
        }

        Method method = this.handlerMapping.get(url);
        // 获取方法参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 获取请求参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 保存参数
        Object[] paramValues = new Object[parameterTypes.length];
        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) {
                for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                    String value = Arrays.toString(entry.getValue())
                            .replaceAll("\\[|\\]","")
                            .replaceAll(",\\s",",");
                    paramValues[i] = value;
                }
            }
        }
        try {
            String beanName = firstLetterLowercase(method.getDeclaringClass().getSimpleName());
            // 反射
            method.invoke(this.ioc.get(beanName),paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

四、 运行阶段

配置自己的服务器(Tomcat),然后运行项目:

运行的输出结果:
在这里插入图片描述
访问页面:
在这里插入图片描述
修改值:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值