注解实现请求URL映射到Java方法

效果

收到请求并且鉴权后会自动映射请求到方法上来


image.png

实现

1、定义@Controller 注解,标识类需要被扫描

/**
 * 标识类需要被扫描
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

2、定义@RequestMapping 注解,标识类需要被扫描

/**
 * 映射url到类和方法上
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    //url
    String value();

    //默认为GET
    RequestMethod method() default RequestMethod.DEFAULT;

    //是否需要身份验证
    boolean auth() default true;
}

3、RequestMethod 用于枚举请求的方法

/**
 * 请求方法枚举,只枚举了我们需要的方法
 */
public enum RequestMethod {
    GET, POST, DELETE, DEFAULT
}

4、RequestHandler 用于存放url映射到方法的相关信息

/**
 * 请求处理者类
 *
 * @author Jerry
 * @date 2019/7/22 11:01
 */
public class RequestHandler {
    //映射的路径
    public String path;
    //方法GET、POST等
    public RequestMethod requestMethod;
    //类对象
    public Class clazz;
    //方法对象
    public Method method;
    //类的实例对象
    public Object instance;
    //是否需要身份验证
    public boolean isAuth;
}

5、RequestMappingProcessor 用于扫描注解

/**
 * 注解扫描器
 *
 * @author Jerry
 * @date 2019/7/22 10:51
 */
public class RequestMappingProcessor {
    //存放url到RequestHandler的映射
    public static HashMap<String, RequestHandler> requestMappingMap = new HashMap<>();

