手写一个简易的类springMVC

首先了解一下SpringBean的生命周期:


937513-20160507202024015-234747937.png

工程目录结构如图所示:


jjmvc.PNG

注解类JJAutowired:

package jj.mvc.anonation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.FIELD}) 
public @interface JJAutowired {
    String value() default "";
}

注解类JJController:

package jj.mvc.anonation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE}) 
public @interface JJController {
    String value() default "";
}


注解类JJRequestMapping:

package jj.mvc.anonation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE,ElementType.METHOD}) 
public @interface JJRequestMapping {
    String value() default "";
}


注解类JJService:

package jj.mvc.anonation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE}) 
public @interface JJService {
    String value() default "";
}

controller实现,我们的请求派发入口:

package jj.mvc.controller;

import jj.mvc.anonation.JJAutowired;
import jj.mvc.anonation.JJController;
import jj.mvc.anonation.JJRequestMapping;
import jj.mvc.anonation.JJService;

@JJController
@JJRequestMapping("jj/mv/controller")
public class BaseController {
    
    @JJAutowired
    private MyService myservice;
    
    @JJRequestMapping("/test1")
     public String test1(String name) {
        return "hello "+name;    
     }
     
}

简单的service接口和实现:

package jj.mvc.service;

public interface MyService {
    String doService(String name);
}

下面实现mvc核心的servlet相关:
首先是web.xml:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
<!--   org.springframework.web.servlet.DispatcherServlet -->
    <servlet>
        <servlet-name>jjmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>configMVC.properties</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>jjmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

配置文件configMVC.properties很简单,一个mvc包扫描字面量:

scanBase=jj.mvc

适配器类:

package jj.mvc.servlet;

import java.lang.reflect.Method;

public class HandlerAdapter {
    Class<?> handlerClazz ;
    Method handlerMethod;
    String[] parameters;
    public Class<?> getHandlerClazz() {
        return handlerClazz;
    }
    public void setHandlerClazz(Class<?> handlerClazz) {
        this.handlerClazz = handlerClazz;
    }
    public Method getHandlerMethod() {
        return handlerMethod;
    }
    public void setHandlerMethod(Method handlerMethod) {
        this.handlerMethod = handlerMethod;
    }
    public String[] getParameters() {
        return parameters;
    }
    public void setParameters(String[] parameters) {
        this.parameters = parameters;
    }
    
}

最重要的代码,servlet,重点看doDispatch方法实现:

package jj.mvc.servlet;

import jj.mvc.anonation.JJAutowired;
import jj.mvc.anonation.JJController;
import jj.mvc.anonation.JJRequestMapping;
import jj.mvc.anonation.JJService;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

