手写Mini版Spring实现的基本思路

2022-09-13  本文已影响0人  AC编程
手写Mini版Spring实现的基本思路

github-demo源码

alanchenyan/alanchen-mvc

一、实现功能

手写实现简单Spring:MVC、IOC、DI功能。

二、说明

该项目需要部署在tomcat等容器中运行。

三、SpringMVC(alanchen-mvc)加载顺序

四、init核心代码

@Override
public void init(ServletConfig config) {

    // 1、加载配置文件
    doLoadConfig(config);

    // 2、扫描所有的class文件
    doScanPackage(contextConfig.getProperty("scanPackage"));

    // 3、初始化IOC容器,将扫描到的类实例化,保存到IOC容器中(IOC部分)
    doInstance();

    // TODO 在DI之前完成AOP(新生成的代理对象)

    // 4、完成依赖注入(DI部分)
    doAutowired();

    // 5、映射访问路径和方法的关系(MVC部分)
    initHandlerMapping();
}

五、pom.xml

加入javax.servlet依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.alanchen</groupId>
    <artifactId>alanchen-mvc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

六、WEB-INF/web.xml配置

配置Servlet

<?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/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    <display-name>alanchen-mvc</display-name>
    
    <servlet>
        <servlet-name>AlanChenDispatcherServlet</servlet-name>
        <servlet-class>com.spring.alanchen.myspring.servlet.AlanChenDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>
        <!-- 表示容器在应用启动时就加载并初始化这个servlet,实例化并调用其init()方法 -->
        <load-on-startup>0</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>AlanChenDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
</web-app>

七、代码明细

7.1 手写Spring核心代码
package com.spring.alanchen.myspring.servlet;

import java.lang.reflect.Method;

/**
 * @author Alan Chen
 * @description
 * @date 2020-07-28
 */
public class Handler {

    private Object controller;
    
    private Method method;

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }
}


/**
 * @author Alan Chen
 * @description
 * 
 *  SpringMVC加载顺序 1、tomcat 2、web.xml 3、DispatchServlet
 * 
 *  MVC作为入口 启动IOC容器 完成DI
 * 
 * @date 2020-07-28
 */
