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

前置知识

前置准备

知识准备

已掌握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 

资源准备

尚硅谷丨2022版JavaWeb教程视频

教学资源

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

提取码:yyds

servlet优化的过程2

当Fruitservlet中的方法过多,会导致switch case判断语句过长

使用反射优化switch-case

优化如下:

@WebServlet("/fruit.do")
public class FruitServlet extends ViewBaseServlet {
    private FruitDAO fruitDAO = new FruitDAOImpl();
​
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
​
        String operate = req.getParameter("operate");
        if(StringUtil.isEmpty(operate)){
            operate = "index";
        }
        //获取当前类中所有的方法
        Method[] methods = this.getClass().getDeclaredMethods();
        for (Method m:
                methods) {
            //获取方法名称
            String methodName = m.getName();
            if(operate.equals(methodName)){
                //表示找到和operate同名的方法,那么通过反射技术调用它
                m.invoke(this,req,resp);
            }
        }

这里顺便复习一下Java的反射机制,是其成为动态语言的关键。

这里的FruitServlet类加载完后,我们可以对其进行以下操作:

Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类

  • 在运行时构造任意一个类的对象

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时获取泛型信息

  • 在运行时调用任意一个对象的成员变量和方法

  • 在运行时处理注解

  • 生成动态代理

这里我们要调用它的方法

用到了getDeclaredMethods()方法来获取了FruitServlet类中的所有方法

获取之后使用invoke方法来调用

传入的参数是this:当前类的对象,FruitServlet中的每一个方法刚好对应相同的参数(request,respond)

注意:这里获取方法是getDeclaredMethods(),才能获取到private,因为在是在本类中调用

引入DispatcherServlet

所有的请求交给DispatcherServlet,根据请求的不同定位不同的servlet,再定位到不同的方法。

FruitServlet改名为FruitController(控制器)

增加DispatcherServlet类

@WebServlet("*.do")

①拦截所有以.do结尾的请求

②假设URL是http://localhost:8080/pro15/hello.do

通过req.getServletPath();获取请求中的“/hello.do”

③字符串截取到“hello”,发送给HelloController(类似之前的add.html页面发送add给FruitServlet)

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

④hello和helloController对应上

将servlet文件夹改名为controllers

去掉@WebServlet("/fruit.do"),让FruitController变为一个普通的类

创建hello和helloController对应说明文件,在src下创建applicationContext.xml

这里介绍到xml的概念

xml介绍

概念

HTML : 超文本标记语言 XML : 可扩展的标记语言 HTML是XML的一个子集

体会xml的可扩展

XML包含三个部分:

  1. XML声明 , 必须要有,而且声明这一行代码必须在XML文件的第一行

  2. DTD 文档类型定义(可有可无)

  3. XML正文

<?xml version="1.0" encoding="utf-8" ?>
​
<beans>
    <!-- 这个bean标签的作用是将来servletpath中涉及的名字对应的fruit,那么就要FruitController这个类来处理-->
    <bean id="fruit" class="com.fancy.fruit.controllers.FruitController"></bean>>
</beans>

xml文件配置好后,在DispatherServlet类的构造器中添加代码,让该类在实例化阶段的构造器中解析刚才配置的xml配置文件。

@WebServlet("*.do")
public class DispatherServlet extends ViewBaseServlet{
    //servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
    public DispatherServlet(){
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applictationContext.xml");
​
    }

这里复习一下getClassLoader().getResourceAsStream("applictationContext.xml")这两个方法的相关知识。

复习类的加载与ClassLoader

类的加载过程

加载
  1. .class文件加载到内存中

  2. 将静态数据转换成方法区的运行时数据

  3. 生成java.lang.Class对象(作为方法区中类数据的访问入口,即引用地址)

链接

验证-准备-解析

初始化
  • 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。

  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类 的初始化。

  • 虚拟机会保证一个类的<clinit>(()方法在多线程环境中被正确加锁和同步。

什么时候发生类初始化

ClassLoader

类加载器作用是用来把类(class)装载进内存的。

getResourceAsStream方法

getResourceAsStream(String str):获取类路 径下的指定文件的输入流

所以这里我们获取到了applictationContext.xml的输入流

接下来创建Document对象,获取输入流中的bean标签。

public class DispatherServlet extends ViewBaseServlet{
    //servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
    public DispatherServlet(){
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applictationContext.xml");
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            //创建document对象
            Document document = documentBuilder.parse(inputStream);
​
            NodeList beanNodeList = document.getElementsByTagName("bean");
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
​
    }

通过IO流读取文件内容,再通过API获取文本中你设置好的节点的名字和属性

获取全类名后,要获取它的实例对象,这样就获得了beanId、beanObj。

最后将所有的bean和其对应的类实例对象绑定在一起,封装成map(差不多spring里的ioc)

public class DispatherServlet extends ViewBaseServlet{
    private Map<String,Object> beanMap = new HashMap<>();
    //servlet有加载-实例化-服务-销毁的生命周期,所以先在实例化阶段的构造器中解析xml配置文件
    public DispatherServlet(){
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("applictationContext.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");
                    //获取全类名后,要获取它的实例对象
                    Object beanObj = Class.forName(className).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();
        }
​
    }

⑤得到了XXcontroller对象,成功调用了XX方法

回到下方的service方法,fruit现在是controller,传给前端控制器fruit前端控制器就会去实例化fruitcontroller。就像传hello会调用hellocontroller。(servletPath是“hello”)

接下来我们要调用该对象里的方法来进行我们请求的操作add、update、delete...

剪切service方法体,注意这里是剪切的pro14的

DispatcherServlet类
@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";
    }
    //获取当前类中所有的方法
    Method[] methods = this.getClass().getDeclaredMethods();
    for (Method m:
            methods) {
        //获取方法名称
        String methodName = m.getName();
        if(operate.equals(methodName)){
            //表示找到和operate同名的方法,那么通过反射技术调用它
            try {
                m.invoke(this,req,resp);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
    throw new RuntimeException("operate值非法!");
}

改方法体(通过反射获得所有该类的方法然后循环匹配调用该方法)

不用循环,改动

    @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,HttpServletResponse.class);
            if (method != null){
                method.invoke(this,req,resp);
​
            }else {
                throw new RuntimeException("operate值非法!");
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

总结

①拦截所有以.do结尾的请求

②假设URL是http://localhost:8080/pro15/hello.do

③字符串截取到“hello”,发送给HelloController(类似之前的add.html页面发送add给FruitServlet)

④hello和helloController对应上(fruit和fruitController对应上)

⑤hello和helloController就对应上了,得到了XXcontroller对象,成功调用了XX方法

以上,通过读取xml配置文件,获取对应的beanId和className,再用反射加载className创建类的实例,放到map中,通过前端请求路径,获得对应的key,在DispatherServlet组件中通关反射获取对应XXController类,在对应类中通过反射获取对应类的方法,结合下图理解。

改动一下DispatherServlet集成的父类,改为HttpServlet

posted @ 2022-08-10 00:23  Fancy[love]  阅读(91)  评论(0编辑  收藏  举报