SpringMVC实现

一:编写注解

Controller注解

开发Controller注解,这个注解只有一个value属性,默认值为空字符串,代码如下:

package annotation;

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

/**
 * 自定义Controller注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {

    public String value() default "";
}

RequestMapping注解

开发RequestMapping注解,用于定义请求路径,这个注解只有一个value属性,默认值为空字符串,代码如下:

package annotation;

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

/**
 * 自动以RequestMapping注解
 */

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {

    public String value()default "";

}

二、编写核心的注解处理器

开发AnnotationHandleServlet

这里使用一个Servlet来作为注解处理器,编写一个AnnotationHandleServlet,代码如下:

package handler;

import annotation.Controller;
import annotation.RequestMapping;
import utils.BeanUtils;
import utils.DispatchActionConstant;
import utils.RequestMapingMap;
import utils.ScanClassUtil;
import utils.View;
import utils.WebContext;

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.Set;

/**
 * 自定义注解的核心处理器,负责调用目标业务方法处理用户请求,类似于SpringMvc的DespatcherServlet
 *
 * @author itguang
 * @create 2018-04-05 21:54
 **/
public class AnnotationHandleServlet extends HttpServlet {


    /**
     * 从HttpRequest中解析出 请求路径,即 RequestMapping() 的value值.
     *
     * @param request
     * @return
     */
    private String pareRequestURI(HttpServletRequest request) {

        String path = request.getContextPath() + "/";
        String requestUri = request.getRequestURI();
        String midUrl = requestUri.replace(path, "");
        String lastUrl = midUrl.substring(0, midUrl.lastIndexOf("."));


        return lastUrl;
    }


    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException

