手写一个SpringMVC

网上很多关于springmvc的分析,但很少有手动实现的,这里通过一个简单的demo实现mvc基本功能,仅供学习参考。

我们先简单了解下springmvc请求流程,如下图:
《手写一个SpringMVC》
从图上得知,最先处理请求的是dispatcherServlet,它接受请求并查询得到拦截器链HandlerExecutionChain,HandlerAdapter经过适配器调用具体的处理器(Controller).

查看源码可以到spring-webmvc.jar中,org.springframework.web.servlet/DispatcherServlet.class,其中方法doDispatch()完成了一个请求到返回数据的完整操作.

下面我们开始动手了,先创建一个javaweb工程,写一个Servlet,如下:

@WebServlet(urlPatterns = "/*", loadOnStartup = 1)
public class DispatcherServlet extends HttpServlet {

    private List<String> clzList = new ArrayList<>();
    private Map<String, Object> beansMap = new HashMap<>();
    private Map<String, Object> urlMapping = new HashMap<>();

    @Override
    public void init() throws ServletException {
        try {
            //扫描配置  这里是包名
            scanPackages("com.iti.smvc");
            //实例化对象
            doInstance();
            //建立对象之间的依赖ioc
            ioc();
            //建立url到controller的映射
            doMapping();

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

注:servlet3.0不再需要web.xml, 这里将成员变量写入servlet并不是很好的实现,会导致线程不安全
这个servlet要做一些初始化的工作,如:
1.扫描包名,将class对象装入clzList列表
2.遍历clzList列表,实例化有Controller和Service标注的类
3.依赖注入,将service注入到controller
4.建立url与controller中方法url的mapping关系

我们依次来实现他们:
下面的是扫描配置方法

private void scanPackages(String packageUrl) {
        String fileUrl = getClass().getClassLoader().getResource("/"+packageUrl.replaceAll("\\.", "/")).getFile();
        File scanfile = new File(fileUrl);
        String[] fileList = scanfile.list();
        for (String fileName: fileList) {
            File file = new File(fileUrl+fileName);
            if (file.isDirectory()) {
                scanPackages(packageUrl + "." + fileName);;
            } else {
                clzList.add(packageUrl + "." + fileName.replace(".class", ""));
            }
        }
    }

接着是对象实例化:

private void doInstance() throws Exception{
        if (clzList.size()>0) {
            for (String clzName : clzList) {
                Class<?> clzClass = Class.forName(clzName);
                if (clzClass.isAnnotationPresent(Controller.class)) {
                    //for controller
                    RequestMapping rm = clzClass.getAnnotation(RequestMapping.class);
                    beansMap.put(rm.value(), clzClass.newInstance());
                } else if (clzClass.isAnnotationPresent(Service.class)) {
                    Service service = clzClass.getAnnotation(Service.class);
                    beansMap.put(service.value(), clzClass.newInstance());
                }
            }
        }
    }

接着是依赖注入,其实是反射:

private void ioc() throws Exception{
        if (beansMap.size()>0) {
            for (Map.Entry<String, Object> entry : beansMap.entrySet()) {
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Autowired.class)) {
                        Qualifier anno = field.getAnnotation(Qualifier.class);
                        field.set(entry.getValue(), beansMap.get(anno.value()));
                    }
                }
            }
        }
    }

最后是建立请求url与controller的mapping关系,如下:

private void doMapping() {
        if (beansMap.size()>0) {
            for (Map.Entry<String, Object> entry: beansMap.entrySet()) {
                Class<? extends Object> obj = entry.getValue().getClass();
                if (obj.isAnnotationPresent(Controller.class)){
                    RequestMapping rm = obj.getAnnotation(RequestMapping.class);
                    Method[] methods = obj.getMethods();
                    for (Method method: methods) {
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping anno = method.getAnnotation(RequestMapping.class);
                            urlMapping.put(rm.value() + anno.value(), method);
                        }
                    }
                }
            }
        }
    }

还要把注解创建下:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Qualifier {
    String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

此时就可以启动服务完成初始化工作了
下面我们要创建一个sevice方法来接收url请求

@Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI(); 
        String contextPath = req.getContextPath(); 
        //得到请求地址
        String path = requestURI.replace(contextPath, "");
        Method method = (Method)urlMapping.get(path);
        if (method != null) {
            try {
                Object obj = method.invoke(beansMap.get("/"+path.split("/")[1]));
                resp.getOutputStream().write(obj.toString().getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

上面是得到请求url,然后从urlMapping得到要调用的method,再通过反射调用,最后通过outputStream输出到浏览器上。

下面编写一个controller和service测试下
controller代码如下:

@Controller
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    @Qualifier("helloservice")
    Helloservice helloservice;

    @RequestMapping("/sayHello")
    public String sayHello() {
        //System.out.println(helloservice.hello());
        return "controller";
    }
}

service代码如下:

@Service("helloservice")
public class Helloservice {
    public String hello() {
        return "hello";
    }
}

请求url:http://localhost:8080/hello/sayHello
学习交流,欢迎加群:64691032

    原文作者:weistar103
    原文地址: https://segmentfault.com/a/1190000016265738
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