美文网首页
12 手写SpringMVC

12 手写SpringMVC

作者: 攻城狮哦哦也 | 来源:发表于2019-10-16 21:11 被阅读0次

    1 概述

    1、简易的实现SpringMVC中注解类的加载,和IOC、DI的实现
    2、实现DispatchServlet根据RequestMapping等注解标注路径的的请求分发
    3、利用策略模式和反射对请求方法的参数的处理

    2 代码实现

    2.1 注解类准备

    • EnjoyController
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({java.lang.annotation.ElementType.TYPE}) //作用范围:用在接口或类上
    @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
    @Documented//明该注解将被包含在javadoc中    @Inherited:说明子类可以继承父类中的该注解
    public @interface EnjoyController {
        String value() default "";
    }
    
    
    • EnjoyQualifier
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EnjoyQualifier {
        String value() default "";
    }
    
    
    • EnjoyRequestMapping
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EnjoyRequestMapping {
        //路径EnjoyRequestMapping(value)
        String value() default "";
    }
    
    
    • EnjoyRequestParam
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EnjoyRequestParam {
        String value() default "";
    }
    
    
    • EnjoyService
    @Target({java.lang.annotation.ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EnjoyService {
        String value() default "";
    }
    
    

    2.2 Controller、Service类准备

    • Controller
    @EnjoyController
    @EnjoyRequestMapping("/james")
    public class JamesController {
        
        @EnjoyQualifier("JamesServiceImpl")//此处为模拟依赖注入
        private JamesService jamesService;
        
        @EnjoyRequestMapping("/query")
        public void query(HttpServletRequest request, HttpServletResponse response,
                @EnjoyRequestParam("name") String name,
                @EnjoyRequestParam("age") String age) {
            
            try {
                PrintWriter pw = response.getWriter();
                String result = jamesService.query(name,age);
                pw.write(result);
            }
            catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }    
    }
    
    • Service
    @EnjoyService("JamesServiceImpl")
    public class JamesServiceImpl implements JamesService {
        
        public String query(String name, String age) {
            
            return "{name="+name+",age="+age+"}";
        }
        
        public String insert(String param) {
            // TODO Auto-generated method stub
            return  "insert successful.....";
        }
        
        public String update(String param) {
            // TODO Auto-generated method stub
            return "update successful.....";
        }
        
    }
    
    

    2.3 DisPatchServlet类准备(重点)

    /**
     * Servlet implementation class DispatcherServlet
     */
    public class DispatcherServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
    //承接需要IOC装载类的全类名
        List<String> classNames = new ArrayList<String>();
    //承接IOC实例化的bean对象
        Map<String, Object> beans = new HashMap<String, Object>();
    //承接Controller中方法和对应方法访问路径
        Map<String, Object> handlerMap = new HashMap<String, Object>();
    
        Properties prop = null;
    
        private static String HANDLERADAPTER = "jamesHandlerAdapter";
    
        /**
         * Default constructor.
         */
        public DispatcherServlet() {
            // TODO Auto-generated constructor stub
        }
    
        /**
         * @see Servlet#init(ServletConfig)
         */
        public void init(ServletConfig config) throws ServletException {
            // 1、我们要根据一个基本包进行扫描,扫描里面的子包以及子包下的类
            scanPackage("com.enjoy");
    
            for (String classname : classNames) {
                System.out.println(classname);
            }
    
            // 2、我们肯定是要把扫描出来的类进行实例化
            instance();
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
    
            // 3、依赖注入,把service层的实例注入到controller
            ioc();
    
            // 4、建立一个path与method的映射关系
            HandlerMapping();
            for (Map.Entry<String, Object> entry : handlerMap.entrySet()) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
            /*
             * InputStream is = this.getClass()
             * .getResourceAsStream("/config/properties/spring.properties"); prop =
             * new Properties(); try { prop.load(is); } catch (IOException e) {
             * e.printStackTrace(); }
             */
    
        }
    
        /**
         * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
         *      response)
         */
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            this.doPost(request, response);
        }
    
        /**
         * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
         *      response)
         */
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //获取到请求路径   /james-springmvc/james/query
            String uri = request.getRequestURI();
                   //获取容器路径  /james-springmvc
            String context = request.getContextPath();
            //将  "/james-springmvc/james/query"  去掉"/james-springmvc"
            String path = uri.replace(context, "");
            //根据请求路径来获取要执行的方法 
            Method method = (Method) handlerMap.get(path);
            //拿到控制类
            JamesController instance = (JamesController) beans.get("/" + path.split("/")[1]);
            //处理器(处理器也做为Spring内置对象被装进beans中,因为处理器类上也标注了@enjoyService注解)
            HandlerAdapterService ha = (HandlerAdapterService) beans.get(HANDLERADAPTER);
    
            
            /*
             * @RequestMapping("/order")
             * order(@RequestBody String params, @RequestHeader @RequestParam String param1){//参数有多种类型接收方式
             * }
             */
            
            
            
            //通过参数解析器将参数解析并获取对应参数的值,将参数放在args数组中返回,并传给反射方法来调用目标方法
            Object[] args = ha.hand(request, response, method, beans);
    
            try {
                method.invoke(instance, args);
                // method.invoke(instance, new
                // Object[]{request,response,null});//拿参数
                
                /*如果有多个参数类型,就得这样写了(可用策略模式,省去以下代码)
                 * if(ParamType == HttpServletRequest){
                    
                }else if(ParamType == @RquestHeader){
                    
                }else
                *用策略模式实现(把粒度控制得更细),新建 JamesHandlerAdapter
                */
                
                
                
            } 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();
            }
    
        }
    
        /*
             * 将目标方法和方法访问路径关系传入handlerMap中保存
             * 
             */
        private void HandlerMapping() {
            if (beans.entrySet().size() <= 0) {
                System.out.println("没有类的实例化!");
                return;
            }
    
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                Object instance = entry.getValue();
    
                Class<?> clazz = instance.getClass();
                // 拿所有Controller的类
                if (clazz.isAnnotationPresent(EnjoyController.class)) {
                    //@com.enjoy.james.annotation.EnjoyRequestMapping(value=/james)
                    EnjoyRequestMapping requestMapping = (EnjoyRequestMapping) clazz
                            .getAnnotation(EnjoyRequestMapping.class);
                    // 获取Controller类上面的EnjoyRequestMapping注解里的请求路径
                    String classPath = requestMapping.value();
                    // 获取控制类里的所有方法
                    Method[] methods = clazz.getMethods();
                    // 获取方法上的EnjoyRequestMapping设置的路径,与方法名建立映射关系
                    for (Method method : methods) {
                        //判断哪些方法上使用EnjoyRequestMapping路径注解
                        if (method.isAnnotationPresent(EnjoyRequestMapping.class)) {
                            //@com.enjoy.james.annotation.EnjoyRequestMapping(value=/query)
                            EnjoyRequestMapping methodrm = (EnjoyRequestMapping) method
                                    .getAnnotation(EnjoyRequestMapping.class);
                            String methodPath = methodrm.value();
                            // 把方法上与路径建立映射关系( /james/query--->public void com.enjoy.james.controller.JamesController.query )
                            handlerMap.put(classPath + methodPath, method);
                        } else {
                            continue;
                        }
                    }
                }
            }
        }
    
        // 初始化IOC容器
        private void ioc() {
    
            if (beans.entrySet().size() <= 0) {
                System.out.println("没有类的实例化!");
                return;
            }
            //将实例化好的bean遍历,
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                Object instance = entry.getValue();//获取bean实例
    
                Class<?> clazz = instance.getClass();//获取类,用来判断类里声明了哪些注解(主要是针对控制类里的判断,比如使用了@Autowired  @Qualifier,对这些注解进行解析)
                //判断该类是否使用了EnjoyController注解
                if (clazz.isAnnotationPresent(EnjoyController.class)) {
                    Field[] fields = clazz.getDeclaredFields();// 拿到类里面的属性
                    // 判断是否声明了自动装配(依赖注入)注解,比如@Autrowired @Qualifier
                    for (Field field : fields) {
                        if (field.isAnnotationPresent(EnjoyQualifier.class)) {
                            EnjoyQualifier qualifier = (EnjoyQualifier) field.getAnnotation(EnjoyQualifier.class);
                            //拿到@EnjoyQualifier("JamesServiceImpl")里的指定要注入的bean名字"JamesServiceImpl"
                            String value = qualifier.value();
    
                            field.setAccessible(true);//将属性设置为允许修改,否则不能注入
                            try {
                                // 从MAP容器中获取"JamesServiceImpl"对应的bean,并注入实例控制层bean,解决依赖注入
                                field.set(instance, beans.get(value));
                            } catch (IllegalArgumentException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        } else {
                            continue;
                        }
                    }
                } else {
                    continue;
                }
            }
        }
    
        private void instance() {
            if (classNames.size() <= 0) {
                System.out.println("包扫描失败!");
                return;
            }
            //遍历扫描到的class文件,将需要实例化的类(加了注解的类)进行反射创建对象(像注解就不需要实例化)
            for (String className : classNames) {
                // com.enjoy.james.service.impl.JamesServiceImpl.class
                String cn = className.replace(".class", "");
    
                try {
                    Class<?> clazz = Class.forName(cn);//拿到class类,用来实例化
                    // 将扫描到的类,获取类名,并判断是否标记了EnjoyController注解
                    if (clazz.isAnnotationPresent(EnjoyController.class)) {
                        EnjoyController controller = (EnjoyController) clazz.getAnnotation(EnjoyController.class);
                        Object instance = clazz.newInstance();
                        //获取对应的请求路径"/james"
                        EnjoyRequestMapping requestMapping = (EnjoyRequestMapping) clazz
                                .getAnnotation(EnjoyRequestMapping.class);
                        String rmvalue = requestMapping.value();//得到"/james"请求路径
                        //用路径做为key,对应value为实例化对象
                        beans.put(rmvalue, instance);
                    } else if (clazz.isAnnotationPresent(EnjoyService.class)) {
                        //获取当前clazz类的注解(通过这个注解可得到当前service的id)  @com.enjoy.james.annotation.EnjoyService(value=JamesServiceImpl)
                        EnjoyService service = (EnjoyService) clazz.getAnnotation(EnjoyService.class);
                        Object instance = clazz.newInstance();
                        //put(JamesServiceImpl,instance)
                        beans.put(service.value(), instance);
                    } else {
                        continue;
                    }
                } catch (ClassNotFoundException 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();
                }
            }
        }
    
        private void scanPackage(String basePackage) {
            //扫描编译好的类路径下所有的类
            URL url = this.getClass().getClassLoader().getResource("/" + replaceTo(basePackage));
    
            String fileStr = url.getFile();
    
            File file = new File(fileStr);
            //拿到所有类com.enjoy下的james文件夹
            String[] filesStr = file.list();
    
            for (String path : filesStr) {
                File filePath = new File(fileStr + path);//扫描com.enjoy.james下的所有class类
    
                //递归调用扫描,如果是路径,继续扫描
                if (filePath.isDirectory()) {
                    // com.enjoy.james
                    scanPackage(basePackage + "." + path);
                } else {
                    classNames.add(basePackage + "." + filePath.getName());//如果是class文件则加入List集合(待生成bean)
                }
            }
        }
    
        private String replaceTo(String basePackage) {
            return basePackage.replaceAll("\\.", "/");
        }
    
    }
    
    • JamesHandlerAdapter
      可以将目标方法中的参数进行处理,在请求时将request传入的请求参数根据目标方法method定义的参数类型进行处理,传出一个Object[]对象数组,该数组在框架利用反射调用目标方法时作为参数传入,例如method.invoke(instance, args); 而对于不用的参数类型,我们可以用策略模式来处理
    @EnjoyService("jamesHandlerAdapter")
    public class JamesHandlerAdapter implements HandlerAdapterService {
        //对method方法里的参数进行处理
        public Object[] hand(HttpServletRequest request,//需要传入request,拿请求的参数
                HttpServletResponse response, Method method,//执行的方法,可以拿到当前待执行的方法有哪些参数
                Map<String, Object> beans) {
            //拿到当前待执行的方法有哪些参数
            Class<?>[] paramClazzs = method.getParameterTypes();
            //根据参数的个数,new 一个参数的数组,将方法里的所有参数赋值到args来
            Object[] args = new Object[paramClazzs.length];
            
            //1、要拿到所有实现了ArgumentResolver这个接口的实现类
            Map<String, Object> argumentResolvers = getBeansOfType(beans,
                    ArgumentResolver.class);
            
            int paramIndex = 0;
            int i = 0;
            //对每一个参数进行循环,每个参数都有特殊处理(比如RequestParam的处理类为 RequestParamArgumentResolver )
            for (Class<?> paramClazz : paramClazzs) {
                //哪个参数对应了哪个参数解析类,用策略模式来找
                for (Map.Entry<String, Object> entry : argumentResolvers.entrySet()) {
                    ArgumentResolver ar = (ArgumentResolver)entry.getValue();
                    
                    if (ar.support(paramClazz, paramIndex, method)) {
                        args[i++] = ar.argumentResolver(request,
                                response,
                                paramClazz,
                                paramIndex,
                                method);
                    }
                }
                paramIndex++;
            }
            
            return args;
        }
        //获取实现了ArgumentResolver接口的所有实例(其实就是每个参数的注解实例)
        private Map<String, Object> getBeansOfType(Map<String, Object> beans,//所有bean
                Class<?> intfType) //类型的实例
        {
            
            Map<String, Object> resultBeans = new HashMap<String, Object>();
            
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
                //拿到实例-->反射对象-->它的接口(接口有多实现,所以为数组)
                Class<?>[] intfs = entry.getValue().getClass().getInterfaces();
                
                if (intfs != null && intfs.length > 0) {
                    for (Class<?> intf : intfs) {
                        //接口的类型与传入进来的类型一样,把实例加到resultBeans里来
                        if (intf.isAssignableFrom(intfType)) {
                            resultBeans.put(entry.getKey(), entry.getValue());
                        }
                    }
                }
            }
            
            return resultBeans;
        }
        
    }
    
    
    • ArgumentResolver
      参数解析接口类
    public interface ArgumentResolver {
        
        public boolean support(Class<?> type, int paramIndex, Method method);
        
        //参数解析方法,每一个参数都会调用一次这个方法
        public Object argumentResolver(HttpServletRequest request,
                HttpServletResponse response, Class<?> type, 
                int paramIndex,//参数索引下坐标,有很多注解,你得知道是哪个参数的注解,每个参数的索引顺序不一样
                Method method);
    }
    
    • RequestParamArgumentResolver
      参数解析实现类,解析对应的@EnjoyRequestParam注解
    @EnjoyService("requestParamArgumentResolver")
    //解析声明注解为RequestParam, 获取注解的值
    public class RequestParamArgumentResolver implements ArgumentResolver {
        //判断传进来的参数是否为EnjoyRequestParam
        public boolean support(Class<?> type, int paramIndex, Method method) {
            
            Annotation[][] an = method.getParameterAnnotations();
            
            Annotation[] paramAns = an[paramIndex];
            
            for (Annotation paramAn : paramAns) {
                //判断传进的paramAn.getClass()是不是 EnjoyRequestParam 类型
                if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                    return true;
                }
            }
            return false;
        }
        //参数解析,并获取注解的值
        public Object argumentResolver(HttpServletRequest request,
                HttpServletResponse response, Class<?> type, int paramIndex,
                Method method) {
            //获取方法中参数的所有注解并放入二维数组中
            Annotation[][] an = method.getParameterAnnotations();
            //根据paramIndex获取对应的参数注解信息
            Annotation[] paramAns = an[paramIndex];
            
            for (Annotation paramAn : paramAns) {
                //判断该注解是否为EnjoyRequestParam类注解
                if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                    EnjoyRequestParam rp = (EnjoyRequestParam)paramAn;
                    
                    String value = rp.value();
                    //获取参数注解中标注的参数别名,根据别名获取请求中的参数值并返回
                    return request.getParameter(value);
                }
            }
            
            return null;
        }
        
    }
    

    相关文章

      网友评论

          本文标题:12 手写SpringMVC

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