    {
        System.out.println("AnnotationHandlerServlet-->doGet....");
        this.excute(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("AnnotationHandlerServlet-->doPost....");
        this.excute(request, response);
    }

    private void excute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1.将当前 HttpRequest 放到ThreadLocal中,方便在Controller中使用
        WebContext.requestHodler.set(request);
        //将 HttpReponse 放到ThreadLocal中,方便在Controller中使用
        WebContext.responseHodler.set(response);

        //2.解析请求的url
        String requestUrl = pareRequestURI(request);

        //3.根据 请求的url获取要使用的类
        Class<?> clazz = RequestMapingMap.getClassName(requestUrl);
        //4.创建类的实例
        Object classInstance = BeanUtils.instanceClass(clazz);

        //5.获取类中定义的方法
        Method[] methods = BeanUtils.findDeclaredMethods(clazz);

        //遍历所有方法,找出url与RequestMapping注解的value值相匹配的方法
        Method method = null;
        for (Method m : methods) {

            if (m.isAnnotationPresent(RequestMapping.class)) {
                String value = m.getAnnotation(RequestMapping.class).value();
                if (value != null && !"".equals(value.trim()) && requestUrl.equals(value.trim())) {
                    //找到要执行的目标方法
                    method = m;
                    break;
                }

            }

        }

        //6.执行url对应的方法,处理用户请求

        if (method != null) {
            Object retObject = null;
            try {
                //利用反射执行这个方法
                retObject = method.invoke(classInstance);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

            //如果有返回值,就代表用户需要返回视图
            if (retObject != null) {
                View view = (View) retObject;
                //判断要使用的跳转方式
                if (view.getDispathAction().equals(DispatchActionConstant.FORWARD)) {
                    //使用服务器端跳转方式
                    request.getRequestDispatcher(view.getUrl()).forward(request, response);
                } else if (view.getDispathAction().equals(DispatchActionConstant.REDIRECT)) {
                    //使用客户端跳转方式
                    response.sendRedirect(request.getContextPath() + view.getUrl());
                } else {
                    request.getRequestDispatcher(view.getUrl()).forward(request, response);
                }
            }


        }


    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        /**
         * 重写了Servlet的init方法后一定要记得调用父类的init方法,
         * 否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时
         * 就会出现java.lang.NullPointerException异常
         */
        super.init(config);
        System.out.println("---初始化开始---");
        //获取web.xml中配置的要扫描的包
        String basePackage = config.getInitParameter("basePackage");
        //如果配置了多个包,例如:<param-value>me.gacl.web.controller,me.gacl.web.UI</param-value>
        if (basePackage.indexOf(",")>0) {
            //按逗号进行分隔
            String[] packageNameArr = basePackage.split(",");
            for (String packageName : packageNameArr) {
                initRequestMapingMap(packageName);
            }
        }else {
            initRequestMapingMap(basePackage);
        }
        System.out.println("----初始化结束---");
    }

    /**
     * @Method: initRequestMapingMap
     * @Description:添加使用了Controller注解的Class到RequestMapingMap中
     */
    private void initRequestMapingMap(String packageName){
        //得到扫描包下的class
        Set<Class<?>> setClasses =  ScanClassUtil.getClasses(packageName);
        for (Class<?> clazz :setClasses) {

            if (clazz.isAnnotationPresent(Controller.class)) {
                Method [] methods = BeanUtils.findDeclaredMethods(clazz);
                for(Method m:methods){//循环方法,找匹配的方法进行执行
                    if(m.isAnnotationPresent(RequestMapping.class)){
                        String anoPath = m.getAnnotation(RequestMapping.class).value();
                        if(anoPath!=null && !"".equals(anoPath.trim())){
                            if (RequestMapingMap.getRequesetMap().containsKey(anoPath)) {
                                throw new RuntimeException("RequestMapping映射的地址不允许重复!");
                            }
                            //把所有的映射地址存储起来  映射路径--类
                            RequestMapingMap.put(anoPath, clazz);
                        }
                    }
                }
            }
        }
    }


}

AnnotationHandleServlet的实现思路:
1.AnnotationHandleServlet初始化(init)时扫描指定的包下面使用了Controller注解的类,
2.遍历类中的方法,找到类中使用了RequestMapping注解标注的那些方法,
获取RequestMapping注解的value属性值,value属性值指明了该方法的访问路径,以RequestMapping注解的value属性值作为key,Class类作为value将存储到一个静态Map集合中。
3.当用户请求时(无论是get还是post请求),会调用封装好的execute方法 ,
execute会先获取请求的url,然后解析该URL,根据解析好的URL从Map集合中取出要调用的目标类 ,再遍历目标类中定义的所有方法,找到类中使用了RequestMapping注解的那些方法,判断方法上面的RequestMapping注解的value属性值是否和解析出来的URL路径一致,如果一致,说明了这个就是要调用的目标方法,此时就可以利用java反射机制先实例化目标类对象,然后再通过实例化对象调用要执行的方法处理用户请求。

在Web.xml文件中注册AnnotationHandleServlet

就像使用SpringMvc一样我们也需要在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>AnnotationHandleServlet</servlet-name>
        <servlet-class>handler.AnnotationHandleServlet</servlet-class>
        <init-param>
            <description>配置要扫描包及其子包, 如果有多个包,以逗号分隔</description>
            <param-name>basePackage</param-name>
            <param-value>controller</param-value>

        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>AnnotationHandleServlet</servlet-name>
        <!-- 拦截所有以.do后缀结尾的请求 -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

三, Controller注解和RequestMapping注解测试

新建一个controller包,在该包下面新建 LoginController.java,如:

package controller;

import annotation.Controller;
import annotation.RequestMapping;
import utils.View;
import utils.ViewData;
import utils.WebContext;

import javax.servlet.http.HttpServletRequest;

/**
 * @author itguang
 * @create 2018-04-06 09:26
 **/
@Controller
public class LoginController {


    //使用RequestMapping注解指明forward1方法的访问路径
    @RequestMapping("login2")
    public View forward1() {


        System.out.println("login2...");

        HttpServletRequest request = WebContext.requestHodler.get();

        String username = request.getParameter("username");
        String password = request.getParameter("password");


        //执行完forward1方法之后返回的视图
        return new View("/Login2.jsp");
    }

    /**
     * 处理登录请求,接受参数
     * @return
     */
    @RequestMapping("login")
    public View login(){

        System.out.println("login...");

        //首先获取当前线程的request对象
        HttpServletRequest request = WebContext.requestHodler.get();

        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //将数据存储到ViewData中
        ViewData viewData = new ViewData();
        viewData.put("msg","欢迎你"+username);
        // 相当于
        // request.setAttribute("msg","欢迎你"+username);

      return new View("/index.jsp");
    }
}

index.jsp:

<%--
  Created by IntelliJ IDEA.
  User: itguang
  Date: 2018/4/5
  Time: 15:24
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <label style="color: red;">${msg}</label>
  </body>
</html>

Login2.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
    <title>login2.jsp登录页面</title>
</head>

<body>
<fieldset>
    <legend>用户登录</legend>
    <form action="${pageContext.request.contextPath}/login.do" method="post">
        用户名:<input type="text" value="${param.usename}" name="username">
        <br/>
        密码:<input type="text" value="${param.pwd}" name="password">
        <br/>
        <input type="submit" value="登录"/>
    </form>
</fieldset>
<hr/>
<label style="color: red;">${msg}</label>
</body>
</html>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容