前置知识

前置准备

知识准备

已掌握JavaSE/MySQL/JDBC+HTML/CSS/JavaScript基础

并已完成了Javaweb前置知识的学习

01-JavaWeb-HTML初识

02-JavaWeb-CSS初识

03-JavaWeb-JavaScript初识

04-JavaScript基础应用-鼠标悬浮/离开表格格式变化

05-JavaWeb-Tomcat8安装、Servlet初识

06-JavaWeb-Servlet方法/生命周期、HTTP/会话session

07-JavaWeb-视图模板技术Thymeleaf的使用

08-JavaWeb-Servlet保存作用域

09-JavaWeb-阶段性项目1:最简单的后台库存管理系统

10-JavaWeb阶段性项目1:系统的servlet优化1

11-JavaWeb阶段性项目1:系统的servlet优化2

12-JavaWeb阶段性项目1:系统的servlet优化3

资源准备

尚硅谷丨2022版JavaWeb教程视频

教学资源

https://pan.baidu.com/s/1TS7QJ_a2vHHmXkggAs8RMQ

提取码:yyds


servlet优化的过程4

提取视图资源处理通用代码

java一个项目要多久_数据

beanMap,一个个Controller形成的容器

fruit.do对应-FruitController

①确认是哪个Controller(xml文件)

②确认是哪个方法(operate)

再拷贝一份pro16,继续开始优化。。。

优化点分析

FruitController中的每一个方法都需要从请求中获取参数.

都要进行客户端重定向。

public class FruitController extends ViewBaseServlet {

    private ServletContext servletContext ;

    public void setServletContext(ServletContext servletContext) throws ServletException {
        this.servletContext = servletContext;
        super.init(servletContext);
    }

    private FruitDAO fruitDAO = new FruitDAOImpl();
    

    private void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.设置编码
        req.setCharacterEncoding("UTF-8");
        //2.获取参数
        String fidStr = req.getParameter("fid");
        int fid = Integer.parseInt(fidStr);
        String fname = req.getParameter("fname");
        String priceStr = req.getParameter("price");
        int price = Integer.parseInt(priceStr);
        String fcountStr = req.getParameter("fcount");
        Integer fcount = Integer.parseInt(fcountStr);
        String remark = req.getParameter("remark");

        //3.执行更新
        fruitDAO.updateFruit(new Fruit(fid,fname,price,fcount,remark));

        //4.资源跳转
        //super.processTemplate("index",req,resp);
        //此处需要重定向,目的是重新给IndexServlet发请求,然后覆盖到session中,这样index页面上显示的数据才是最新的
        resp.sendRedirect("fruit.do");//重定向,重新给fruit.do,给session重新给fruit更改后的数据
    }

    private void edit(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String fidStr = req.getParameter("fid");
        //HTML是模板,thymeleaf是引擎,我们在servlet中调用了引擎并且给了引擎需要的模板和参数
        if(StringUtil.isNotEmpty(fidStr)){
            int fid = Integer.parseInt(fidStr);
            Fruit fruit = fruitDAO.getFruitByFid(fid);
            //获取到fruit对象后将之放置在session作用域
            req.setAttribute("fruit",fruit);
            //thymeleaf的viewBaseServlet中的方法,处理模板数据
            super.processTemplate("edit",req,resp);
        }
    }

    private void del(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //获取fid
        String fidStr = req.getParameter("fid");
        if(StringUtil.isNotEmpty(fidStr)){
            int fid = Integer.parseInt(fidStr);
            fruitDAO.delFruit(fid);

            resp.sendRedirect("fruit.do");
        }
    }

    private void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.设置编码
        req.setCharacterEncoding("UTF-8");
        //2.获取参数
        String fname = req.getParameter("fname");
        String priceStr = req.getParameter("price");
        int price = Integer.parseInt(priceStr);
        String fcountStr = req.getParameter("fcount");
        Integer fcount = Integer.parseInt(fcountStr);
        String remark = req.getParameter("remark");

        Fruit fruit = new Fruit(0, fname, price, fcount, remark);

        fruitDAO.addFruit(fruit);

        resp.sendRedirect("fruit.do");
    }

    private void index(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
        //添加关键词查询功能

        Integer pageNo = 1 ;
        HttpSession session = request.getSession() ;

        String oper = request.getParameter("oper");
        //如果oper!=null 说明是通过表单的查询按钮点击过来的
        //如果oper = null 说明不是通过表单的查询按钮点击过来的
        String keyword = null;
        if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){
            //说明是通过表单的查询按钮点击过来的
            //此时pageNo应该还原为1,keyword应该从请求参数中获取
            pageNo = 1;
            keyword = request.getParameter("keyword");
            if(StringUtil.isEmpty(keyword)){
                keyword = "";
            }
            session.setAttribute("keyword",keyword);
        }else {
            //说明此处不是通过表单的查询按钮点击过来的,比如是点击上一页、下一页或直接在地址栏输入
            //此时keyword应该从session作用域中获取
            String pageNoStr = request.getParameter("pageNo");
            if(StringUtil.isNotEmpty(pageNoStr)){
                pageNo = Integer.parseInt(pageNoStr);
            }
            Object keywordObj =session.getAttribute("keyword");
            if(keywordObj != null){
                keyword = (String) keywordObj;
            }else {
                keyword = "" ;
            }
        }

        session.setAttribute("pageNo",pageNo);

        FruitDAO fruitDAO = new FruitDAOImpl();
        List<Fruit> fruitList = fruitDAO.getFruitList(keyword,pageNo);

        session.setAttribute("fruitList",fruitList);

        //总记录条数
        int fruitCount = fruitDAO.getFruitCount(keyword);
        //总页数
        int pageCount = (fruitCount+5-1)/5 ;
        /*
        总记录条数       总页数
        1               1
        5               1
        6               2
        10              2
        11              3
        fruitCount      (fruitCount+5-1)/5
         */
        session.setAttribute("pageCount",pageCount);

        //此处的视图名称是 index
        //那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
        //逻辑视图名称 :   index
        //物理视图名称 :   view-prefix + 逻辑视图名称 + view-suffix
        //所以真实的视图名称是:      /       index       .html
        super.processTemplate("index",request,response);
    }
}