public class AlanChenDispatcherServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    private Properties contextConfig = new Properties();

    private List<String> classNames = new ArrayList<String>();

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

    private Map<String, Handler> handlerMapping = new HashMap<String, Handler>();

    @Override
    public void init(ServletConfig config) {

        // 1、加载配置文件
        doLoadConfig(config);

        // 2、扫描所有的class文件
        doScanPackage(contextConfig.getProperty("scanPackage"));

        // 3、初始化IOC容器,将扫描到的类实例化,保存到IOC容器中(IOC部分)
        doInstance();

        // TODO 在DI之前完成AOP(新生成的代理对象)

        // 4、完成依赖注入(DI部分)
        doAutowired();

        // 5、映射访问路径和方法的关系(MVC部分)
        initHandlerMapping();

        System.out.println("alanchenMVC is init.");
    }

    /**
     * 加载配置文件
     * 
     * @param config
     */
    private void doLoadConfig(ServletConfig config) {
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        InputStream inStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(inStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inStream != null) {
                try {
                    inStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        doDispatch(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        doDispatch(req, resp);
    }

    /**
     * 6、委派,通过URL去找到一个对应的Method并通过Response返回
     * 
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        // uri=/alanchen-mvc/user/query
        String uri = req.getRequestURI();
        
        //context=/alanchen-mvc
        String context = req.getContextPath();
        
        //path=/user/query
        String path = uri.replaceAll(context, "").replaceAll("/+", "/");
        
        if(!handlerMapping.containsKey(path)) {
            writeResult(resp,"404 NOT FOUND");
            return;
        }

        Handler handler = handlerMapping.get(path);
        Method method = handler.getMethod();
        Object controller = handler.getController();

        Object arg[] = handArg(req, resp, method);
        Object result=null;
        try {
            result = method.invoke(controller, arg);
        }catch(Exception e) {
            writeResult(resp,"500 服务器异常");
            e.printStackTrace();
        }
        
        if(result!=null) {
            writeResult(resp,result);
        }else {
            System.out.println("没有返回值");
        }
    }
    
    /**
     * 将返回值写回客户端
     * @param resp
     * @param result
     */
    private void writeResult(HttpServletResponse resp,Object result) {
        PrintWriter pw = null;
        try {
            String obj = String.valueOf(result);
            pw = resp.getWriter();
            pw.write(obj);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }

    /**
     * 获取请求参数值
     * 
     * @param req
     * @param resp
     * @param method
     * @return
     */
    private Object[] handArg(HttpServletRequest req, HttpServletResponse resp, Method method) {
        //获取形参列表
        Class<?>[] paramClazzs = method.getParameterTypes();
        Object[] args = new Object[paramClazzs.length];

        int args_i = 0;
        int index = 0;

        for (Class<?> paramClazz : paramClazzs) {
            if (ServletRequest.class.isAssignableFrom(paramClazz)) {
                args[args_i++] = req;
            }

            if (ServletResponse.class.isAssignableFrom(paramClazz)) {
                args[args_i++] = resp;
            }

            Annotation[] paramAns = method.getParameterAnnotations()[index];
            if (paramAns.length > 0) {
                for (Annotation paramAn : paramAns) {
                    if (AlanChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                        AlanChenRequestParam rp = (AlanChenRequestParam) paramAn;
                        args[args_i++] = req.getParameter(rp.value());
                    }
                }
            }
            index++;
        }
        return args;
    }

    /**
     * 映射访问路径和方法的关系
     */
    private void initHandlerMapping() {
        if (beans.entrySet().size() == 0) {
            System.out.println("实例化的类数量为0");
            return;
        }

        try {
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                Object instance = entry.getValue();
                Class<?> clazz = instance.getClass();
                
                //用取反的方式减少if嵌套层数
                if (!clazz.isAnnotationPresent(AlanChenController.class)) {
                    continue;
                }
                
                AlanChenRequestMapping mapping = clazz.getAnnotation(AlanChenRequestMapping.class);
                String classPath = "/"+mapping.value();
        
                /**
                 * clazz.getDeclaredMethods() 是获取所有方法
                 * clazz.getMethods() 只获取public修饰的方法
                 * Spring也只处理public的方法
                 */
                Method[] methods = clazz.getMethods();
                
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(AlanChenRequestMapping.class)) {
                        continue;
                    }
                    
                    AlanChenRequestMapping methodMapping = method.getAnnotation(AlanChenRequestMapping.class);
                    String methodPath = "/"+methodMapping.value();

                    Handler handler = new Handler();
                    handler.setController(instance);
                    handler.setMethod(method);
                    
                    //将多个重复的斜杠替换成一个斜杠
                    String url = (classPath + methodPath).replaceAll("/+", "/");
                    handlerMapping.put(url, handler);
                    
                    System.out.println("url="+url+";method="+method.getName());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 完成依赖注入(DI)
     */
    private void doAutowired() {
        if (beans.entrySet().size() == 0) {
            System.out.println("实例化的类数量为0");
            return;
        }

        try {
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                Object instance = entry.getValue();
                Class<?> clazz = instance.getClass();
                
                //获取所有属性,包括private修饰的属性
                Field[] fileds = clazz.getDeclaredFields();

                for (Field field : fileds) {
                    if (!field.isAnnotationPresent(AlanChenAutowired.class)) {
                        continue;
                    }
                    
                    AlanChenAutowired autowired = field.getAnnotation(AlanChenAutowired.class);

                    // 即使属性是private,也强制设置为可访问
                    field.setAccessible(true);

                    // 1、优先通过用户配置的value去取对象
                    String beanName = autowired.value();
                    if (beanName.isEmpty()) {
                        // 2、通过类名首字母小写取对象
                        beanName = field.getName();
                    }

                    Object obj = beans.get(beanName);
                    // 如果通过名称无法获取到对象,则通过类型获取
                    if (obj == null) {
                        // 3、通过接口类型取对象,注入接口的实现类
                        beanName = field.getType().getName();
                        obj = beans.get(beanName);
                    }
                    field.set(instance, obj);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化IOC容器,将扫描到的类实例化,保存到IOC容器中(IOC部分)
     */
    private void doInstance() {
        if (classNames.size() == 0) {
            System.out.println("扫描到的class文件数量为0");
            return;
        }

        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);

                if (clazz.isAnnotationPresent(AlanChenController.class)) {
                    // 初始化Controller类
                    Object instace = clazz.newInstance();
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    beans.put(beanName, instace);
                } else if (clazz.isAnnotationPresent(AlanChenService.class)) {
                    // 初始化Service类
                    Object instace = clazz.newInstance();
                    AlanChenService service = clazz.getAnnotation(AlanChenService.class);

                    // 1、优先使用用户配置的value值做为key
                    String beanName = service.value();
                    if (beanName.isEmpty()) {
                        // 2、如果没有配置value值,则用类名首字母小写的类名做为key
                        beanName = lowerFirstCase(clazz.getSimpleName());
                    }
                    System.out.println("beanName=" + beanName);
                    beans.put(beanName, instace);

                    // 3、用实现接口的类型名称做为key,注入接口的实现类
                    Class<?>[] intefaces = clazz.getInterfaces();
                    for (Class inteface : intefaces) {
                        System.out.println("inteface=" + inteface.getName());
                        beans.put(inteface.getName(), instace);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 类名首字母转小写
     * 
     * @param str
     * @return
     */
    private String lowerFirstCase(String str) {
        if(str == null || str.isEmpty()) {
            return str;
        }
        
        /**
         * 首字母是大写才转小写,否则不需要处理
         * 大写字母范围:65-90
         */
        char[] chars = str.toCharArray();
        char first = chars[0];
        if(first>=65 && first <=90) {
            chars[0] += 32;
            return String.valueOf(chars);
        }
        return str;
    }

    /**
     * 扫描所有的class文件
     * 
     * @param basePackage
     */
    private void doScanPackage(String basePackage) {
        //硬盘上的目录是用斜杠/分割,因此要将.替换成斜杠/
        URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
        
        //fileStr=D://alanchen-mvc/WEB-INF/classes/com/spring/alanchen/
        String fileStr = url.getFile();

        File file = new File(fileStr);
        String[] filesStr = file.list();
        for (String path : filesStr) {
            File filePath = new File(fileStr + path);
            if (filePath.isDirectory()) {
                // 递归直到找到文件为止
                doScanPackage(basePackage + "." + path);
            } else {
                if(filePath.getName().endsWith(".class")) {
                    classNames.add(basePackage + "." + filePath.getName().replace(".class", ""));
                }
            }
        }
    }
}
7.2 annaotation
package com.spring.alanchen.myspring.annaotation;

import java.lang.annotation.*;

@Target({ElementType.FIELD}) // 该注解用在类的成员变量上
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
@Documented // 该注解包含到javadoc中

public @interface AlanChenAutowired {

    String value() default "";
}


@Target({ElementType.TYPE}) // 该注解用在类上
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
@Documented // 该注解包含到javadoc中
// @Inherited 该注解可被继承

public @interface AlanChenController {

    String value() default "";
}


@Target({ElementType.TYPE,ElementType.METHOD}) // 该注解用在类上、方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
@Documented // 该注解包含到javadoc中

public @interface AlanChenRequestMapping {

    String value() default "";
}


@Target({ElementType.PARAMETER}) // 该注解用参数上
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
@Documented // 该注解包含到javadoc中

public @interface AlanChenRequestParam {

    String value() default "";
}


@Target({ElementType.TYPE}) // 该注解用在类上
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
@Documented // 该注解包含到javadoc中

public @interface AlanChenService {

    String value() default "";
}
7.3 service
package com.spring.alanchen.service;

/**
 * @author Alan Chen
 * @description
 * @date 2020-07-28
 */
public interface IUserSerivce {

    String query(String name,String age);
}

@AlanChenService
public class UserSerivceImpl implements IUserSerivce {

    public String query(String name, String age) {
        return "name="+name+";age"+age;
    }
}
7.4 controller
package com.spring.alanchen.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.spring.alanchen.myspring.annaotation.AlanChenAutowired;
import com.spring.alanchen.myspring.annaotation.AlanChenController;
import com.spring.alanchen.myspring.annaotation.AlanChenRequestMapping;
import com.spring.alanchen.myspring.annaotation.AlanChenRequestParam;
import com.spring.alanchen.service.IUserSerivce;


/**
 * @author Alan Chen
 * @description 访问:http://127.0.0.1:8080/alanchen-mvc/user/query?name=ac&age=18
 * @date 2020-07-29
 */
@AlanChenController
@AlanChenRequestMapping("/user")
public class UserController {

    @AlanChenAutowired
    IUserSerivce userSerivceImpl;

    @AlanChenRequestMapping("query")
    public String query(HttpServletRequest request, HttpServletResponse response,
            @AlanChenRequestParam("name") String name, @AlanChenRequestParam("age") String age) {
        
        return userSerivceImpl.query(name, age);
    }
}
7.5 application.properties
scanPackage=com.spring.alanchen
上一篇下一篇

猜你喜欢

热点阅读