1. 程式人生 > >基於servlet+filter+反射模擬實現天貓首頁的後端

基於servlet+filter+反射模擬實現天貓首頁的後端

前言:為了深入web原理,本專案沒有使用框架,主要描述了從請求到頁面展現的思路,詳情請見文末的具體專案

一、為什麼要用filter?直接servlet實現不就行了

因為天貓這樣的專案需要很多servlet處理具體種類的業務,比如後臺的管理頁面有增刪改查,訂單頁面也有增刪改查,每一個操作都需要寫一個servlet。使用filter+反射可以解決這個冗餘的問題。

二、從瀏覽器輸入路徑到filter

舉個例子,我們本地天貓專案的首頁地址為http://127.0.0.1:8080/tmall/forehome,tmall此處是專案名,我們在web.xml設定filter攔截所有請求,url-pattern設定的/*,所有請求都會對映到tmall.filter.ForeServletFilter這個Java類。

    <filter>
        <filter-name>ForeServletFilter</filter-name>
        <filter-class>tmall.filter.ForeServletFilter</filter-      class>
    </filter>
    <filter-mapping>
        <filter-name>ForeServletFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

在filter的doFilter方法裡面判斷請求,如是/fore開頭的,跳轉到前臺對應的servlet再做具體邏輯資料處理。

@WebFilter(filterName = "ForeServletFilter")
public class ForeServletFilter implements Filter {
    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //獲取從專案名開始的路徑
        String uri = request.getRequestURI();
        //獲取專案名路徑
        String contextPath = request.getContextPath();
        //獲得路徑字尾
        String path = StringUtils.remove(uri, contextPath);
        if(path.startsWith("/fore")&&!path.startsWith("/foreServlet")) {
            //需要將方法名取出並且放到session裡面
            String method = StringUtils.substringAfterLast(path, "fore");
            request.setAttribute("method", method);
            //跳轉向foreServlet
            request.getRequestDispatcher("/foreServlet").forward(request, response);
            return;
        }
        chain.doFilter(request, response);

    }

    @Override
    public void init(FilterConfig config) throws ServletException {

    }

}

三、從filter跳轉到servlet

web.xml設定servlet對映,跳轉後來到ForeServlet這個Java類。

    <servlet>
        <servlet-name>ForeServlet</servlet-name>
        <servlet-class>tmall.servlet.ForeServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>ForeServlet</servlet-name>
        <url-pattern>/foreServlet</url-pattern>
    </servlet-mapping>

這裡我們只寫具體方法而不寫service方法,讓它繼承其父類BaseForeServlet的service方法。這裡的具體方法就包括了前面所說的各種後臺的管理頁面和訂單頁面的增刪改查方法,此處舉個首頁的例子,定義一個home()方法:

public class ForeServlet extends BaseForeServlet {
    public String home(HttpServletRequest request, HttpServletResponse response, Page page) {
        List<Category> cs = categoryDAO.list(page.getStart(), page.getCount());
        //填充資料
        request.setAttribute("cs", cs);
        new ProductDAO().fill(cs);
        new ProductDAO().fillByRow(cs);
        return "home.jsp";
    }
}

四、使用反射呼叫具體方法

那麼是如何呼叫到這個子類servlet的home()方法的呢?
這裡我們通過BaseForeServlet的service方法,首先擷取路徑裡面的方法名,然後使用反射執行子類的home()方法,將資料填充好。

@WebServlet(name = "BaseForeServlet")
public abstract class BaseForeServlet extends HttpServlet {
    
    protected CategoryDAO categoryDAO = new CategoryDAO();
    protected OrderDAO orderDAO = new OrderDAO();
    protected OrderItemDAO orderItemDAO = new OrderItemDAO();
    protected ProductDAO productDAO = new ProductDAO();
    protected ProductImageDAO productImageDAO = new ProductImageDAO();
    protected PropertyDAO propertyDAO = new PropertyDAO();
    protected PropertyValueDAO propertyValueDAO = new PropertyValueDAO();
    protected ReviewDAO reviewDAO = new ReviewDAO();
    protected UserDAO userDAO = new UserDAO();
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = (String)req.getAttribute("method");
        Method md = null;
        Page page = new Page(1, 5);
        try {
            md = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class,
                    javax.servlet.http.HttpServletResponse.class, Page.class);
            String redirect = md.invoke(this, req, resp, page).toString();
            if (redirect.startsWith("@")) {
                resp.sendRedirect(redirect);
            }else if (redirect.startsWith("%")) {
                resp.sendRedirect(redirect);
            }else {
                req.getRequestDispatcher(redirect).forward(req, resp);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

五、頁面呈現

foreServlet返回home.jsp到父類,父類判斷路徑字首,然後跳轉到對應的頁面。

String redirect = md.invoke(this, req, resp, page).toString();
            if (redirect.startsWith("@")) {
                resp.sendRedirect(redirect);
            }else if (redirect.startsWith("%")) {
                resp.sendRedirect(redirect);
            }else {
                req.getRequestDispatcher(redirect).forward(req, resp);
            }

四、總結

本專案我們還暗含了MVC的設計模式,Model是Dao、Bean等資料,View是jsp頁面,Control是servlet。因為篇幅有限,很多內容都沒有展示,具體專案詳見網上一個大神的專案:天貓J2EE項