手写简易版SpringMVC

爱被打了一巴掌 2023-07-20 12:19 40阅读 0赞

为了更好了Spring以及SpringMVC的核心原理,本文参考其他书籍实现了一个简易版的SpringMVC框架,框架的构建过程如下:

  1. 创建个Maven工程,并在pom.xml文件中引入Servlet;

    1. <dependency>
    2. <groupId>javax.servlet</groupId>
    3. <artifactId>javax.servlet-api</artifactId>
    4. <version>4.0.1</version>
    5. </dependency>
  2. 工程结构如下:
    在这里插入图片描述
  3. 在web.xml里配置前端控制器DispatcherServlet,用于拦截所有的请求,并在初始化参数里配置application.properties文件的路径


    DispatcherServlet
    com.framework.servlet.DispatcherServlet

    contextConfigLocation
    application.properties

    1

    1. <servlet-name>DispatcherServlet</servlet-name>
    2. <url-pattern>/*</url-pattern> </servlet-mapping>
  4. application.properties文件中只填写包扫描路径即可

    scanPackage=com.demo

  5. 创建各种注解

    @Target({ ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Autowired {

    1. String value() default "";

    }

    @Target({ ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Controller {

    1. String value() default "";

    }

    @Target({ ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestMapping {

    1. String value() default "";

    }

    @Target({ ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestParam {

    1. String value() default "";

    }

    @Target({ ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Service {

    1. String value() default "";

    }

  6. 创建一组业务处理逻辑,用DemoController接收请求并返回DemoService的处理结果给浏览器

    @Controller
    @RequestMapping(“/demo”)
    public class DemoController {

    1. @Autowired
    2. DemoService demoService;
    3. @RequestMapping("/query")
    4. public void query(HttpServletRequest request, HttpServletResponse response, @RequestParam("name") String name) throws IOException {
    5. String result= demoService.get(name);
    6. response.getWriter().write(result);
    7. }

    }

    public interface DemoService {

    1. String get(String name);

    }

    @Service
    public class DemoServiceImpl implements DemoService {

    1. @Override
    2. public String get(String name) {
    3. return "My name is " + name;
    4. }

    }

  7. 接下来是重中之重构造DispatcherServlet类

    我们将在DispatcherServlet的init方法中进行:

    1. 加载配置文件
    2. 扫描所有的类
    3. 实例化所有单例Bean并放到IOC容器中
    4. 完成依赖注入
    5. 初始化HandlerMapping容器

    然后,因为本框架只对Get请求进行处理,所以只重写doGet方法。

具体过程如下:

  1. 创建DispatcherServlet成员变量:

    //加载application.properties配置文件中的内容
    private Properties contextConfig = new Properties();

    //保存扫描到的所有类名称
    private List classNames = new ArrayList<>();

    //IOC容器,保存所有Bean实例
    private Map ioc = new HashMap<>();

    //handlerMapping容器,保存URL和Method的映射关系
    private Map handlerMapping = new HashMap<>();

  2. 在DispatcherServlet中重写init方法并实现以下方法:

    @Override
    public void init(ServletConfig config) {

    1. //加载配置文件
    2. loadConfig(config.getInitParameter("contextConfigLocation"));
    3. //包扫描
    4. initScanner(contextConfig.getProperty("scanPackage"));
    5. //实例化扫描到的类,并放入到IOC容器中
    6. initBeans();
    7. //完成依赖注入
    8. initAutowired();
    9. //建立所有URL和Controller中方法的对应关系,并保存到HandlerMapping容器中
    10. initHandlerMapping();

    }

  3. 实现loadConfig方法

    private void loadConfig(String contextConfigLocation) {

    1. //找到application.properties文件路径
    2. InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
    3. //加载application.properties
    4. try {
    5. contextConfig.load(is);
    6. } catch (IOException e) {
    7. e.printStackTrace();
    8. } finally {
    9. //关闭资源
    10. try {
    11. is.close();
    12. } catch (IOException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }
  4. 实现initScanner方法,扫描com.demo包以及子包的类,并将类的全限定名称保存起来,为initBean实例化这些类做准备;

    private void initScanner(String scanPackage) {

    1. //扫描com.demo包以及子包的类
    2. URL url = getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
    3. File classPath = new File(url.getFile());
    4. for (File file: classPath.listFiles()) {
    5. if (file.isDirectory()) {
    6. initScanner(scanPackage + "." +file.getName());
    7. } else {
    8. if (!file.getName().endsWith(".class")) continue;
    9. String className = scanPackage + "." + file.getName().replace(".class", "");
    10. classNames.add(className);
    11. }
    12. }
    13. }
  5. 通过遍历classNames中的路径名称,并以反射的方法实例化带有@Controller和@Service注解的类,最后把实例加入到IOC容器中;

    private void initBeans() throws Exception {

    1. if (classNames.isEmpty()) return;
    2. for (String className: classNames) {
    3. Class<?> clazz = Class.forName(className);
    4. //加了注解的类才能初始化
    5. if (clazz.isAnnotationPresent(Controller.class)) {
    6. Object instance = clazz.newInstance();
    7. ioc.put(clazz.getSimpleName(), instance);
    8. } else if (clazz.isAnnotationPresent(Service.class)) {
    9. Object instance = clazz.newInstance();
    10. for (Class<?> i : clazz.getInterfaces()) {
    11. if (ioc.containsKey(i.getName())) {
    12. throw new RuntimeException(i.getName() + "已存在");
    13. }
    14. ioc.put(i.getName(), instance);
    15. }
    16. } else {
    17. continue;
    18. }
    19. }
    20. }
  6. 实现initAutowired方法,遍历IOC容器中所有的实例,找到实例中带有@Autowire注解的成员变量,这些成员变量都是对象的引用,还没用进行赋值,所以用反射的方法将这些变量进行引用赋值。

    private void initAutowired() throws IllegalAccessException {

    1. if (ioc.isEmpty()) return;
    2. for (Map.Entry<String, Object> entry: ioc.entrySet()) {
    3. //获取被@Autowire注解的字段
    4. Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
    5. for (Field field: declaredFields) {
    6. if (!field.isAnnotationPresent(Autowired.class)) continue;
    7. //根据变量类型获取类路径
    8. String beanName = field.getType().getName();
    9. //设置暴力访问
    10. field.setAccessible(true);
    11. //将变量所注入的类和根据类路径查找到的实例设置给这个变量
    12. field.set(entry.getValue(), ioc.get(beanName));
    13. }
    14. }
    15. }
  7. 初始化HandlerMapping,它是SpringMVC中完成URL到Controller映射的组件,实质上是一个Map,容器初始化会建立所有URL和Controller中方法的对应关系,保存到HandlerMapping中,用户请求时根据请求的URL快读定位到Controller中的某个方法。确定了方法后,就把请求参数绑定到方法的形参上。

    private void initHandlerMapping() {

    1. if (ioc.isEmpty()) return;
    2. for (Map.Entry<String, Object> entry: ioc.entrySet()) {
    3. Class<?> clazz = entry.getValue().getClass();
    4. if (!clazz.isAnnotationPresent(Controller.class)) continue;
    5. //保存类上面的@RequestMapping("/demo")
    6. String baseUrl = "";
    7. if (clazz.isAnnotationPresent(RequestMapping.class)) {
    8. RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
    9. baseUrl = requestMapping.value();
    10. }
    11. //获取方法上的Url
    12. for (Method method: clazz.getMethods()) {
    13. if (!method.isAnnotationPresent(RequestMapping.class)) continue;
    14. RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
    15. String fullUrl = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
    16. //把URL和对应的方法放到HandlerMapping容器中
    17. handlerMapping.put(fullUrl, method);
    18. }
    19. }
    20. }
  8. 重写doGet方法,将请求转发给doDispatch方法进行处理

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

    1. String url = req.getRequestURI();
    2. String contextPath = req.getContextPath();
    3. url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
    4. //handlerMapping中查到请求的URL,没有就返回404
    5. if (!handlerMapping.containsKey(url)) {
    6. resp.getWriter().write("404 Not Found");
    7. return;
    8. }
    9. //通过url找到对应的方法,用反射的方式知道方法对应的实例对象,并执行这个方法
    10. Method method = handlerMapping.get(url);
    11. Map<String, String[]> parameterMap = req.getParameterMap();
    12. String beanName = method.getDeclaringClass().getSimpleName();
    13. method.invoke(ioc.get(beanName), req, resp, parameterMap.get("name")[0]);
    14. }

通过以上步骤,我们完成了简易版SpringMVC框架的构建,下面看看运行结果:

  1. 访问路径为http://localhost:8080/mvc\_write\_war/时,返回404
    在这里插入图片描述
  2. 访问路径为http://localhost:8080/mvc\_write\_war/demo/query?name=peter
    在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,40人围观)

还没有评论,来说两句吧...

相关阅读

    相关 简易的Mybatis

    手写简易的Mybatis -------------------- 此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,...