public class JJservlet extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    private  Properties properties=new Properties() ;
    
    private  List<Class<?>> clazzList=new ArrayList<Class<?>>();
    
    private  Map<String,Object> beanMap=new HashMap<String,Object>();
    
    private  Map<String,HandlerAdapter> requestMapping=new HashMap<String,HandlerAdapter>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        super.doPost(req, resp);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        try {
            Object result=doDispatch(req,resp);
            resp.getWriter().println(result);
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
        super.init(config);
        //contextConfigLocation属性时我们在web.xml里注册过的。对应于本案例就是 "configMVC.properties"
        String contextConfigLocation=config.getInitParameter("contextConfigLocation");
        try {
            properties.load(this.getClass().getResourceAsStream(contextConfigLocation));
            //我们在configMVC.properties配置的scanBase属性值。
            String scanBase=properties.getProperty("scanBase");
            //其实就是springMVC的<context:component-scan base-package="xx.yyy.zzz" />
            //扫描包,意味着scanBase值相关的这些路径下的java文件需要我们去扫描其注解,可以理解为@Component,只不过springMVC的实现颗粒度很细,我们可以指定@Controller,@Service,@Repositoty,include,exclude等
            doScan(scanBase);
            //初始化bean,也就是常说的控制反转IOC,当然我们这里没有实现一个专门的context上下文容器去很好的管理这些bean ,另外这里都是单例模式,而springMVC可以通过@scope实现非单例模式,比如原型模式等。
            //在这里,我想说一个题外话,单例模式可以解决spring bean的@autowired属性注入循环依赖(a->b,b->c,c->a这种),原型则不行,
            // 因为单例模式在初始化时有Map维护正在初始化的类的记录/原型则没有。当然如果通过构造器驻入,无论哪种模式都会报错。
            initBean();
            //这一步就是装配,也就是常说的依赖注入DI
            doAutowired();
            //注册url和处理器(这里只是简单的controller方法)的映射关系
            doRequestMapping();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }

    public void doScan(String scanBase) {
        File file =new File(scanBase);
        //递归的扫描,是java字节码文件则把class类名加入clazzList,是文件夹则递归
        if(file.isDirectory()) {
            for(File lfile:file.listFiles()) {
                doScan(lfile.getAbsolutePath());
            }
        }else {
            clazzList.add(file.getClass());
        }
    }
    
    public void initBean() throws InstantiationException, IllegalAccessException {
        String beanName;
        String annotationValue;
        //从我们doScan注册的clazzList中遍历拿到需要初始化的bean类(@Component类)
        for(Class<?> clazz:clazzList) {
            if(clazz.isAnnotationPresent(JJController.class)) {
                //如果是@JJController注解的bean,看看有没有写value,没有就取类名,比如我们的BaseController它的beanName就是BaseController
                beanName=clazz.getSimpleName();
                annotationValue=clazz.getAnnotation(JJController.class).value().trim();
                if(!annotationValue.equals("")) {
                    beanName=annotationValue;
                }
                //初始化一个类实例放入beanMap(单例模式的实现,另外这里我们也没有实现构造器入参)
                beanMap.put(beanName, clazz.newInstance());
                
            }else if(clazz.isAnnotationPresent(JJService.class)) {
                //如果是@JJService注解的bean,看看有没有写value,没有就取类名,比如我们的MyServiceImpl它的beanName就是MyServiceImpl
                beanName=clazz.getSimpleName();
                annotationValue=clazz.getAnnotation(JJService.class).value().trim();
                if(!annotationValue.equals("")) {
                    beanName=annotationValue;
                }
                Object instance = clazz.newInstance();
                beanMap.put(beanName, instance);
                //因为在controller层,我们注入的service一般都抽象成接口,所以同样的实例我们要通过接口名做key再放一次
                for(Class<?> interFace:clazz.getInterfaces()) {
                    beanMap.put(interFace.getSimpleName(), instance);
                }
                
            }
        }
    }
    
    public void doAutowired() throws IllegalArgumentException, IllegalAccessException {
        for(Entry<String, Object> beanEntry: beanMap.entrySet()) {
            Object bean = beanEntry.getValue();
            //如果一个bean的属性有autowired注解,那么需要从beanMap中拿出来把它装配进bean
            for(Field beanFiled : bean.getClass().getFields()) {
                if(beanFiled.isAnnotationPresent(JJAutowired.class)) {
                    /*System.out.println("beanFiled:"+beanFiled.getType().getName());
                    System.out.println("beanFiled Simple:"+beanFiled.getType().getSimpleName());*/
                    //请注意,这里有个问题,如果是被@autowired()注解的bean在initBean时,其beanName不是类名,那么无法装进来,除非实现对@Qualifier注解或者@Resource的提取,这里就不讨论了
                    String beanFiledType=beanFiled.getType().getSimpleName();
                    //反射装配
                    beanFiled.set(bean, beanMap.get(beanFiledType));
                }
            }
        }
    }
    
    public void doRequestMapping() {
        for(Class<?> clazz:clazzList) {
            //映射表注册,因为@JJRequestMapping都在@JJController中,所以遍历@JJController修饰的类
            if(clazz.isAnnotationPresent(JJController.class)) {
                String urlbase="";
                if(clazz.isAnnotationPresent(RequestMapping.class)) {
                    urlbase=clazz.getAnnotation(JJRequestMapping.class).value();
                }
                //实际上springMVC的实现不是这样的,估计会加上method.getModifiers(),应该是拿到当前类的public方法,也是为什么非public方法无法做controller映射的原因
                for(Method method:clazz.getDeclaredMethods()) {
                    if(method.isAnnotationPresent(JJRequestMapping.class)) {
                        //urlMapping = 类的公共JJRequestMapping+方法JJRequestMapping
                        String urlMapping=urlbase+method.getAnnotation(JJRequestMapping.class).value();
                        //把处理类,处理方法封装成handlerAdapter,并绑定相应url添加入requestMapping
                        HandlerAdapter handlerAdapter=new HandlerAdapter();
                        handlerAdapter.setHandlerClazz(clazz);
                        handlerAdapter.setHandlerMethod(method);
                        requestMapping.put(urlMapping, handlerAdapter);
                    }
                }
            }
        }
    }
    
    public Object doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        String url=req.getRequestURI();
        //拿到请求参数构建一个入参Map,这里我们只考虑普通的x-www-form-urlencoded提交,其他有关文件和其他流式的方式不做实现
        Map<String, String[]> parametersMap= req.getParameterMap();
        //构建入参数组
        String[][] parameters=new String[parametersMap.size()][];
        int i=0;
        //填充入参数组
        for(Entry<String, String[]> paraSet : parametersMap.entrySet()) {
            parameters[i++]=paraSet.getValue();
        }

        //requestMapping是一个<url,请求处理器>的map,这是我们通过@JJController的@JJRequestMapping()里的值实现的,servlet在初始化时就已经把映射关系注入了这个map
        HandlerAdapter handlerAdapter=requestMapping.get(url);
        if(handlerAdapter!=null) {
            /**
             * 实际SpringMVC的处理很复杂,不是返回一个执行方法这么简单,大家可以去看看源码,大致通过一下几步:
             * 1.将映射请求注册到处理器HandlerMapping;
             * 2.HandlerMapping会把请求url映射为HandlerExecutionChain类型的handler对象;(包含过滤器的整个执行链)
             * 3.将handler对象作为参数传递给HandlerAdapter的实例化对象,调用其handler方法会生成一个ModelAndView对象;并且在这个过程会有过滤器的环绕执行
             * 我们这里只是简易的实现,并且也没有抽象成ModelAndView视图对象
             */
            Method invokeMethod=handlerAdapter.getHandlerMethod();
            //反射调用相关方法,注意:我们这里因为没有ModelAndView所以也就没有做视图处理,service 方法只是单纯的把方法return值通过流写回去。
            return invokeMethod.invoke(handlerAdapter.getHandlerClazz(), handlerAdapter.getParameters());
        }else {
            //找不到则抛出错误;说明该url没有相关JJRequestMapping注册过
            throw new IllegalAccessException();
        }
    }

}

HandlerExecutionChain.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 对于java中的思考的方向,1必须要看前端的页面,对于前端的页面基本的逻辑,如果能理解最好,不理解也要知道几点。 ...
    神尤鲁道夫阅读 792评论 0 0
  • Based on Java™ Servlet Specification v3.1 [TOC] Servlet和S...
    0x70e8阅读 1,298评论 0 7
  • 作者: 一字马胡 转载标志 【2018-01-07】 更新日志 导入 Spring源码分析系列文章索引: Spr...
    一字马胡阅读 3,084评论 1 18
  • 16. Web MVC 框架 16.1 Spring Web MVC 框架介绍 Spring Web 模型-视图-...
    此鱼不得水阅读 998评论 0 4
  • 日日碌碌日日无所为 夜夜买醉夜夜难入睡 纵有浪迹天涯之心 难解尘事烦扰之身 吾愿万水千山花鸟虫鱼相伴 无欲无求无爱...
    张开丽阅读 169评论 0 0