资讯专栏INFORMATION COLUMN

手写一个SpringMVC

ernest.wang / 2455人阅读

摘要:网上很多关于的分析,但很少有手动实现的,这里通过一个简单的实现基本功能,仅供学习参考。下面编写一个和测试下代码如下代码如下请求学习交流,欢迎加群

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

我们先简单了解下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 clzList = new ArrayList<>();
    private Map beansMap = new HashMap<>();
    private Map 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 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 entry: beansMap.entrySet()) {
                Class 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76973.html

相关文章

  • 我对 SpringMVC 的一些误解

    摘要:引言刚考完期末,再也不用考试啦最近学习了慕课网的实战课手写,剑指开源框架灵魂。最近将本课程和看透结合起来学习,感觉受益匪浅,同时,纠正了我之前对的一些误解。误解洪荒时代的当年,开发都需要手动去实现。为了解决太多的问题,引入了,进行统一调度。 引言 刚考完期末,再也不用考试啦!!! 最近学习了慕课网的实战课《手写SpringMVC,剑指开源框架灵魂》。 showImg(https://s...

    seanlook 评论0 收藏0
  • SpringMVC入门就这么简单

    摘要:也就是说映射器就是用于处理什么样的请求提交给处理。这和是一样的提交参数的用户名编号提交配置处理请求注册映射器包框架接收参数设置无参构造器,里边调用方法,传入要封装的对象这里的对象就表示已经封装好的了对象了。 什么是SpringMVC? SpringMVC是Spring家族的一员,Spring是将现在开发中流行的组件进行组合而成的一个框架!它用在基于MVC的表现层开发,类似于struts...

    SKYZACK 评论0 收藏0
  • SpringMVC【校验器、统一处理异常、RESTful、拦截器】

    摘要:只要有一个拦截器不放行,不能执行完成号不放行和号不放行测试结果总结只有前边的拦截器方法放行,下边的拦截器的才执行。至于他们的拦截器链的调用顺序,和的是没有差别的。 前言 本博文主要讲解的知识点如下: 校验器 统一处理异常 RESTful 拦截器 Validation 在我们的Struts2中,我们是继承ActionSupport来实现校验的...它有两种方式来实现校验的功能 手写...

    marser 评论0 收藏0
  • Spring+SpringMVC+Maven+Mybatis+MySQL+Jetty项目搭建(1)

    摘要:接口声明并实现接口声明一个接口新建一个类,并实现接口单元测试单元测试是为了验证第步中接口的方法。中新增类使用实现单元测试指定注入的配置文件使用标准的注释来告诉使用在中新增类文件运行单元测试右键运行结果到此,我们已经搭建了一个基于的项目环境。 本文详细讲述如何搭建一个Spring+SpringMVC+Maven+Mybatis+MySQL项目环境。eclipse、maven 及 mysq...

    KoreyLee 评论0 收藏0

发表评论

0条评论

ernest.wang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<