手写一个简单的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

posted @   gluawwa  阅读(276)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示