改FruitController

改update方法

public class FruitController extends ViewBaseServlet {

    private FruitDAO fruitDAO = new FruitDAOImpl();

    private void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.设置编码
        req.setCharacterEncoding("UTF-8");
        //2.获取参数
        String fidStr = req.getParameter("fid");
        int fid = Integer.parseInt(fidStr);
        String fname = req.getParameter("fname");
        String priceStr = req.getParameter("price");
        int price = Integer.parseInt(priceStr);
        String fcountStr = req.getParameter("fcount");
        Integer fcount = Integer.parseInt(fcountStr);
        String remark = req.getParameter("remark");

        //3.执行更新
        fruitDAO.updateFruit(new Fruit(fid,fname,price,fcount,remark));

        //4.资源跳转
        //super.processTemplate("index",req,resp);
        //此处需要重定向,目的是重新给IndexServlet发请求,然后覆盖到session中,这样index页面上显示的数据才是最新的
        resp.sendRedirect("fruit.do");//重定向,重新给fruit.do,给session重新给fruit更改后的数据
    }
private String update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1.设置编码
    req.setCharacterEncoding("UTF-8");
    //2.获取参数
    String fidStr = req.getParameter("fid");
    int fid = Integer.parseInt(fidStr);
    String fname = req.getParameter("fname");
    String priceStr = req.getParameter("price");
    int price = Integer.parseInt(priceStr);
    String fcountStr = req.getParameter("fcount");
    Integer fcount = Integer.parseInt(fcountStr);
    String remark = req.getParameter("remark");

    //3.执行更新
    fruitDAO.updateFruit(new Fruit(fid,fname,price,fcount,remark));

    //4.资源跳转
    //super.processTemplate("index",req,resp);
    //此处需要重定向,目的是重新给IndexServlet发请求,然后覆盖到session中,这样index页面上显示的数据才是最新的
    resp.sendRedirect("fruit.do");//重定向,重新给fruit.do,给session重新给fruit更改后的数据
    return "redirect:fruit.do"; 
}

这样就把redirect:fruit.do这个字符串也交给了中央控制器,由它统一转发、重定向

