前置知识
前置准备
知识准备
已掌握JavaSE/MySQL/JDBC+HTML/CSS/JavaScript基础
并已完成了Javaweb前置知识的学习
04-JavaScript基础应用-鼠标悬浮/离开表格格式变化
05-JavaWeb-Tomcat8安装、Servlet初识
06-JavaWeb-Servlet方法/生命周期、HTTP/会话session
09-JavaWeb-阶段性项目1:最简单的后台库存管理系统
10-JavaWeb阶段性项目1:系统的servlet优化1
11-JavaWeb阶段性项目1:系统的servlet优化2
12-JavaWeb阶段性项目1:系统的servlet优化3
资源准备
教学资源
https://pan.baidu.com/s/1TS7QJ_a2vHHmXkggAs8RMQ
提取码:yyds
servlet优化的过程4
提取视图资源处理通用代码
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