    /**
     * 扫描解析指定包下的@Controller和@RequestMapping注解
     * <p>
     * 只能扫描class文件,暂未实现扫描jar包
     *
     * @return 生成的映射表
     */
    public static HashMap<String, RequestHandler> scanRequestMapping() {
        //扫描的包路径
        final String pkgPath = "com/zlt/controller";
        //用于字符拼接
        final String pkgClassPath = pkgPath.replace("/", ".") + ".";

        requestMappingMap.clear();
        Enumeration<URL> enumeration = null;
        try {
            //获取该包下的class文件
            enumeration = Thread.currentThread().getContextClassLoader().getResources(pkgPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (enumeration == null) {
            System.out.println("扫描失败");
            return null;
        }
        try {
            while (enumeration.hasMoreElements()) {
                URL url = enumeration.nextElement();
                File file = new File(url.getFile());
                String[] fileList = file.list();
                if (fileList == null) {
                    System.err.println(file.getAbsolutePath() + "包没有类");
                    return null;
                }
                for (String path : fileList) {
                    Class clazz = Thread.currentThread().getContextClassLoader().loadClass(pkgClassPath + path.substring(0, path.length() - 6));
                    Controller controller = (Controller) clazz.getAnnotation(Controller.class);
                    if (controller != null) {
                        System.out.println(clazz.getName());
                        RequestMapping parentRequestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
                        //类注解上的一级路径
                        String basePath = null;
                        RequestMethod baseMethod = null;
                        if (parentRequestMapping != null) {
                            //获取类注解上的一级路径
                            basePath = ensurePath(parentRequestMapping.value());
                            baseMethod = parentRequestMapping.method();
                            //忽略类注解上的auth
                            //baseAuth = parentRequestMapping.auth();

                            //默认为RequestMethod.GET
                            if (baseMethod == RequestMethod.DEFAULT) baseMethod = RequestMethod.GET;
                        }
                        //生成类的实例对象
                        Object instance = clazz.newInstance();
                        System.out.println("扫描到类 " + instance.getClass().getCanonicalName());
                        for (Method method : clazz.getDeclaredMethods()) {
                            System.out.println("    " + method.getName() + "()");
                            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                            if (requestMapping != null) {//获取到方法上的注解
                                method.setAccessible(true);
                                //生成RequestHandler实例
                                RequestHandler warp = new RequestHandler();
                                //拼接生成最终的url
                                warp.path = basePath + ensurePath(requestMapping.value());
                                //获取方法注解上的RequestMethod
                                RequestMethod requestMethod = requestMapping.method();
                                if (requestMethod == RequestMethod.DEFAULT) {
                                    //如果类注解设置了RequestMethod,而方法注解没有设置RequestMethod,则类注解会覆盖方法注解的请求方法
                                    warp.requestMethod = baseMethod != null ? baseMethod : RequestMethod.GET;//默认为GET
                                } else {//否则使用方法注解上的RequestMethod
                                    warp.requestMethod = requestMethod;
                                }
                                warp.method = method;
                                //默认为true
                                warp.isAuth = requestMapping.auth();
                                warp.clazz = clazz;
                                warp.instance = instance;
                                //检查参数,这里还可以再检查一下返回值类型什么的
                                //这里的方法参数类型和个数都是定死了,可以拓展支持不同参数类型和返回类型
                                if (method.getParameterCount() == 2) {
                                    Class<?>[] classes = method.getParameterTypes();
                                    if (HttpServletRequest.class == classes[0] &&
                                            HttpServletResponse.class == classes[1]) {
                                        requestMappingMap.put(warp.path, warp);
                                        continue;
                                    }
                                }
                                System.err.println(method.getName() + " 参数错误");
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (requestMappingMap != null) {
            System.out.println("url映射:");
            for (Map.Entry<String, RequestHandler> entry : requestMappingMap.entrySet()) {
                RequestHandler warp = entry.getValue();
                System.out.println(warp.path + "  " + warp.requestMethod + "  " + warp.clazz.getCanonicalName() + "." + warp.method.getName() + "()");
            }
        }
        return requestMappingMap;
    }


    /**
     * 规范url格式
     * <p>
     * 标准格式为前有ur开头l“/”而后面没有 如 /user、/user/login等
     */
    private static String ensurePath(String path) {
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        return path;
    }

    public static HashMap<String, RequestHandler> getRequestMappingMap() {
        return requestMappingMap;
    }
}

6、定义过滤器 RequestFilter ,过滤没有映射的url

/**
 * Request过滤类
 * <p>
 * 通过url、method等过滤未映射的Request
 *
 * @author Jerry
 * @date 2019/7/23 15:57
 */
public class RequestFilter implements Filter {
    private HashMap<String, RequestHandler> handlerMap = RequestMappingProcessor.getRequestMappingMap();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = ((HttpServletRequest) servletRequest);
        String fullPath = request.getPathInfo();

        RequestHandler handler = handlerMap.get(fullPath);
        if (handler != null) {
            //把处理者添加到request的attribute中
            request.setAttribute(Config.KEY_REQUEST_HANDLER, handler);
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            //直接就不往下传递请求了,这里可以处理一下,排除一下页面 如druid
            System.out.println(request.getRequestURI() + ":没有映射");
        }
    }
}

7、定义过滤器 AuthenticationFilter ,用于请求鉴权

/**
 * 用来过滤未登录的请求
 *
 * @author Jerry
 * @date 2019/7/22 14:16
 */
public class AuthenticationFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (servletRequest.getAttribute(Config.KEY_REQUEST_DRUID) != null) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        RequestHandler handler = (RequestHandler) servletRequest.getAttribute(Config.KEY_REQUEST_HANDLER);
        if (handler != null) {
            if (handler.isAuth) {//如果需要验证身份
                boolean isLogin = false;
                try {
                    //这里始终为false,需要实现鉴权逻辑
                    isLogin = false;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (isLogin) {//如果鉴权通过
                    filterChain.doFilter(servletRequest, servletResponse);
                } else {
                        //如果鉴权没通过
                }
            } else {
                filterChain.doFilter(servletRequest, servletResponse);
            }
        } else {
            //处理者为空情况
            System.out.println("AuthenticationFilter:" + ((HttpServletRequest) servletRequest).getRequestURI());
        }
    }
}

8、DispatchServlet 用于执行分发请求到对应方法上

/**
 * 负责分发请求的Servlet
 * <p>
 * 根据配置,分发请求到不同的方法
 *
 * @author Jerry
 * @date 2019/7/20 17:41
 */
public class DispatchServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RequestHandler handler = (RequestHandler) request.getAttribute(Config.KEY_REQUEST_HANDLER);
        if (handler != null) {
            try {
                Object data = handler.method.invoke(handler.instance, request, response);
               //自己处理返回值
            } catch (Exception e) {
                response.getWriter().write(ResponseBody.JSON_ERROR_SYSTEM);
                e.printStackTrace();
            }
        } else {
            System.err.println(request.getRequestURI());
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容