手写springmvc

先上一张图了解一下springmvc流程

手写springmvc
了解了框架的原理之后,学习框架就非常的简单

本文主要实现了简易的ioc 注解主要实现了 @Autowired @Controller @RequestMapping @Service

@RequestParam,只做简单的实现

项目结构

手写springmvc

注解实现

@ChaoAutowired

@Target({ElementType.FIELD})//用于修饰在成员变量上
@Retention(RetentionPolicy.RUNTIME) //声明周期 运行时可以获得
@Documented //javadoc 可以
public @interface ChaoAutowired {
    String value() default "";
}

@ChaoController

@Target({ElementType.TYPE})//用于修饰在类上
@Retention(RetentionPolicy.RUNTIME) //声明周期 运行时可以获得
@Documented //javadoc 可以
public @interface ChaoController {
    String value() default "";
}

@ChaoRequestMapping

@Target({ElementType.METHOD,ElementType.TYPE})//用于修饰在成员变量,类上
@Retention(RetentionPolicy.RUNTIME) //声明周期 运行时可以获得
@Documented //javadoc 可以
public @interface ChaoRequestMapping {
    String value() default "";
}

@ChaoRequestParam 与 @ChaoService 与之前的都相同 这里不需要重复了

MainController

@ChaoController("mainController")
@ChaoRequestMapping("/main")
public class MainController {

    @ChaoAutowired("mainService")
    private MainService mainService;

