【Java】自定义MVC框架

4 篇文章 0 订阅
1 篇文章 0 订阅

 

目录

1.自定义SpringMVC框架初级版本

2.自定义SpringMVC框架终极版本


1.自定义SpringMVC框架初级版本

  1. 创建@RequestMapping注解

    1. 注解的作用主要是为了给servlet里面的方法做映射的。

    2. 注解一般是作用于方法上,并且保留到运行的时候还要存在于字节码

       RequestMapping
      1. 这是一个注解,它要打在模块化的Servlet的方法上。
          2. 用于表示当什么样的请求来的时候,就执行这个方法
          3.要明确内容:
              3.1 注解打在方法上
              3.2 注解要保留到运行阶段,
              3.3 这个注解不是空注解,所以它需要一个value属性,用来写地址:  @RequestMapping("/aa/bb")
       BaseServlet这是一个总管类,专门用来抓请求,抓尾巴带 .do的请求
      
          1. 它必须是一个Servlet,它才能抓请求。
          2. 抓到请求之后,要对这个请求进行处理,然后去调用对应模块类的方法,所以它需要重写service方法
              2.1 从请求地址里面截取出来后面的映射路径
                  localhost:82/user/reigster.do  =====  /user/register
              2.2 扫描这个包下的所有类com.jcli.servlet03 ,得到这个包下的所有类的字节码
              2.3 遍历每一个字节码,然后得到类中的所有方法
              2.4 遍历每一个方法,然后取出方法身上的注解 @RequestMapping的内容
              2.5 判断如果有这个注解的话,就把注解里面的value属性的值(注解里面的字符串)给取出来
              2.6 和我们在2.1 截取出来的字符串进行比较,如果一样,就表示找到这个方法了。
              2.7 就让这个方法执行即可。用反射来调用即可!

  2. 创建UserController (UserServlet) , 定义方法,

  3. 在这个方法上面添加@RequestMapping注解

  4. 创建DispatcherServlet (BaseServlet)继承HttpServlet, 路径配置 *.do

  5. 在DispatcherServlet的 重写的service()方法里面

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

存在问题:

  1. 在总的控制器里面扫描指定包的时候,扫描的有点多了。

  2. 等来了请求再去解析,再去反射创建对象调用 ---> 影响处理请求的速度的

  3. 扫描的包名写死了  

2.自定义SpringMVC框架终极版本

  1. 在项目部署的时候,就马上启动扫描的工作了。

    a. 把扫描的工作提前放到servlet的init方法去做

    b. 让这个init方法调用的时机再提前一些,提前到项目发布的时候就执行。

    c. 设置 <load-on-startup> 1</load-on-startup>

  2. DispatcherServlet注册的时候,不要使用注解来注册了,而是使用xml来注册, 在xml里面注册的时候,就可以配置servlet的初始化参数,用户指定扫描具体那个包。 <init-param>

  3. 扫描得到包下的所有类了之后,不是每一个类我们都要查看它的所有方法有没有requestMapping这个注解

    1. 只看类上有没有一个注解@Controller , 这个注解是自定义的注解。

    2. 谁身上有这个注解,我们就解析这个类里面的所有方法。

  4. 在init方法里面完成扫描的工作之后,需要使用一个Map集合来进行映射关系,也就是完成扫描工作之后,使用一个map集合来包装 映射的地址和controller的对象。 map集合里面就包装了请求地址和调用方法的一个关系。 KEY : 请求地址 , value : 包装的javaBean。

    class MethodBean{

    private Method method; //具体的方法对象

    private Object obj; //调用这个方法要用的实例对象

    }

    Map<String , MethodBean> map ;

    MethodBean mb = new MethodBean(方法对象 , 类的实例对象);

    map.put("/user/register" , mb);

  5. 在请求来的时候,在service里面获取请求的地址

  6. 截获请求的地址了之后,就可以直接问map要方法来调用。

    MethodBean mb = map.get("/user/register");

    Method m = mb.getMethod();

    m.invoke(mb.getObj() , req , resp);

