在Java中搭建一个简单的MVC框架

一 . 前言

自定义一个简单的web MVC 框架,实现一个Controller控制处理多个请求。
在IDEA中创建Maven web项目,最终项目层级结构如下:
IDEA中项目结构

二. 代码实现

1. 思路分析

  1. 获得请求的URI和项目部署路径, 截取获得映射路径 eg: /user/login
  2. 扫描某个包里面的所有类的字节码对象集合List
  3. 遍历字节码对象集合List
  4. 获得类里面的所有的Method
  5. 遍历所有的Method
  6. 获得method上面的注解的value属性值
  7. 判断value属性值是否和获得映路径一致, 一致就调用method

2. 代码实现

2.1 Controller注解

这个注解打在类身上,主要是为了解决我们在总的控制器类里面解析太多类的问题以后只要解析哪个类身上打上这个注解,我们就解析哪个类。

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

2.2 RequestMapping注解

此注解注解方法,表示映射路径

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value();
}

2.3 UserController

所有有关用户操作的请求,都交给这个controller来完成

  • 类上打注解 @Controller
  • 方法上打注解 @RequestMappting
@Controller
public class UserController {

    @RequestMapping("/user/register")
    public void register(HttpServletRequest req, HttpServletResponse resp){
        System.out.println("执行了UserController的register方法~!");
    }

    @RequestMapping("/user/login")
    public void login(HttpServletRequest req , HttpServletResponse resp){
        System.out.println("执行了UserController的login方法~!");
    }
}

2.4 MethodBean

用来封装 被调用的方法和 调用这个方法用到的实例对象,这里用Lombok注解生成构造方法。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MethodBean {
    private Method method;//具体的方法对象
    private Object obj;//调用这个方法要用的实例对象
}

2.5 DispatcherServlet

  1. 它是一个servlet,要抓住指定的请求
  2. 重写两个方法 init方法和service
  3. 注册DispatcherServlet,使用xml方式来注册
public class DispatcherServlet extends HttpServlet {
    //在类的成员变量中定义map集合
    Map<String , MethodBean> map = new HashMap<>();
    @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            //1. 读取初始化参数
            String packageName = config.getInitParameter("packageName");
            //2. 扫描这个包
            List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage(packageName);
            //3. 遍历集合中的每一个类
            for (Class<?> clazz : classList) {
                //4. 判定这个类身上有没有 @Controller注解
                boolean flag = clazz.isAnnotationPresent(Controller.class);
                if (flag) {
                    //5. 得到这个类里面所有方法
                    Method[] methods = clazz.getMethods();
                    //6. 遍历每一个方法
                    for (Method method : methods) {
                        //7. 判断方法上是否有注解 @RequestMapping , 要注意有的方法身上没有这个注解,那么返回的是null
                        RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                        if(annotation != null){//获取注解身上的value值。
                            String mapping = annotation.value();
                            //8. 使用map集合来装映射管理  key : mapping  ,value : method &  clazz.newInstance();
                            MethodBean methodBean = new MethodBean(method, clazz.newInstance());
                            map.put(mapping,methodBean);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            String uri = req.getRequestURI();
            String path  = uri.substring(req.getContextPath().length() , uri.lastIndexOf('.'));

            //2. 拿着地址去map集合里面找匹配的集合
            MethodBean bean = map.get(path);

            //3. 要记得判定map返回的结果,因为这个映射地址在map集合里面并不一定有方法与之对应
            if(bean != null){
                //如果进入这个if分支,即表示找到匹配的记录,找到之后,就让方法执行即可
                Method method = bean.getMethod();
                Object object = bean.getObj();

                method.invoke(object ,req , resp );
            }else{
                System.out.println(path + " ,没有找到匹配的方法可以执行!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.6 ClassScannerUtils

这是一个工具类,用来获取包下的所有class的实例。

public static List<Class<?>> getClasssFromPackage(String packageName) {
        List clazzs = new ArrayList<>();
        // 是否循环搜索子包
        boolean recursive = true;
        // 包名对应的路径名称
        String packageDirName = packageName.replace('.', '/');
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {

                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findClassInPackageByFile(packageName, filePath, recursive, clazzs);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return clazzs;
    }

    /**
     * 在package对应的路径下找到所有的class
     */
    public static void findClassInPackageByFile(String packageName, String filePath, final boolean recursive,
                                                List<Class<?>> clazzs) {
        File dir = new File(filePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        // 在给定的目录下找到所有的文件,并且进行条件过滤
        File[] dirFiles = dir.listFiles(new FileFilter() {

            public boolean accept(File file) {
                boolean acceptDir = recursive && file.isDirectory();// 接受dir目录
                boolean acceptClass = file.getName().endsWith("class");// 接受class文件
                return acceptDir || acceptClass;
            }
        });

        for (File file : dirFiles) {
            if (file.isDirectory()) {
                findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, clazzs);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    clazzs.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.7 web.xml文件配置

配置DispatcherServlet,这配置抓取 *.do 的请求,可任意配置。

<?xml version="1.0" encoding="UTF-8"?>
        <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        version="2.5">

  <!--配置DispatcherServlet-->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>com.primo.servlet.DispatcherServlet</servlet-class>
    <!--这里还需要额外的配置-->
    <!--1. 要告诉这个DispatcherServlet扫描哪个包?-->
    <init-param>
      <param-name>packageName</param-name>
      <param-value>com.primo.Controller</param-value>
    </init-param>
    <!--2. 让servlet初始化的时机提前-->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
</web-app>

2.7 pom.xml文件配置

配置项目依赖jar包

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.primo</groupId>
  <artifactId>myMVC</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
	<!--Servlet jar包-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!--lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.18</version>
    </dependency>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

</project>

三. 测试

1. 部署tomcat

tomcat配置

2. 访问测试

  • 启动tomcat,在浏览器中访问
    在这里插入图片描述
  • 控制台中执行了UserController的register方法,访问成功
    在这里插入图片描述

四. 总结

此案例实现了一个简单的MVC框架,对学习理解Java中最常用的SpringMVC框架有一定的帮助。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法提供完整的项目开发服务。但是,我可以提供一些资源和指导,帮助您开始使用Spring框架搭建Java项目。 首先,您需要了解Spring框架的基本概念和原理。Spring是一个轻量级的Java框架,它提供了依赖注入(Dependency Injection)和面向切面编程(Aspect Oriented Programming)等功能,可以简化Java应用程序的开发。您可以在Spring官方网站上找到相关的文档和教程,学习Spring框架的基础知识。 接下来,您需要选择一个Java开发工具,例如Eclipse或IntelliJ IDEA。这些工具都有很好的Spring集成支持,可以帮助您快速搭建Spring项目。 在开始项目开发之前,您需要确定项目的需求和架构。您可以使用Spring Boot来快速搭建一个基于MVC架构的Web应用程序。Spring Boot提供了自动配置和快速开发功能,可以帮助您快速搭建Java Web应用程序。您可以在Spring Boot官方网站上找到相关的文档和教程,学习Spring Boot的基础知识。 最后,您可以使用Maven或Gradle等构建工具来管理项目依赖和构建过程。这些工具可以帮助您快速构建和部署Java应用程序。您可以在Maven或Gradle官方网站上找到相关的文档和教程,学习构建工具的基础知识。 总之,Spring框架一个非常强大和灵活的Java框架,可以帮助您快速开发高效的Java应用程序。通过学习和实践,您可以逐步掌握Spring框架的使用方法,并搭建出符合您需求的Java项目。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值