改DispatcherServlet

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");

        String servletPath = req.getServletPath();
        servletPath = servletPath.substring(1);
        int lastDotIndex = servletPath.lastIndexOf(".do");
        servletPath = servletPath.substring(0,lastDotIndex);

        Object controllerBeanObj = beanMap.get(servletPath);

        String operate = req.getParameter("operate");
        if(StringUtil.isEmpty(operate)){
            operate = "index";
        }

        try {
            Method method = controllerBeanObj.getClass().getDeclaredMethod(operate,HttpServletRequest.class,HttpServletResponse.class);
            if (method != null){
                //controller组件中的方法调用
                method.setAccessible(true);
                Object returnObj = method.invoke(controllerBeanObj,req,resp);
                //视图处理
                String methodReturnStr = (String) returnObj;
                if(methodReturnStr.startsWith("redirect:")){
                    String redirectStr = methodReturnStr.substring("redirect:".length());
                    //这里就把XXcontroller里各个方法里重定向的方法转移到中央控制器dispatcherServlet里来了
                    resp.sendRedirect(redirectStr);
                }
            }else {
                throw new RuntimeException("operate值非法!");
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

1,处理请求参数、2、调用目标方法、3、渲染页面(视图处理)

设置编码也可以挪到中央控制器里

改edit方法

private String edit(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String fidStr = req.getParameter("fid");
    //HTML是模板,thymeleaf是引擎,我们在servlet中调用了引擎并且给了引擎需要的模板和参数
    if(StringUtil.isNotEmpty(fidStr)){
        int fid = Integer.parseInt(fidStr);
        Fruit fruit = fruitDAO.getFruitByFid(fid);
        //获取到fruit对象后将之放置在session作用域
        req.setAttribute("fruit",fruit);
        //thymeleaf的viewBaseServlet中的方法,处理模板数据
        //super.processTemplate("edit",req,resp);
        return "edit";
    }
    return "error";
}

相应的,在DispatcherServlet里添加新的判断

else {
    super.processTemplate(methodReturnStr,req,resp);
}

为什么要集中处理重定向和转发?

这就跟现实生活是一样的,建立一个专门用于解决重定向和转发的服务站,有事直接找这个就行了,代码冗余变得很少了

后面业务只用写controller控制器了 servlet 视图解析都不用管了

DispatcherServlet中的responds都可以去掉了

改好之后代码

package com.fancy.myssm.basedao.myspringmvc;

import com.fancy.myssm.basedao.util.StringUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet{
    private Map<String,Object> beanMap = new HashMap<>();
    //servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
    public DispatcherServlet(){

    }

    //应该使用init方法加载而不是构造器
    public void init() throws ServletException {
        super.init();
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            //创建document对象
            Document document = documentBuilder.parse(inputStream);

            //获取所有的bean节点
            NodeList beanNodeList = document.getElementsByTagName("bean");

            for (int i = 0; i < beanNodeList.getLength(); i++) {
                Node beanNode = beanNodeList.item(i);
                //如果是一个元素节点,就强转为元素节点
                if(beanNode.getNodeType() == Node.ELEMENT_NODE){
                    Element beanElement = (Element) beanNode;
                    //获取了bean中的id属性、class属性
                    String beanId = beanElement.getAttribute("id");
                    String className = beanElement.getAttribute("class");
                    //获取全类名后,要获取它的实例对象
                    Class controllerBeanClass = Class.forName(className);
                    Object beanObj = controllerBeanClass.newInstance() ;

                    //将beanId、beanObj放入Map中
                    beanMap.put(beanId,beanObj);
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");

        String servletPath = req.getServletPath();
        servletPath = servletPath.substring(1);
        int lastDotIndex = servletPath.lastIndexOf(".do");
        servletPath = servletPath.substring(0,lastDotIndex);

        Object controllerBeanObj = beanMap.get(servletPath);

        String operate = req.getParameter("operate");
        if(StringUtil.isEmpty(operate)){
            operate = "index";
        }

        try {
            Method method = controllerBeanObj.getClass().getDeclaredMethod(operate,HttpServletRequest.class);
            if (method != null){
                //controller组件中的方法调用
                method.setAccessible(true);
                Object returnObj = method.invoke(controllerBeanObj,req);
                //视图处理
                String methodReturnStr = (String) returnObj;
                if(methodReturnStr.startsWith("redirect:")){
                    String redirectStr = methodReturnStr.substring("redirect:".length());
                    //这里就把XXcontroller里各个方法里重定向的方法转移到中央控制器dispatcherServlet里来了
                    resp.sendRedirect(redirectStr);
                }else {
                    super.processTemplate(methodReturnStr,req,resp);
                }
            }else {
                throw new RuntimeException("operate值非法!");
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
package com.fancy.fruit.controllers;

import com.fancy.fruit.dao.FruitDAO;
import com.fancy.fruit.dao.impl.FruitDAOImpl;
import com.fancy.fruit.pojo.Fruit;
import com.fancy.myssm.basedao.myspringmvc.ViewBaseServlet;
import com.fancy.myssm.basedao.util.StringUtil;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;


public class FruitController {

    private FruitDAO fruitDAO = new FruitDAOImpl();

    private String update(HttpServletRequest req) throws ServletException{

        //2.获取参数
        String fidStr = req.getParameter("fid");
        int fid = Integer.parseInt(fidStr);
        String fname = req.getParameter("fname");
        String priceStr = req.getParameter("price");
        int price = Integer.parseInt(priceStr);
        String fcountStr = req.getParameter("fcount");
        Integer fcount = Integer.parseInt(fcountStr);
        String remark = req.getParameter("remark");

        //3.执行更新
        fruitDAO.updateFruit(new Fruit(fid,fname,price,fcount,remark));

        //4.资源跳转
        //super.processTemplate("index",req,resp);
        //此处需要重定向,目的是重新给IndexServlet发请求,然后覆盖到session中,这样index页面上显示的数据才是最新的
        //resp.sendRedirect("fruit.do");//重定向,重新给fruit.do,给session重新给fruit更改后的数据
        return "redirect:fruit.do";
    }

    private String edit(HttpServletRequest req) throws ServletException, IOException {
        String fidStr = req.getParameter("fid");
        //HTML是模板,thymeleaf是引擎,我们在servlet中调用了引擎并且给了引擎需要的模板和参数
        if(StringUtil.isNotEmpty(fidStr)){
            int fid = Integer.parseInt(fidStr);
            Fruit fruit = fruitDAO.getFruitByFid(fid);
            //获取到fruit对象后将之放置在session作用域
            req.setAttribute("fruit",fruit);
            //thymeleaf的viewBaseServlet中的方法,处理模板数据
            //super.processTemplate("edit",req,resp);
            return "edit";
        }
        return "error";
    }

    private String del(HttpServletRequest req) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //获取fid
        String fidStr = req.getParameter("fid");
        if(StringUtil.isNotEmpty(fidStr)){
            int fid = Integer.parseInt(fidStr);
            fruitDAO.delFruit(fid);

            return "redirect:fruit.do";
        }
        return "error";
    }

    private String add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.设置编码
        req.setCharacterEncoding("UTF-8");
        //2.获取参数
        String fname = req.getParameter("fname");
        String priceStr = req.getParameter("price");
        int price = Integer.parseInt(priceStr);
        String fcountStr = req.getParameter("fcount");
        Integer fcount = Integer.parseInt(fcountStr);
        String remark = req.getParameter("remark");

        Fruit fruit = new Fruit(0, fname, price, fcount, remark);

        fruitDAO.addFruit(fruit);

        return "redirect:fruit.do";
    }


    private String index(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
        //添加关键词查询功能

        Integer pageNo = 1 ;
        HttpSession session = request.getSession() ;

        String oper = request.getParameter("oper");
        //如果oper!=null 说明是通过表单的查询按钮点击过来的
        //如果oper = null 说明不是通过表单的查询按钮点击过来的
        String keyword = null;
        if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){
            //说明是通过表单的查询按钮点击过来的
            //此时pageNo应该还原为1,keyword应该从请求参数中获取
            pageNo = 1;
            keyword = request.getParameter("keyword");
            if(StringUtil.isEmpty(keyword)){
                keyword = "";
            }
            session.setAttribute("keyword",keyword);
        }else {
            //说明此处不是通过表单的查询按钮点击过来的,比如是点击上一页、下一页或直接在地址栏输入
            //此时keyword应该从session作用域中获取
            String pageNoStr = request.getParameter("pageNo");
            if(StringUtil.isNotEmpty(pageNoStr)){
                pageNo = Integer.parseInt(pageNoStr);
            }
            Object keywordObj =session.getAttribute("keyword");
            if(keywordObj != null){
                keyword = (String) keywordObj;
            }else {
                keyword = "" ;
            }
        }

        session.setAttribute("pageNo",pageNo);

        FruitDAO fruitDAO = new FruitDAOImpl();
        List<Fruit> fruitList = fruitDAO.getFruitList(keyword,pageNo);

        session.setAttribute("fruitList",fruitList);

        //总记录条数
        int fruitCount = fruitDAO.getFruitCount(keyword);
        //总页数
        int pageCount = (fruitCount+5-1)/5 ;
        /*
        总记录条数       总页数
        1               1
        5               1
        6               2
        10              2
        11              3
        fruitCount      (fruitCount+5-1)/5
         */
        session.setAttribute("pageCount",pageCount);

        //此处的视图名称是 index
        //那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
        //逻辑视图名称 :   index
        //物理视图名称 :   view-prefix + 逻辑视图名称 + view-suffix
        //所以真实的视图名称是:      /       index       .html
        return "index" ;
    }
}

可以看到FruitController已经不用继承viewbaseservlet了,但还是和servlet有耦合,因为方法要传的参数还有request