package com.jcli.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//打在类上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
package com.jcli.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//打在方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value();
}
package com.jcli.controller;

import com.jcli.annotation.Controller;
import com.jcli.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//可以处理请求~!
@Controller
public class UserController {
    @RequestMapping("/user/register")
    public void register(HttpServletRequest request, HttpServletResponse response){
        System.out.println("执行了UserController的register方法~!");
    }
}
package com.jcli.bean;

import java.lang.reflect.Method;
//主要是用来封装 被调用的方法和 调用这个方法用到的实例对象
public class MethodBean {
    private Method m ; //要执行的方法对象
    private Object o ;  // 要调用上面方法用到的实例对象

    public MethodBean() {
    }

    public MethodBean(Method m, Object o) {
        this.m = m;
        this.o = o;
    }

    public Method getM() {
        return m;
    }

    public void setM(Method m) {
        this.m = m;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }
}

 

package com.jcli.utils;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
 * @Description:扫描包下的类
 */
public class ClassScannerUtils {

    /**
     * 获得包下面的所有的class
     * @param
     * @return List包含所有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();
                }
            }
        }
    }


}

注册: 
    1. Servlet注册时候要用xml 注册, 不要使用注解测试
    2. 需要让这个Servlet初始化的时机更提前一些,所以需要写上 <load-on-startup>1</load-on-startup>
    3. 需要在注册的时候,提供一个初始化参数,这个初始化参数就是用来设置扫描哪个包!
        <init-param>
            <param-name>packageName</param-name>
            <param-value>com.itheima.servlet</param-value>
        </init-param>
    4. 也只抓尾巴带 .do的请求

init : 
	1. 扫描包,找类,找方法,把注解的内容给分解出来。	
	2. 就得到了什么样的地址会执行什么类的什么方法,可以使用一个JavaBean和Map集合来封装他们
        class MethodBean{
            private Method m;  //这个方法
            private Object o;  // 调用这个方法要用的对象
        }
	
		map.put(映射地址 ,MethodBean对象 );

service :
	1. 截取地址,  /user/register
	2. 拿着这个地址,去问map要东西:  
  
package com.jcli.servlet;

import com.jcli.annotation.Controller;
import com.jcli.annotation.RequestMapping;
import com.jcli.bean.MethodBean;
import com.jcli.utils.ClassScannerUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//这是一个Servlet,专门用来抓尾巴带 .do的请求。
public class DispatherServlet extends HttpServlet {
    private 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 annotation = method.getAnnotation(RequestMapping.class);
                        //    8.判断有没有注解
                        if (annotation != null) {
                            //    9.取出来注解的内容
                            String value = annotation.value();
                            //    10.封装方法和对象到javaBean
                            MethodBean methodBean = new MethodBean(method, clazz.newInstance());
                            //    11.封装到集合
                            map.put(value, methodBean);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            //1.获取地址
            String uri = req.getRequestURI();
            String path = uri.substring(0, uri.lastIndexOf("."));
            //    2.拿着地址去问map集合要对象
            MethodBean methodBean = map.get(path);
            //    3.找到则执行
            if (methodBean!=null){
                Method m = methodBean.getM();
                Object o = methodBean.getO();
                m.invoke(o,req,req);
            }else {
                System.out.println("没有找到要执行的方法:" + path);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--注册DispatcherServlet-->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>com.jcli.servlet.DispatherServlet</servlet-class>

        <!--通过初始化参数的方式,告诉servlet,要扫描哪个包-->
        <init-param>
            <param-name>packageName</param-name>
            <param-value>com.jcli.controller</param-value>
        </init-param>

        <!--项目启动,就执行这个类的初始化方法-->
        <load-on-startup>1</load-on-startup>
    </servlet>

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


</web-app>

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值