• Spring IOC MVC DI简单实现


    目录的大致情况:所有的类都会加进来.

    1.首先先写基本的Controller Service ServiceImpl

     Controller Service ServiceImpl是用来验证下面写的框架信息是否正常运行

    package demo.controller;
    
    import demo.service.HelloService;
    import framework.annotation.FAutowried;
    import framework.annotation.FController;
    import framework.annotation.FRequestMapping;
    import framework.annotation.FRequestParam;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * DemoController.
     *
     * @author Feng Yongkang, 2019/9/9
     * @version v1.0
     */
    @FController
    @FRequestMapping("/demo")
    public class HelloController {
        @FAutowried
        private HelloService service;
    
        @FRequestMapping("/getName")
        public void getName(HttpServletRequest req, HttpServletResponse resp, @FRequestParam("name") String name) {
            service.firstServlet(name);
            try {
                System.out.println(name);
                PrintWriter writer = resp.getWriter();
                writer.write(name);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
    
            }
    
        }
    }
    

      

    package demo.service;
    
    /**
     * HelloService.
     *
     * @author Feng Yongkang, 2019/9/10
     * @version v1.0
     */
    public interface HelloService {
        void firstServlet(String str);
    }
    

      

    package demo.service.impl;
    
    import demo.service.HelloService;
    import framework.annotation.FService;
    
    /**
     * HelloService.
     *
     * @author Feng Yongkang, 2019/9/10
     * @version v1.0
     */
    @FService
    public class HelloServiceImpl implements HelloService {
        public void firstServlet(String str) {
            System.out.println("你好,我接收到一个参数:" + str);
        }
    }
    

    2.需要用的注解

    这里是考虑最简单常用的几个注解.可以支持简单的请求处理

    package framework.annotation;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.FIELD)//这个注解的使用范围
    @Retention(RetentionPolicy.RUNTIME)//生命周期
    @Documented
    public @interface
    FAutowried {
        String value() default "";
    }
    

      

    package framework.annotation;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.TYPE)//这个注解的使用范围
    @Retention(RetentionPolicy.RUNTIME)//生命周期
    @Documented
    public @interface FController {
        String value() default "";
    }
    

      

    package framework.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD, ElementType.TYPE})//这个注解的使用范围
    @Retention(RetentionPolicy.RUNTIME)//生命周期
    @Documented
    public @interface FRequestMapping {
        String value() default "";
    }
    

      

    package framework.annotation;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.PARAMETER)//这个注解的使用范围
    @Retention(RetentionPolicy.RUNTIME)//生命周期
    @Documented
    public @interface FRequestParam {
        String value() default "";
    }
    

      

    package framework.annotation;
    
    import java.lang.annotation.*;
    
    @Target(ElementType.TYPE)//这个注解的使用范围
    @Retention(RetentionPolicy.RUNTIME)//生命周期
    @Documented
    public @interface FService {
        String value() default "";
    }
    

    3.核心DispatcherServlet

    这个是关键.所有的功能全包含在这里.

    dispatcherServlet查看顺序,先看init方法(里面方法步骤一个一个看).init方法执行完毕,则Spring容器也就启动完毕.

    再看service方法,如何处理请求.一个请求到达的时候,根据uri去HandlerMapping用适合的方法处理请求.

    package framework;
    
    import framework.annotation.*;
    
    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.io.InputStream;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.util.*;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * FDispatcherServlet.
     *
     * @author Feng Yongkang, 2019/9/9
     * @version v1.0
     */
    public class FDispatcherServlet1 extends HttpServlet {
        //resources下的配置文件中的内容
        private Properties contextConfig = new Properties();
    
        //指定要扫描路径的下的类名的集合
        private List<String> classNames = new ArrayList<String>();
    
        //classNames中被注解标识为bean的类实例
        private Map<String, Object> ioc = new HashMap<String, Object>();
    
        //保存所有url与映射的关系
        private List<Handler> handlerMappping = new ArrayList<Handler>();
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doPost(req, resp);
    
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            try {
                doDispatch(req, resp);
            } catch (Exception e) {
                e.printStackTrace();
                resp.getWriter().write("500 EXCEPTION");
            }
        }
    
        /**
         * 根据uri得到匹配的Handler.根据Handler中的paramIndexMapping填充方法中的参数.
         * 参数是实参,根据请求中的数据获取的.
         * 通过代理模式,执行ioc容器下保存的Controller的bean对应的URI的方法.完成请求处理.
         *
         * @param req
         * @param resp
         * @throws IOException
         */
        private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
            try {
                //查找与请求匹配的handler
                Handler handler = getHandler(req);
                if (handler == null) {
                    resp.getWriter().write("404 NOT FOUND");
                    return;
                }
                //获得方法的形参类型
                Class<?>[] parameterTypes = handler.method.getParameterTypes();
                //在调用invoke代理时,根据参数的位置,传递参数用.
                Object[] paramValues = new Object[parameterTypes.length];
    
                //获得请求中的所有参数,并遍历,看请求中参数的key是否出现
                Map<String, String[]> params = req.getParameterMap();
    
                for (Map.Entry<String, String[]> param : params.entrySet()) {
                    String value = Arrays.toString(param.getValue()).replaceAll("\[|\]", "").replaceAll("/+", "/");
                    //如果找到对象开始填充参数值
                    if (!handler.paramIndexMapping.containsKey(param.getKey())) {
                        continue;
                    }
                    Integer index = handler.paramIndexMapping.get(param.getKey());
                    if (index != null) {
                        paramValues[index] = convert(parameterTypes[index], value);
                    }
                }
                //设置方法中的request与response对象,如果方法中有这个key就返回对应的value如果没有就返回null
                Integer reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
                if (reqIndex != null) {
                    paramValues[reqIndex] = req;
                }
                Integer respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
                if (respIndex != null) {
                    paramValues[respIndex] = resp;
                }
    
    
                handler.method.invoke(handler.controller, paramValues);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void init(ServletConfig config) throws ServletException {
            //1 加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
            //2 扫描所有相关的类
            doScanner(contextConfig.getProperty("scanPackage"));
            //3 初始化所有相关的类
            doInstance();
            //4 自动注入.到这里Spring的功能已经完成
            doAutowired();
            //5 初始HandlerMapping,这里开始就是SpringMVC的功能
            doHandlerMapping();
            System.out.println("Spring初始化完成");
        }
    
        /**
         * 遍历ioc容器的中bean的方法.private的方法一般不作为请求.
         * public类型的被FAutowried修饰.根据类上面的注解和方法上面的注解,生成uri转为正则方便后续的匹配.
         * uri的正则,Controller,还有对应的方法组成一个Handler.放入List集合中.方便请求时生成代理,处理请求.
         */
        private void doHandlerMapping() {
            if (ioc.isEmpty()) {
                return;
            }
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                Class<?> claszz = entry.getValue().getClass();
                if (!claszz.isAnnotationPresent(FController.class)) {
                    continue;
                }
                String baseUrl = "";
                //获取公用uri
                if (claszz.isAnnotationPresent(FRequestMapping.class)) {
                    FRequestMapping annotation = claszz.getAnnotation(FRequestMapping.class);
                    baseUrl = annotation.value();
                }
                //扫描所有公共方法
                for (Method method : claszz.getMethods()) {
                    if (!method.isAnnotationPresent(FRequestMapping.class)) {
                        continue;
                    }
                    FRequestMapping annotation = method.getAnnotation(FRequestMapping.class);
                    //如果用户的FRequestMapping注解中没有/,在类注解和方法注解中,拼接的时候给他加上,防止用户加了,在拼接后的URI中,把多于一个/换成一个/
                    String methodUrl = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
                    //handlerMapping.put(methodUrl, method);
                    Pattern pattern = Pattern.compile(methodUrl);
                    //pattern:uri的正则匹配格式,method:处理对应请求的方法,entry.getValue():方法所在的实例化后放在ioc中的bean(Controller实例)
                    handlerMappping.add(new Handler(entry.getValue(), method, pattern));
                }
            }
        }
    
    
        /**
         * 迭代ioc容器中所有bean的字段.如果这个字段有FAutowried修饰.
         * 1.优先根据注解的别名去ioc容器中查找对应的实例进行注入.
         * 2.字段所属类型的类型全名(一般写的都是接口类型)
         * 3.没有考虑Controller通过接口的实现进行声明而不是接口类型进行声明.
         */
        private void doAutowired() {
            if (ioc.isEmpty()) {
                return;
            }
            //循环IOC中的所有类,然后对需要赋值的属性进行赋值
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                //获得bean实例中所有字段,判断字段是否有@Autowried,
                // 如果有,优先注解自定义的属性名,默认字段对应的类型名(在放入IOC时,对于接口类型,直接接口的全名作为key,对于接口实现类,类名首字母小写作为类型名)
                Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (!declaredField.isAnnotationPresent(FAutowried.class)) {
                        continue;
                    }
                    FAutowried annotation = declaredField.getAnnotation(FAutowried.class);
                    String beanName = annotation.value().trim();
                    if ("".equals(beanName)) {
                        beanName = declaredField.getType().getName();
                    }
                    //根据key去容器中查找,注入到字段中.
                    boolean accessible = declaredField.isAccessible();
                    declaredField.setAccessible(true);
                    try {
                        declaredField.set(entry.getValue(), ioc.get(beanName));//注入
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    declaredField.setAccessible(accessible);
                }
            }
        }
    
        /**
         * 把classNames中保存的符合的作为bean的类进行实例化并放入到容器中
         * 生成的bean的命名规则1.默认类名首字母小写 2.优先使用自定义名字 3.如果是一个接口,接口全名作为key,
         * 如果一个接口有多个实例就会报错,体现在.查看这个实例的接口名是否已经存在,已经存在则说明,这个接口有别的实现类.
         */
        private void doInstance() {
            if (classNames.isEmpty()) {
                return;
            }
            try {
                for (String className : classNames) {
                    Class<?> clazz = Class.forName(className);
                    //实例化加了注解的类
                    //Controller注解的类实例化
                    if (clazz.isAnnotationPresent(FController.class)) {
                        String beanName = lowerFirstCase(clazz.getSimpleName());
                        ioc.put(beanName, clazz.newInstance());
                        //Service注解的类的实例化
                    } else if (clazz.isAnnotationPresent(FService.class)) {
                        //获得这个注解,看注解中是否有自定义的注解名,自定义的优先,然后是默认的类名首字符小写
                        //1.自定义的beanName
                        FService annotation = clazz.getAnnotation(FService.class);
                        String beanName = annotation.value();
                        //2.默认首字母小写的beanName
                        if ("".equals(beanName)) {
                            beanName = lowerFirstCase(clazz.getSimpleName());
                        }
                        Object instance = clazz.newInstance();
                        ioc.put(beanName, instance);
                        //3.这个类如果有接口.
                        //getInterfaces获得这个类所实现的所有接口,如果这个类对应的接口的getName已经存在,证明,这个接口不止自己一个实现类.
                        //一个接口如果有多个实现类,在注入的时候不知道注入哪一个,如果存在这种情况抛出异常.不存在就以接口名为key,存入实现类的实例.
                        for (Class<?> anInterface : clazz.getInterfaces()) {
                            if (ioc.containsKey(anInterface.getName())) {
                                throw new Exception("The BeanName is exists");
                            }
                            ioc.put(anInterface.getName(), instance);
                        }
                    } else {
                        continue;
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 把指定的路径下所有的class类的类名保存到classNames集合中.
         *
         * @param scanPackage 要扫描的包路径
         */
        //得到所有的class.
        private void doScanner(String scanPackage) {
            //要把所有的文件路径处理成包路径
            URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\.", "/"));
            File classDir = new File(url.getFile());
            for (File file : classDir.listFiles()) {
                if (file.isDirectory()) {
                    doScanner(scanPackage + "." + file.getName());
                } else {
                    if (!file.getName().endsWith(".class")) {
                        //如果不是class文件进行下一次循环.
                        continue;
                    }
                    String className = scanPackage + "." + file.getName().replace(".class", "");
                    classNames.add(className);
                }
            }
        }
    
        /**
         * 加载外部的配置文件.这里用比较简单的properties;key-value类型的对象.
         *
         * @param contextConfigLocation 对应web.xml中的配置的资源文件的路径
         */
        private void doLoadConfig(String contextConfigLocation) {
            InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
            try {
                contextConfig.load(is);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != is) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 把一个首字符大写的字符串转为首字母小写的字符串.
         *
         * @param str 首字母大写的字符串
         * @return 首字母小写的字符串
         */
        private String lowerFirstCase(String str) {
            char[] chars = str.toCharArray();
            chars[0] += 32;
            return String.valueOf(chars);
        }
    
        /**
         * 根据请求路径获得与路径匹配的Handler
         *
         * @param req
         * @return
         * @throws Exception
         */
        private Handler getHandler(HttpServletRequest req) throws Exception {
            if (handlerMappping.isEmpty()) {
                return null;
            }
            //绝对路径
            String requestURI = req.getRequestURI();
            //绝对路径中URI之外的路径
            String contextPath = req.getContextPath();
            requestURI.replace(contextPath, "").replaceAll("/+", "/");
            for (Handler handler : handlerMappping) {
                try {
                    //比较请求中的URL与存在handler中的是否匹配
                    Matcher matcher = handler.pattern.matcher(requestURI);
                    //如果没有继续下一个匹配
                    if (!matcher.matches()) {
                        continue;
                    }
                    return handler;
                } catch (Exception e) {
                    throw e;
                }
            }
            return null;
        }
    
        private Object convert(Class<?> type, String value) {
            if (Integer.class == type) {
                return Integer.valueOf(value);
            }
            return value;
        }
    
        /**
         * Handler 记录Conroller中的RequestMapping和Method的对应关系
         */
        private class Handler {
            protected Object controller;
            protected Method method;
            protected Pattern pattern;
            protected Map<String, Integer> paramIndexMapping;//参数的别名,或者类型名作为key,位置作为value.
    
            public Handler(Object controller, Method method, Pattern pattern) {
                this.controller = controller;//保存方法对应的实例
                this.method = method;//保存映射的方法
                this.pattern = pattern;
                paramIndexMapping = new HashMap<String, Integer>();//参数顺序
                putParamIndexMapping(method);
            }
    
            /**
             * 把Controller中方法中的参数有注解的根据注解名,或者request response类型的参数放入到map中
             * 只考虑有FRequestParam注解的别名与request,response的类型名.作为key,在参数中的位置作为value.
             * 在调用invoke方法的时候,可以按照参数顺序进行传参.
             *
             * @param method
             */
            private void putParamIndexMapping(Method method) {
                //1.一个参数可能有很多注解,一个方法有可能有很多参数,所以返回一个注解的二维数组.如果注解里面指定了参数的参数的别名,就把参数别名作为key,参数位置作为value放入map.
                //这里仅仅考虑FRequestParam注解,像验证,RequestBody,没有注解没有考虑.
                Annotation[][] pa = method.getParameterAnnotations();
                for (int i = 0; i < pa.length; i++) {
                    for (Annotation a : pa[i]) {
                        if (a instanceof FRequestParam) {
                            String paranName = ((FRequestParam) a).value();
                            if (!"".equals(paranName.trim())) {
                                paramIndexMapping.put(paranName, i);
                            }
                        }
                    }
                }
                //2.对于 request与response,默认放入根据类型名与参数位置放入map
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> type = parameterTypes[i];
                    if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                        paramIndexMapping.put(type.getName(), i);
                    }
                }
            }
        }
    }
    
  • 相关阅读:
    windows常用快捷键
    【Linux】查看系统位数
    【Centos】yum 安装mariaDB
    【Centos7 GRUB】修改开机等待时间
    Shell脚本编写规范
    【Shell Basic】source . 与 bash sh 的区别
    linux防火墙之 ufw
    【HotSpot】jps命令行详解
    【Ubuntu 16】网络配置文件
    【刷题】BZOJ 2179 FFT快速傅立叶
  • 原文地址:https://www.cnblogs.com/xiaoeyu/p/11536006.html
Copyright © 2020-2023  润新知