搭建一个简单的Java MVC框架

  • 一 . 前言
  • 二. 代码实现
  • 1. 思路分析
  • 2. 代码实现
  • 2.1 Controller注解
  • 2.2 RequestMapping注解
  • 2.3 UserController
  • 2.4 MethodBean
  • 2.5 DispatcherServlet
  • 2.6 ClassScannerUtils
  • 2.7 web.xml文件配置
  • 2.7 pom.xml文件配置
  • 三. 测试
  • 1. 部署tomcat
  • 2. 访问测试
  • 四. 总结


一 . 前言

自定义一个简单的web MVC 框架,实现一个Controller控制处理多个请求。

在IDEA中创建Maven web项目,最终项目层级结构如下:

java 怎么搭建框架 java框架搭建步骤_xml

二. 代码实现

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

java 怎么搭建框架 java框架搭建步骤_maven_02

2. 访问测试

  • 启动tomcat,在浏览器中访问
  • 控制台中执行了UserController的register方法,访问成功

四. 总结

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