项目源码地址:https://gitee.com/qyh24/java-web.git
一、原有项目结构
- 最初的做法是: 一个请求对应一个Servlet,这样存在的问题是servlet太多了
- 把一些列的请求都对应一个Servlet, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet -> 合并成FruitServlet
通过一个operate的值来决定调用FruitServlet中的哪一个方法
使用的是switch-case - 在上一个版本中,Servlet中充斥着大量的switch-case,试想一下,随着我们的项目的业务规模扩大,那么会有很多的Servlet,也就意味着会有很多的switch-case,这是一种代码冗余
因此,我们在servlet中使用了反射技术,我们规定operate的值和方法名一致,那么接收到operate的值是什么就表明我们需要调用对应的方法进行响应,如果找不到对应的方法,则抛异常 - 在上一个版本中我们使用了反射技术,但是其实还是存在一定的问题:每一个servlet中都有类似的反射技术的代码。因此继续抽取,设计了中央控制器类:DispatcherServlet
DispatcherServlet这个类的工作分为两大部分:
1.根据url定位到能够处理这个请求的controller组件:
1)从url中提取servletPath : /fruit.do -> fruit
2)根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中
通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller组件<bean id="fruit" class="com.atguigu.fruit.controllers.FruitController/>
3)根据获取到的operate的值定位到我们FruitController中需要调用的方法
2.调用Controller组件中的方法:- 获取参数
获取即将要调用的方法的参数签名信息: Parameter[] parameters = method.getParameters();
通过parameter.getName()获取参数的名称;
准备了Object[] parameterValues 这个数组用来存放对应参数的参数值
另外,我们需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型 - 执行方法
Object returnObj = method.invoke(controllerBean , parameterValues); - 视图处理
String returnStr = (String)returnObj;
if(returnStr.startWith(“redirect:”)){
…
}else if…
- 获取参数
二、多个Servlet合并成一个Servlet
现在项目结构
最初的做法是: 一个请求对应一个Servlet,这样存在的问题是servlet太多了
期望结构
把一些列的请求都对应一个Servlet, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet -> 合并成FruitServlet
通过一个operate的值来决定调用FruitServlet中的哪一个方法
使用的是switch-case
@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置编码
request.setCharacterEncoding("UTF-8");
//2.获取操作
String operate = request.getParameter("operate");
System.out.println("获取到的操作为---->"+operate);
if (StringUtil.isEmpty(operate)){
operate = "index";
}
switch (operate){
case "index":
index(request,response);
break;
case "add":
add(request,response);
break;
case "del":
del(request,response);
break;
case "edit":
edit(request,response);
break;
case "update":
update(request,response);
break;
}
}
private void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置编码
request.setCharacterEncoding("utf-8");
//2.获取参数
String fidStr = request.getParameter("fid");
Integer fid = Integer.parseInt(fidStr);
String fname = request.getParameter("fname");
String priceStr = request.getParameter("price");
int price = Integer.parseInt(priceStr);
String fcountStr = request.getParameter("fcount");
Integer fcount = Integer.parseInt(fcountStr);
String remark = request.getParameter("remark");
//3.执行更新
fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark ));
//4.资源跳转
//super.processTemplate("index",request,response);
//request.getRequestDispatcher("index.html").forward(request,response);
//此处需要重定向,目的是重新给IndexServlet发请求,重新获取furitList,然后覆盖到session中,这样index.html页面上显示的session中的数据才是最新的
response.sendRedirect("fruit.do");
}
public void edit(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
Fruit fruit = fruitDAO.getFruitByFid(fid);
request.setAttribute("fruit",fruit);
super.processTemplate("edit",request,response);
}
}
private void del(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
fruitDAO.delFruit(fid);
//super.processTemplate("index",request,response);
response.sendRedirect("fruit.do");
}
}
private void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String fname = request.getParameter("fname");
Integer price = Integer.parseInt(request.getParameter("price")) ;
Integer fcount = Integer.parseInt(request.getParameter("fcount"));
String remark = request.getParameter("remark");
Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ;
fruitDAO.addFruit(fruit);
response.sendRedirect("fruit.do");
}
private void index(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
HttpSession session = request.getSession() ;
Integer pageNo = 1 ;
String oper = request.getParameter("oper");
//如果oper!=null 说明 通过表单的查询按钮点击过来的
//如果oper是空的,说明 不是通过表单的查询按钮点击过来的
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);
}
}
三、通过反射技术直接调用方法
通过反射技术获取所有方法名,根据operate匹配相应的方法,直接调用
//获取当前类中所有的方法
Method[] methods = this.getClass().getDeclaredMethods();
for (Method m : methods) {
//获取方法名称
String methodName = m.getName();
if (methodName.equals(operate)){
try {
m.invoke(this,request,response);
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}throw new RuntimeException("operate值非法");
}
四、DispatcherServlet根据URL定位Controller
1.新建application
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="fruit" class="com.atguigu.fruit.servlets.FruitController"></bean>
</beans>
2.新建中央处理器
- 解析配置文件
- 根据路径匹配对应的Controller
- 根据operate调用Controller相应的方法
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet{
//beanMap 存放 id 和 class的关系
private Map<String,Object> beanMap = new HashMap<>();
//Servlet生命周期:实例化、初始化、服务、销毁
public DispatcherServlet() {
try {
//解析配置文件,创建文件流,读取文件信息
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("application.xml");
//1.创建factory
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//2.创建documentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//3.获取document对象
Document document = documentBuilder.parse(inputStream);
//4.获取所有的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;
String beanId = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
Object beanObj = Class.forName(className).newInstance();
beanMap.put(beanId,beanObj);
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置编码
request.setCharacterEncoding("UTF-8");
//2.想通过.do前面的字段来匹配Controller
String servletPath = request.getServletPath();
//servletPath = /index.do -> index -> IndexController
System.out.println("servletPath = " + servletPath);
//3.解析path,得到index
servletPath = servletPath.substring(1,servletPath.length() - 3);
//4.index对应到IndexController
Object controllerBeanObj = beanMap.get(servletPath);
//5.根据operate调用相应方法
String operate = request.getParameter("operate");
System.out.println("获取到的操作为---->"+operate);
if (StringUtil.isEmpty(operate)){
operate = "index";
}
//获取当前类中所有的方法
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
for (Method m : methods) {
//获取方法名称
String methodName = m.getName();
if (methodName.equals(operate)){
try {
m.invoke(controllerBeanObj,request,response);
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}throw new RuntimeException("operate值非法");
}
}
3.视图处理
- 通过每个方法返回的字符串决定重定向还是调用thymeleaf
DispatcherServlet
//获取当前类中所有的方法
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
for (Method m : methods) {
//获取方法名称
String methodName = m.getName();
if (methodName.equals(operate)){
try {
//方法调用
m.setAccessible(true);
String methodReturnStr = (String) m.invoke(controllerBeanObj,request,response);
//视图处理
if (methodReturnStr.startsWith("redirect:")){
String redirectStr = methodReturnStr.substring("redirect:".length());
response.sendRedirect(redirectStr);
}else {
super.processTemplate(methodReturnStr,request,response);
}
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}throw new RuntimeException("operate值非法");
}
Controller
public String edit(HttpServletRequest request){
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
Fruit fruit = fruitDAO.getFruitByFid(fid);
request.setAttribute("fruit",fruit);
// super.processTemplate("edit",request,response);
return "edit";
}
return null;
}
private String del(HttpServletRequest request){
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
fruitDAO.delFruit(fid);
//super.processTemplate("index",request,response);
// response.sendRedirect("fruit.do");
return "redirect:fruit.do";
}
return null;
}
四、统一获取参数
try {
//1.获取参数
//获取当前方法的参数,返回参数数组
Parameter[] parameters = m.getParameters();
//数组存放value的值
Object[] parameterValues = new Object[parameters.length];
for (int i = 0; i < parameterValues.length; i++) {
Parameter parameter = parameters[i];
String parameterName = parameter.getName();
//参数名特殊处理
if ("request".equals(parameterName)){
parameterValues[i] = request;
}else if ("response".equals(parameterName)){
parameterValues[i] = response;
}else if ("session".equals(parameterName)){
parameterValues[i] = request.getSession();
}else {
//从请求中获取参数
String parameterValue = request.getParameter(parameterName);
String typeName = parameter.getType().getName();
if ("java.lang.Integer".equals(typeName)){
parameterValues[i] = Integer.parseInt(parameterValue);
}
parameterValues[i] = parameterValue;
}
}