zoukankan      html  css  js  c++  java
  • 手写一个简单的spring mvc解析器

         spring mvc的运行流程如下图所示

       在这里我们只实现基本功能,对流程做一定的简化。我们需要以下几个步骤:

          加载配置文件

          获取所有的class名称

          获取需要注入的实例

          缓存方法映射

          注入bean

      首先,我们需要新建一个web工程,并创建相应注解,以及添加依赖,工程目录如下所示:

       

      由于是手写自己的mvc demo,pom文件中只需要添加servlet的依赖

      

    <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
    </dependency>

      第一步,加载配置文件,首先在resource目录下新建application.properties文件,并创建自己的servlet,配置web.xml

    application.properties文件

    #设置扫描根目录,此处为了简化,直接使用properties文件
    baseScanPackage=com.study.mvc

    自定义的servlet

    ...
    // DispatcherServlet类继承HttpServlet,并覆写doGet、doPost方法
    
    public class DispatcherServlet extends HttpServlet { private static final String Context_CONFIG_LOCATON = "contextConfigLocation"; private Properties properties = new Properties(); 
        //缓存读取到的class
        private Set<String> classNames = new HashSet<>();
    
        //缓存className与class实例的对应关系
        private Map<String, Object> iocMap = new HashMap<>();
    
        //缓存url与controller的映射关系
        private Map<String, Object> controllerMap = new HashMap<>();
    
        //缓存url与其对应方法的映射关系
        private Map<String, Method> handMappingMap = new HashMap<>();
    
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
            String configPath = servletConfig.getInitParameter(Context_CONFIG_LOCATON);
    
            try {
    
                //加载配置文件
                doLoadConfig(configPath);
    
                //获取所有class名称
                String baseScanPackage = properties.getProperty("baseScanPackage");
                doLoadClassNames(baseScanPackage);
    
                //获取需要注入的实例
                doLoadIocMap();
    
                //方法映射
                doHandMapping();
    
                //注入bean
                doIoc();
    
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
    
    
            super.init();
        }
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doDispatcher(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doDispatcher(req, resp);
        }
    
        //实际调用的路由方法
        private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
            String url = req.getRequestURI();
            System.out.println(url);
            Method method = handMappingMap.get(url);
            Object controller = controllerMap.get(url);
            try {
    
     
                Parameter[] parameters = method.getParameters();
                if (parameters != null && parameters.length > 0) {
                    Object[] args = new Object[parameters.length];
                    //根据requestParam封装参数
                    for (int i = 0; i < parameters.length; i++) {
                        if (parameters[i].isAnnotationPresent(RequestParam.class)) {
                            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
                            args[i] = req.getParameter(requestParam.value());
                            continue;
                        }
                        args[i] = null;
                    }
                    method.invoke(controller, args);
                } else {
    
                    method.invoke(controller);
                }
    
                resp.getWriter().print("success");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
       ...
    }

    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>
    
    
    <!--定义自己的servlet--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>com.study.mvc.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

    从classPath下加载配置文件并缓存配置到properties中

    private void doLoadConfig(String configPath) throws IOException {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configPath);
            properties.load(inputStream);
    }

     第二步,根据读取到的basePackage获取到package下所有类

    private void doLoadClassNames(String basePackage) throws FileNotFoundException {
            String scanPackage = basePackage.replaceAll("\\.", "/");
            URL url = this.getClass().getClassLoader().getResource(String.format("/%s", scanPackage));
            if (url == null) {
                throw new FileNotFoundException();
            }
            File dir = new File(url.getFile());
    
            for (File file : Optional.ofNullable(dir.listFiles()).orElse(new File[]{})) {
                if (file.isDirectory()) { //如果是目录则递归获取class
                    doLoadClassNames(basePackage + "." + file.getName());
                } else {
                    if (file.getName().endsWith("class")) {
                        String className = basePackage + "." + file.getName().replace(".class", "");
                        classNames.add(className);
                        System.out.println(String.format("load class:%s", className));
                    }
                }
            }
        }

     第三步:获取需要注入的实例,并放入iocMap,在此只处理类上含有@Service或者@Controler的类

    private void doLoadIocMap() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            for (String className : classNames) {
                Class clazz = Class.forName(className);
    
                //加载含有Controller或者Service注解的bean
                if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {
                    iocMap.put(toLowerCaseFirstName(clazz.getSimpleName()), clazz.newInstance());
                }
            }
        }

     第四步:缓存url和method的对应关系到handMappingMap中,首先找到含有@Controller注解的类,然后读取@RequestMapping的value进行拼接

    private void doHandMapping() {
            iocMap.forEach((className, instance) -> {
                Class clazz = instance.getClass();
                String baseUrl = "";
                if (clazz.isAnnotationPresent(Controller.class)) {
    
                    if (clazz.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                        baseUrl = requestMapping.value();
                    }
    
                    Method[] methods = clazz.getDeclaredMethods();
    
                    //拼接@RequestMapping伤的url及其对应的method
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                            String subUrl = requestMapping.value();
                            handMappingMap.put(baseUrl + subUrl, method);
                            controllerMap.put(baseUrl + subUrl, instance);
                        }
                    }
                }
            });
        }

     第五步:在controller中注入所需的bean

    private void doIoc() {
            iocMap.forEach((className, instance) -> {
                Class clazz = instance.getClass();
    
                if (clazz.isAnnotationPresent(Controller.class)) {
    
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field field : fields) {
                        if (field.isAnnotationPresent(Autowired.class)) {
                            String filedName = field.getName();
                           //修改field的access属性
                            field.setAccessible(true);
                            try {
                                field.set(instance, iocMap.get(filedName));
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });
        }

     接下来,就可以进行测试了,首先创建DemoController和DemoService,如下所示:

    @Controller
    @RequestMapping("/demo")
    public class DemoController {
    
        @Autowired
        private DemoService demoService;
    
        @RequestMapping("/get")
        public void demo(@RequestParam("name") String name) {
            System.out.println(name);
            demoService.Demo(name);
        }
    
    }
    @Service
    public class DemoService {
    
        public void Demo(String name) {
            System.out.println("=======" + name + "====");
        }
    }

     在浏览器访问http://localhost:8080/demo/get?name=li,后台成功打印出name

  • 相关阅读:
    zookeeper 初步学习
    nginx+redis+4个tomcat 负载均衡
    nginx的配置文件解析
    nginx-----惹不起的端口修改
    Api2Doc生成 Restful API 文档
    swagger2 注解整体说明
    SpringMVC 中xml 配置多数据源
    lombok 注解使用
    springboot 中 集成druid ,redis
    spring学习---day01
  • 原文地址:https://www.cnblogs.com/wkzhao/p/10274593.html
Copyright © 2011-2022 走看看