【JavaWeb】简单实现DispatcherServlet

本文详细介绍了DispatcherServlet如何解决过多servlet导致的问题,通过条件判断和反射优化,最终阐述了DispatcherServlet作为中央控制器的主要功能,包括从URL中提取servletPath、找到对应组件并调用相应方法。并通过XML配置实现简单IOC容器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、DispatcherServlet解决的问题

在这里插入图片描述**问题1:**如图所示,如果对于某一业务,增删改查如果都创建一个servlet,那么业务越多,对应的servlet就越多。以上述业务中增加和删除业务为例:

@WebServlet("/add.do")
public class AddServlet extends ViewBaseServlet {

    private FruitDAO fruitDAO = new FruitDAOImpl();

    @Override
    protected void doPost(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("index");

    }
}
@WebServlet("/del.do")
public class DelServlet extends ViewBaseServlet {
    private FruitDAO fruitDAO = new FruitDAOImpl();
    @Override
    public void doGet(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("index");
        }
    }
}

解决方案:两个servlet重写了doGet方法,在doGet中进行相关操作,我们可以看到,两个servlet的url是不一样的,即@WebServlet(“/add.do”)和@WebServlet(“/del.do”)。那么可以根据路径参数不同,设置一个operate参数,这个参数由前台传入,

<input type="hidden" name="operate" value="add"/>

发送给服务器,服务器用来判断(条件判断)进入增删改查哪个方法。

 @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("UTF-8");

        String operate = request.getParameter("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;
            default:
                throw new RuntimeException("operate值非法!");
        }

    }

**存在问题:**但是使用条件判断也存在问题,随着业务增加,条件判断会越来越多,这也不符合修改封闭原则。
解决方案:我们可以规定方法名称和operate参数相同,这样就可以用反射的方法来根据operate进入不同的方法了,即使增加业务,也不用修改代码。

 @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("UTF-8");

        String operate = request.getParameter("operate");
        if(StringUtil.isEmpty(operate)){
            operate = "index" ;
        }
        try {
            Method method = this.getClass().getDeclaredMethod(operate, HttpServletRequest.class, HttpServletResponse.class);
            if (method != null) {
                method.invoke(this, request, response);
                return;
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("Invalid operate.");

    }

这样,即使增加新的访问方法,也不用修改这一块代码。
存在问题:但是这样还是存在问题的,如果系统还有一个user相关业务,那我们也要在user里面写一套相同逻辑的反射代码,来处理这一块。
解决方案:因此我们可以继续向上提取,设计了中央控制器类:DispatcherServlet。

二、DispatcherServlet主要功能

中央控制器需要完成功能
在这里插入图片描述 1)从url中提取servletPath : /fruit.do -> fruit
2)根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中
<bean id=“fruit” class="com.atguigu.fruit.controllers.FruitController/>
通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller组件
3)根据获取到的operate的值定位到我们FruitController中需要调用的方法

三、代码实现

package com.lucky.myssm.myspringmvc;

