美文网首页
手写一个简易的类springMVC

手写一个简易的类springMVC

作者: JaJIng | 来源:发表于2019-04-08 16:04 被阅读0次

    首先了解一下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

    相关文章

      网友评论

          本文标题:手写一个简易的类springMVC

          本文链接:https://www.haomeiwen.com/subject/spiuiqtx.html