    @ChaoRequestMapping("/query")
    public void query(HttpServletRequest request, HttpServletResponse response,@ChaoRequestParam("name") String name, @ChaoRequestParam("age") String age) {
        try {
            PrintWriter printWriter = response.getWriter();
            String result = mainService.query(name, age);
            printWriter.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里没有实现@Reponsebody 所以使用了 printwrite 实现的效果是相同的

MainService

public interface MainService {

    String query(String name,String age);

}

MainServiceImpl

@ChaoService("mainService")
public class MainServiceImpl implements MainService {
    public String query(String name, String age) {
        return "name===>"+name+";,age===>"+age;
    }
}

这里只是简单的接收两个参数,注意都是String类型的

因为后面实现参数的匹配没有进行数据类型的转换所以都是String 类型的才能够接收

否则会出现参数类型不匹配的错误

DispatcherServlet

我们知道Springmvc就是对servlet的封装,而最主要的就是这个DispatcherServlet

ioc的实现就是一个 map 我下面的实现很清楚,而且有依赖注入,也就是DI

上代码

public class DispatcherServlet extends HttpServlet {

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

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

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

    //这里声明一个controller bean的map 是为了反射调用方法的时候能够获取实例,key 与方法的url相同
    private Map<String, Object> controllerHandlerMap = new HashMap<>();

    public void init() throws ServletException {
        //把所有的bean扫描出来
        scanPackage("com.chao");

        //根据全类名 进行实例化
        doInstance();

        //进行注入
        doDI();

        bulidUrlMapping();
    }

    private void scanPackage(String packages) {
        String urlPath = "/" + packages.replaceAll("\\.", "/");
        URL url = this.getClass().getClassLoader().getResource(urlPath.replace("classes","target"));
         File file = new File(url.getFile());
        String[] files = file.list();
        for (String path : files) {
            File scanFile = new File(url.getFile() + path);
            if (scanFile.isDirectory()) {
                scanPackage(packages+"."+path);
            } else {
                classNames.add(packages + "." + scanFile.getName());
            }
        }
    }

    //进行bean的实例化
    private void doInstance() {
        if (classNames.size() <= 0) {
            System.out.println("包扫描失败");
            return;
        }

        classNames.forEach(className -> {
            try {
                Class<?> clazz = Class.forName(className.replace(".class", ""));

                if (clazz.isAnnotationPresent(ChaoController.class)) {
                    Object instance = clazz.newInstance(); //创建控制类
                    ChaoRequestMapping chaoRequestMapping = clazz.getAnnotation(ChaoRequestMapping.class);
                    String value = chaoRequestMapping.value(); //这里可以进行判断 如果为空则用类名首字母小写,这里没判断只写思路
                    beans.put(value, instance); //放到ioc容器
                } else if (clazz.isAnnotationPresent(ChaoService.class)) {
                    Object instance = clazz.newInstance(); //创建控制类
                    ChaoService chaoService = clazz.getAnnotation(ChaoService.class);
                    beans.put(chaoService.value(), instance); //放到ioc容器
                }


            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    //把service注入到控制层
    private void doDI() {
        if (beans.entrySet().size() <= 0) {
            System.out.println("无实例化的类");
        }

        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object instance = entry.getValue();
            Class<?> clazz = instance.getClass();

            if (clazz.isAnnotationPresent(ChaoController.class)) {
                Field[] fields = clazz.getDeclaredFields(); //getDeclaredFields私有属性也能够获取
                for (Field field : fields) {
                    if (field.isAnnotationPresent(ChaoAutowired.class)) {
                        ChaoAutowired chaoAutowired = field.getAnnotation(ChaoAutowired.class);
                        String value = chaoAutowired.value();
                        field.setAccessible(true); //打开私有属性的权限
                        try {
                            field.set(instance, beans.get(value));//给autowired 的属性赋值
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    private void bulidUrlMapping() {
        if (beans.entrySet().size() <= 0) {
            System.out.println("无实例化的类");
        }
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object instance = entry.getValue();
            Class<?> clazz = instance.getClass();
            if (clazz.isAnnotationPresent(ChaoController.class)) {
                ChaoRequestMapping chaoRequestMapping = clazz.getAnnotation(ChaoRequestMapping.class);
                String classUrl = chaoRequestMapping.value();
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(ChaoRequestMapping.class)) {
                        ChaoRequestMapping methodAnnotation = method.getAnnotation(ChaoRequestMapping.class);
                        String methodUrl = methodAnnotation.value();
                        String url = classUrl + methodUrl;
                        handlerMap.put(url, method); //这里的路径是需要处理的 可能是/main query 少/ 这里暂不进行处理
                        controllerHandlerMap.put(url, instance);
                    }
                }
            }
        }
    }

    @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 {
        //获取请求路径
        String uri = req.getRequestURI(); //  /myspringmvc/main/query

        if (!handlerMap.containsKey(uri)){
            resp.getWriter().write("404 NOT FOUND");
            return;
        }

        //得到项目根路径
        String contextPath = req.getContextPath(); // /myspringmvc

        String urlPath = uri.replace(contextPath, ""); // /main/query

        Method method = (Method) handlerMap.get(urlPath);

        Object[] params = hand(req, resp, method);

        try {
            method.invoke(controllerHandlerMap.get(urlPath), params);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method) {
        //拿到当前待执行的方法有哪些参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();

        Object[] args = new Object[parameterTypes.length];

        int args_i = 0;
        int index = 0;

        for (Class<?> parameterType : parameterTypes) {
            if (ServletRequest.class.isAssignableFrom(parameterType)) {
                args[args_i++] = request;
            }
            if (ServletRequest.class.isAssignableFrom(parameterType)) {
                args[args_i++] = response;
            }

            Annotation[] paramAns = method.getParameterAnnotations()[index];

            if (paramAns.length > 0) {
                for (Annotation paramAn : paramAns) {
                    if (ChaoRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                        ChaoRequestParam requestParam = (ChaoRequestParam) paramAn;
                        args[args_i++] = request.getParameter(requestParam.value());
                    }
                }
            }
            index++;
        }
        return args;
    }
}

重要地点我都进行了注释,这样看未免不方便大家可以去下载我的源码

另外还有最重要的web.xml

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">

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>com.chao.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>0</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

源码地址

另外还有我的博客地址

博客地址