自定义MethodMVC

问题引出
在实际项目开发的时候,我们没发起一次请求都会调用一个写一个对应servlet来接收数据,而每一个servlet都需要我们到web.xml中进行配置,这样的工作量会很大。
解决方案
我们设想有这样一种模式:
就是当前端发起servlet请求的时候,我们可以在请求的末尾添加一个后缀,(例如: .do),我们统一配置携带这种格式的servlet请求都请求到同一个servlet上,在有这个servlet分发给对于的servlet干活就可以了。


代码步骤
第一步:web.xml配置

<?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">

    <servlet>
        <servlet-name>DispacherServlet</servlet-name>
        <servlet-class>com.gg.controller.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>packageScan</param-name>
<!--            需要扫描的package-->
            <param-value>com.gg.controller</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispacherServlet</servlet-name>
<!--        需要过来的请求,这里我们设置需要过滤掉.do节为的请求-->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

第二步,DispacherServlet的实现

import com.gg.annotation.Controller;
import com.gg.annotation.RequestMapping;
import com.gg.utils.ClassScannerUtils;
import com.gg.utils.MvcMethod;

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.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 存在需要解决的问题:
 * 1. controller包里面任何一个类都会扫描(获得类里面的Method), 加载 --> 扫描太多
 * 2. 等来了请求再去解析,再去反射创建对象调用 ---> 影响处理请求的速度的
 * 3. 扫描的包名写死了
 * 解决:
 * 1.只扫描这个类上面有Controller注解的(获得类里面的Method)
 * 2.提前(服务器启动的时候)解析好, 把对象创建好, method获得好 存到容器【Map】, 等来了请求的时候 根据映射路径(eg: /user/login)直接从容器里面获得对应的Method调用
 * map的key是RequestMapping的value, map的值是对应的MvcMethod
 * 3.配置Servlet的初始化参数
 * */
public class DispatcherServlet extends HttpServlet {

    private Map<String, MvcMethod> methodMap = new HashMap<String, MvcMethod>();
    @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            super.init(config);
            //1.扫描某个包里面的所有类的字节码对象集合List
            String packageScan = config.getInitParameter("packageScan");
            List<Class<?>> classList = ClassScannerUtils.getClasssFromPackage(packageScan);
            //2.遍历字节码对象集合List
            for (Class<?> clazz : classList) {
                if (clazz.isAnnotationPresent(Controller.class)) { //判断Controller类上面是否有Controller注解
                    //3.获得类里面的所有的Method
                    Method[] methods = clazz.getDeclaredMethods();
                    //4.遍历所有的Method
                    for (Method method : methods) {
                        //5.获得method上面的RequestMapping注解 获得注解的value属性值
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        if (requestMapping != null) {
                            String value = requestMapping.value();
                            //6.存到容器Map里面(map的key是RequestMapping的value, map的值是对应的MvcMethod)
                            MvcMethod mvcMethod = new MvcMethod(method, clazz.newInstance(), clazz);
                            methodMap.put(value, mvcMethod);
                        }

                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //1.获得请求的URI和项目部署路径, 截取获得映射路径 eg: /user/login
            String requestURI = request.getRequestURI();   //  /day36c-mvc/user/login.do
            String contextPath = request.getContextPath();//   /day36c-mvc

            String mappingPath = requestURI.substring(contextPath.length(), requestURI.lastIndexOf(".")); //  /user/login


            //2.根据映射路径 从Map获得对应的方法调用
            MvcMethod mvcMethod = methodMap.get(mappingPath);
            if (mvcMethod != null) {
                mvcMethod.getMethod().invoke(mvcMethod.getObj(), request, response);
            }else{
                throw new RuntimeException("找不到资源" + mappingPath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

第三步,工具包的集成
ClassScannerUtils:扫描指定包名,获得该包下的所有class

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;

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();
                }
            }
        }
    }

}

MvcMethod :这是一个javaBean,方便方法的反射调用

import java.lang.reflect.Method;

public class MvcMethod {

    private Method method;  //Method
    private Object obj;    //调用方法, 需要的对象
    private Class clazz;  //当前类的字节码对象

    public MvcMethod() {
    }

    public MvcMethod(Method method, Object obj, Class clazz) {
        this.method = method;
        this.obj = obj;
        this.clazz = clazz;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }
}

自定义Annotation注解:
Controller,用来注解需要扫描的类:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: Controller注解
 * @Author: gg
 */
@Target(ElementType.TYPE) //只能用在类(接口)上面
@Retention(RetentionPolicy.RUNTIME) //任何阶段都有效
public @interface Controller {
}

RequestMapper,用来注解需要调用的方法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: 映射路径注解
 * @Author: gg
 */
@Target({ElementType.METHOD,ElementType.TYPE}) //可以用在方法,类(接口)上面
@Retention(RetentionPolicy.RUNTIME) //任何阶段都有效
public @interface RequestMapping {
    String value();
}

测试类:

import com.gg.annotation.Controller;
import com.gg.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class LoginServlet {
    @RequestMapping("/user/login")
    public void userLogin(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("userLogin被调用了");
        System.out.println("uri->" + req.getRequestURI());
        System.out.println("context->" + req.getContextPath());
    }

    @RequestMapping("/user/update")
    public void userUpdate(HttpServletRequest req, HttpServletResponse resp) {
        System.out.println("userUpdate被调用了----------");
        System.out.println("uri->" + req.getRequestURI());
        System.out.println("context->" + req.getContextPath());
    }
    
    public void courseAdd(HttpServletRequest req, HttpServletResponse resp){
        System.out.println("courseAdd is running");
    }
}

当我们部署好项目在浏览器中输入servlet请求时

http://localhost:8080/DefineMVC/user/login.do
http://localhost:8080/DefineMVC/user/update.do
在这里插入图片描述
当输入没有注解的方法时:http://localhost:8080/DefineMVC/course/add.do
在这里插入图片描述
这样我们就只需要专注写每张表下面的功能就行了,不需要再去创建Servlet.
项目源码下载地址:
链接:https://pan.baidu.com/s/1PsB3PEIOyElQhRGawcNcbA
提取码:izqt

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值