SpringMVC實現

一:編寫注解

Controller注解

開發(fā)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注解

開發(fā)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 "";

}

二、編寫核心的注解處理器

開發(fā)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;

/**
 * 自定義注解的核心處理器,負責調用目標業(yè)務方法處理用戶請求,類似于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.創(chuàng)建類的實例
        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())) {
                    //找到要執(zhí)行的目標方法
                    method = m;
                    break;
                }

            }

        }

        //6.執(zhí)行url對應的方法,處理用戶請求

        if (method != null) {
            Object retObject = null;
            try {
                //利用反射執(zhí)行這個方法
                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){//循環(huán)方法购披,找匹配的方法進行執(zhí)行
                    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將存儲到一個靜態(tài)Map集合中郎逃。
3.當用戶請求時(無論是get還是post請求)哥童,會調用封裝好的execute方法 ,
execute會先獲取請求的url褒翰,然后解析該URL贮懈,根據解析好的URL從Map集合中取出要調用的目標類 ,再遍歷目標類中定義的所有方法优训,找到類中使用了RequestMapping注解的那些方法朵你,判斷方法上面的RequestMapping注解的value屬性值是否和解析出來的URL路徑一致,如果一致,說明了這個就是要調用的目標方法揣非,此時就可以利用java反射機制先實例化目標類對象抡医,然后再通過實例化對象調用要執(zhí)行的方法處理用戶請求。

在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");


        //執(zhí)行完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>
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末早敬,一起剝皮案震驚了整個濱河市忌傻,隨后出現的幾起案子,更是在濱河造成了極大的恐慌搞监,老刑警劉巖水孩,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異琐驴,居然都是意外死亡俘种,警方通過查閱死者的電腦和手機秤标,發(fā)現死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來安疗,“玉大人抛杨,你說我怎么就攤上這事够委〖隼啵” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵茁帽,是天一觀的道長玉罐。 經常有香客問我,道長潘拨,這世上最難降的妖魔是什么吊输? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮铁追,結果婚禮上季蚂,老公的妹妹穿的比我還像新娘。我一直安慰自己琅束,他們只是感情好扭屁,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涩禀,像睡著了一般料滥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艾船,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天葵腹,我揣著相機與錄音,去河邊找鬼屿岂。 笑死践宴,一個胖子當著我的面吹牛,可吹牛的內容都是我干的爷怀。 我是一名探鬼主播阻肩,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霉撵!你這毒婦竟也來了磺浙?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤徒坡,失蹤者是張志新(化名)和其女友劉穎撕氧,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體喇完,經...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡伦泥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片不脯。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡府怯,死狀恐怖,靈堂內的尸體忽然破棺而出防楷,到底是詐尸還是另有隱情牺丙,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布复局,位于F島的核電站冲簿,受9級特大地震影響,放射性物質發(fā)生泄漏亿昏。R本人自食惡果不足惜峦剔,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望角钩。 院中可真熱鬧吝沫,春花似錦、人聲如沸递礼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宰衙。三九已至平道,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間供炼,已是汗流浹背一屋。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袋哼,地道東北人冀墨。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像涛贯,于是被迫代替她去往敵國和親诽嘉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容