import com.lucky.myssm.io.BeanFactory;
import com.lucky.myssm.io.ClassPathXmlApplicationContext;
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.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
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.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet {

    private BeanFactory beanFactory ;

    // 生命周期 实例化 初始化 服务 销毁
    public DispatcherServlet() {

    }

    @Override
    public void init() throws ServletException {
        super.init();
        beanFactory = new ClassPathXmlApplicationContext();

    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置编码
        request.setCharacterEncoding("UTF-8");
        // 假设url http://localhost:8080/pro10/hello.do
        String servletPath = request.getServletPath();
        // /hello.do
        // 通过字符串截取获取 hello
        int lastDot = servletPath.lastIndexOf(".do");
        servletPath = servletPath.substring(1, lastDot);
        // 获取到了hello
        System.out.println(servletPath);
        // 根据service path 获取controller对象
        Object controller = beanFactory.getBean(servletPath);
        // 找到hello对应的controller

        // 调用controller里面对应operate的方法
        request.setCharacterEncoding("UTF-8");
        String operate = request.getParameter("operate");
        if (Objects.isNull(operate) || "".equals(operate)) {
            operate = "index";
        }
        try {
            // 获取名称为operate, 参数为request和response的方法
            Method[] methods = controller.getClass().getDeclaredMethods();
            for (Method method : methods) {
               if (operate.equals(method.getName())) {
                   // 获取请求参数
                   Parameter[] parameters = method.getParameters();
                   Object[] paramValues = new Object[parameters.length];
                   for (int i = 0; i < paramValues.length; i++) {
                       // 获取参数名称
                       Parameter param = parameters[i];
                       String paramName = param.getName();
                       if ("request".equals(paramName)) {
                           paramValues[i] = request;
                       } else if ("reponse".equals(paramName)) {
                           paramValues[i] = response;
                       } else if ("session".equals(paramName)) {
                           paramValues[i] = request.getSession();
                       }
                       else {
                           // 跟据name去request获取参数值
                           String paramValue = request.getParameter(paramName);
                           // 如果参数不是string,那么就需要强转
                           if (paramValue != null) {
                               if ("java.lang.Integer".equals(param.getType().getName())) {
                                   paramValues[i] = Integer.parseInt(paramValue);
                               }
                               else {
                                   paramValues[i] = paramValue; // 这个直接获取的是string
                               }
                           } else {
                               paramValues[i] = null;
                           }

                       }
                   }

                   method.setAccessible(true);
                   Object retVal = method.invoke(controller, paramValues);
                   // 视图处理
                   if (Objects.nonNull(retVal)) {
                       String retValStr = (String)retVal;
                       if (retValStr.startsWith("redirect:")) {
                           // 把redirect:后面拿过来
                           String redirectPath = retValStr.substring("redirect:".length());
                           response.sendRedirect(redirectPath);
                       } else {
                           super.processTemplate(retValStr, request, response);
                       }
                   }
//                   else {
//                       throw new RuntimeException("Invalid operate!");
//                   }
               }
            }
        } catch (IllegalAccessException|InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

获取Controler以及Controler中相关属性装配,才用application.xml配置,相当于简单实现一个IOC容器。
xml文件配置

<?xml version="1.0" encoding="utf-8"?>

<beans>
    <!-- 这个bean标签的作用是 将来servletpath中涉及的名字对应的是fruit(前面的id,这就和dispatch对应起来了),
    那么就要FruitController这个类来处理 -->
    <bean id="fruitDAO" class="com.lucky.fruit.dao.impl.FruitDAOImpl"/>
    <bean id="fruitService" class="com.lucky.fruit.service.impl.FruitServiceImpl">
        <property name="fruitDAO" ref="fruitDAO"/>
    </bean>
    <bean id="fruit" class="com.lucky.fruit.controllers.FruitController">
        <property name="fruitService" ref="fruitService"/>
    </bean>
</beans>

获取controller以及设置controller属性。

package com.lucky.myssm.io;

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.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.Field;
import java.util.HashMap;
import java.util.Map;

public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String,Object> beanMap = new HashMap<>();

    public ClassPathXmlApplicationContext(){
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applicationContext.xml");
            //1.创建DocumentBuilderFactory
            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");
                    Class beanClass = Class.forName(className);
                    //创建bean实例
                    Object beanObj = beanClass.newInstance() ;
                    //将bean实例对象保存到map容器中
                    beanMap.put(beanId , beanObj) ;
                    //到目前为止,此处需要注意的是,bean和bean之间的依赖关系还没有设置
                }
            }
            //5.组装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");
                    NodeList beanChildNodeList = beanElement.getChildNodes();
                    for (int j = 0; j < beanChildNodeList.getLength() ; j++) {
                        Node beanChildNode = beanChildNodeList.item(j);
                        if(beanChildNode.getNodeType()==Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())){
                            Element propertyElement = (Element) beanChildNode;
                            String propertyName = propertyElement.getAttribute("name");
                            String propertyRef = propertyElement.getAttribute("ref");
                            //1) 找到propertyRef对应的实例
                            Object refObj = beanMap.get(propertyRef);
                            //2) 将refObj设置到当前bean对应的实例的property属性上去
                            Object beanObj = beanMap.get(beanId);
                            Class beanClazz = beanObj.getClass();
                            Field propertyField = beanClazz.getDeclaredField(propertyName);
                            propertyField.setAccessible(true);
                            propertyField.set(beanObj,refObj);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值