一、Tomcat

1.1 介绍

Tomcat是一个web容器项目可以部署到里面。

通过context root来定位某个项目。比如http://localhost:8080/baidu/demo09.html ip地址+端口号定位某一台服务器的tomcat,然后通过root定位资源。

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目

1.2 IDEA(2021)里完成部署

  1. 选java Enterprise项目
  2. 新建与WEB-INF同级的HTML
  3. 完成部署前先进行配置tomcat,选tomcat-local
  4. 完成部署

完全可以改的方便些

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_02

尚硅谷Java实战项目 尚硅谷javaweb_xml_03

尚硅谷Java实战项目 尚硅谷javaweb_java_04

最终结果:

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_05

1.3 IDEA特殊

idea把项目部署到这个位置,然后在tomacat里面指明位置,所以tomcat的webapps里面看不到

尚硅谷Java实战项目 尚硅谷javaweb_java_06

1.4 动态web工程目录结构介绍

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_07

二、Servlet(服务器端应用程序)

2.1 Servlet在整个web应用中起到的作用

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_08


尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_09

在整个Web应用中,Servlet主要负责处理请求、协调调度功能。我们可以把Servlet称为Web应用中的控制器

2.2 第一次使用servlet

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_10

服务器端把请求封装成request

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="add" method="post">
        名称:<input type="text" name="fname"/><br/>
        价格:<input type="text" name="price"/><br/>
        库存:<input type="text" name="fcount"/><br/>
        备注:<input type="text" name="remark"/><br/>
        <input type="submit" value="添加" />
    </form>
</body>
</html>

编写AddServlet,响应post请求

public class AddServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String fname = request.getParameter("trfname");
        String priceStr = request.getParameter("price");
        Integer price = Integer.parseInt(priceStr);
        String fcountStr = request.getParameter("fcount");
        Integer fcount = Integer.parseInt(fcountStr);
        String remark = request.getParameter("remark");
        //soutv
        System.out.println("fname = " + fname);
    }
}

对于第一个图的疑问:add是怎么能对应上AddServlet?
我们需要设置。在web.xml中进行配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <!--第一行是随便起的名字,第二行对应的是AddServlet类的全类名-->
        <servlet-name>AddServlet</servlet-name>
        <servlet-class>com.example.servlets.AddServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <!--对应的映射-->
        <servlet-name>AddServlet</servlet-name>
        <url-pattern>/add</url-pattern>
    </servlet-mapping>
</web-app>

执行顺序

1. 用户发请求,action=add
2. 项目中,web.xml中找到url-pattern = /add   
3. 找第11行的servlet-name = AddServlet
4. 找和servlet-mapping中servlet-name一致的servlet 
5. 找servlet-class -> com.atguigu.servlets.AddServlet
6. 用户发送的是post请求(method=post) , 因此 tomcat会执行AddServlet中的doPost方法

运行成功后是这样的,如果出现404则是找不到对应的资源

尚硅谷Java实战项目 尚硅谷javaweb_java_11

用开发者模式观察提交表单后

尚硅谷Java实战项目 尚硅谷javaweb_xml_12

填入信息后即可在idea中看到结果。

乱码在doPost方法里加上 req.setCharacterEncoding(“UTF-8”);

注意:设置编码这一行代码必须放在所有的获取参数动作之前,也就是第一行。并且,tomcat8之后,get方法不需要设置编码,只有post需要。

尚硅谷Java实战项目 尚硅谷javaweb_java_13


尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_14

2.3 连接数据库

效果展示:

尚硅谷Java实战项目 尚硅谷javaweb_xml_15

资料:下载尚硅谷的文件即可,建议先自学jdbc后再应用此部分。

实现过程中的问题:

  1. 我采用的是maven来进行mysql jar包的安装,没有这个绝对跑不起来。
  2. 因为版本是6以后的,所以我们的driver要变一变(加个cj)

2.4 Servlet的继承关系

1、提出问题
为什么IDEA创建Servlet之后不再实现Servlet接口而是继承HttpServlet类?
Servlet接口和HttpServlet类有什么关系?
doXxx()方法和service()方法有什么关系?

2、类型与方法关系

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_16

Servlet中service方法,当客户端发送请求,此方法就会被自动调用(其实是tomcat容器调用的)。具体做的什么事情可以到GenericServlet实现类里的service。

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_17


发现仍然是抽象类。最后去看看他的抽象子类(HttpServlet)

public class HelloServlet extends HttpServlet {
	// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。
	public HelloServlet() {
    }
    
    //override 重写 doGet方法
    //override 重写 doPost方法
}

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {
           
	// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
	// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
	public void init() throws ServletException {
        // NOOP by default
    }
}

// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
     用户发送第一次请求的时候这个service会执行
     用户发送第N次请求的时候,这个service方法还是会执行。
     用户只要发送一次请求,这个service方法就会执行一次。
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            // 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        // 调用重载的service方法。
        service(request, response);
    }
    
      这个service方法的两个参数都是带有Http的。
      这个service是一个模板方法。
      在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        // 获取请求方式
        // 这个请求方式最终可能是:""
        // 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
        // GET POST PUT DELETE HEAD OPTIONS TRACE
        String method = req.getMethod();

        // 如果请求方式是GET请求,则执行doGet方法。
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            // 如果请求方式是POST请求,则执行doPost方法。
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
    
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException{
        // 报405错误
        String msg = lStrings.getString("http.method_get_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        // 报405错误
        String msg = lStrings.getString("http.method_post_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
    
}

再HttpServlet中这些do方法默认都是405的实现风格-要我们的子类去实现对应的方法,否则默认会报405的错误。因此我们在新建Servlet,要考虑请求方法,从而决定重写哪个do方法

2.5 Servlet的生命周期

//演示Servlet的生命周期
public class Demo02Servlet extends HttpServlet {

    public Demo02Servlet(){
        System.out.println("正在实例化....");
    }

    @Override
    public void init() throws ServletException {
        System.out.println("正在初始化.....");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("正在服务.....");
    }

    @Override
    public void destroy() {
        System.out.println("正在销毁......");
    }
}

尚硅谷Java实战项目 尚硅谷javaweb_xml_18


尚硅谷Java实战项目 尚硅谷javaweb_java_19

1) 生命周期:从出生到死亡的过程就是生命周期。对应Servlet中的三个方法:init(),service(),destroy()
2) 默认情况下:
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
从第二次请求开始,每一次都是服务
当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法
3) 通过案例我们发现:
- Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。
- 默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务.这样的好处是什么? 提高系统的启动速度 。 这样的缺点是什么? 第一次请求时,耗时较长。
- 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
4) Servlet的初始化时机:

分为两种情况:

  1. 默认情况下第一次请求servlet对象时实例化,初始化;
  2. 可以通过load-on-startup 节点( load-on-startup属性值只能为整数 ) 来指定Servlet启动顺序,:
    (1)当节点值为负数 (相当于不写时) , 表示当用户第一次请求时, 创建此Servlet !
    (2)当节点值大于0或等于0时, 在服务器启动时, 创建此Servlet !
    启动的顺序是: 值越小越早创建,0时最早创建 !
    值相同时, 自上而下顺序创建 !
<servlet>
        <servlet-name>Demo02Servlet</servlet-name>
        <servlet-class>com.example.servlets.Demo02Servlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

尚硅谷Java实战项目 尚硅谷javaweb_java_20

5) Servlet在容器中是:单例的、线程不安全的
- 单例:所有的请求都是同一个实例去响应
- 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化
- 我们已经知道了servlet是线程不安全的,给我们的启发是: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么不要去:①不要去修改成员变量的值 ②不要去根据成员变量的值做一些逻辑判断

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_21

三、 HTTP协议

1) Http 称之为 超文本传输协议
2) Http是无状态的
3) Http请求响应包含两个部分:请求和响应
1. 请求
请求包含三个部分: 1.请求行 ; 2.请求消息头 ; 3.请求主体
    ![在这里插入图片描述]()

1)请求行包含是三个信息: 1. 请求的方式 ; 2.请求的URL ; 3.请求的协议(一般都是HTTP1.1)

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_22

2)请求消息头中包含了很多客户端需要告诉服务器的信息,比如:我的浏览器型号、版本、我能接收的内容的类型、我给你发的内容的类型、内容的长度等等

尚硅谷Java实战项目 尚硅谷javaweb_java_23

3)请求体,三种情况
get方式,没有请求体,但是有一个queryString
post方式,有请求体,form data
json格式,有请求体,request payload

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_24

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_25

尚硅谷Java实战项目 尚硅谷javaweb_xml_26

2. 响应
响应也包含三个部分: 1. 响应行 ; 2.响应头 ; 3.响应体
    ![在这里插入图片描述]()

1)响应行包含三个信息:1.协议 2.响应状态码(200) 3.响应状态(ok)

2)响应头:包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_27

3)响应体:响应的实际内容(比如请求add.html页面时,响应的内容就是<form…)

4)响应状态码:

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_28

四、会话(Session)

1) Http是无状态的

- HTTP 无状态 :服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的

- 无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱

- 通过会话跟踪技术来解决无状态的问题。

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_29

2) 会话跟踪技术
- 客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端
- 下次客户端给服务器发请求时,会把sessionID带给服务器,那么服务器就能获取到了,那么服务器就判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端
- 常用的API:
request.getSession() -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true) -> 效果和不带参数相同(即和上面一样)
request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的

session.getId() -> 获取sessionID
session.isNew() -> 判断当前session是否是新的
session.getMaxInactiveInterval() -> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval() -> 设置时长的
session.invalidate() -> 强制性让会话立即失效

尚硅谷Java实战项目 尚硅谷javaweb_java_30


尚硅谷Java实战项目 尚硅谷javaweb_xml_31


再次发起请求

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_32


尚硅谷Java实战项目 尚硅谷javaweb_xml_33

3) session保存作用域–一次会话范围都有效

- session保存作用域是和具体的某一个session对应的

- 常用的API:

void session.setAttribute(k,v)

Object session.getAttribute(k)

void removeAttribute(k)

尚硅谷Java实战项目 尚硅谷javaweb_java_34


代码测试(用断点形式来测试)

//演示向HttpSession保存数据
public class Demo04Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.getSession().setAttribute("uname","lina");
    }
}
public class Demo05Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Object unameObj = req.getSession().getAttribute("uname");
        System.out.println(unameObj);
    }
}

第一个测试:先访问demo04在访问demo05

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_35

第二个测试:直接访问demo05

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_36

五、 服务器内部转发以及客户端重定向

1) 服务器内部转发 : request.getRequestDispatcher(“…”).forward(request,response);

- 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的

- 地址栏没有变化

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_37

2) 客户端重定向: response.sendRedirect(“…”);

- 两次请求响应的过程。客户端肯定知道请求URL有变化

- 地址栏有变化

尚硅谷Java实战项目 尚硅谷javaweb_java_38

测试:

//演示服务器端内部转发以及客户端重定向
public class Demo06Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo06......");
        //服务器端内部转发
        //request.getRequestDispatcher("Demo07").forward(request,response);
        //客户端重定向
        response.sendRedirect("Demo07");
    }
}
public class Demo07Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("demo07......");
    }
}

最终控制台结果是一样的,但是用户摁下回车后可以看到网址的变化。

尚硅谷Java实战项目 尚硅谷javaweb_java_39


内部转发:

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_40

重定向:

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_41


尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_42

六、Thymeleaf - 视图模板技术

让网页中显示的数据是从数据库中查出来的

大致流程:

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_43

基础准备:

连接数据库等等操作。

尚硅谷Java实战项目 尚硅谷javaweb_xml_44

1) 添加thymeleaf的jar包
2) 新建一个Servlet类ViewBaseServlet(因为里面有两个方法,其中第二个方法是读取前后缀,然后渲染页面)

package com.example.myssm.myspringmvc;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ViewBaseServlet extends HttpServlet {

    private TemplateEngine templateEngine;

    @Override
    public void init() throws ServletException {
        // 1.获取ServletContext对象
        ServletContext servletContext = this.getServletContext();
        // 2.创建Thymeleaf解析器对象
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
        // 3.给解析器对象设置参数
        // ①HTML是默认模式,明确设置是为了代码更容易理解
        templateResolver.setTemplateMode(TemplateMode.HTML);
        // ②设置前缀
        String viewPrefix = servletContext.getInitParameter("view-prefix");
        templateResolver.setPrefix(viewPrefix);
        // ③设置后缀
        String viewSuffix = servletContext.getInitParameter("view-suffix");
        templateResolver.setSuffix(viewSuffix);
        // ④设置缓存过期时间(毫秒)
        templateResolver.setCacheTTLMs(60000L);
        // ⑤设置是否缓存
        templateResolver.setCacheable(true);
        // ⑥设置服务器端编码方式
        templateResolver.setCharacterEncoding("utf-8");
        // 4.创建模板引擎对象
        templateEngine = new TemplateEngine();
        // 5.给模板引擎对象设置模板解析器
        templateEngine.setTemplateResolver(templateResolver);
    }

    protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 1.设置响应体内容类型和字符集
        resp.setContentType("text/html;charset=UTF-8");
        // 2.创建WebContext对象
        WebContext webContext = new WebContext(req, resp, getServletContext());
        // 3.处理模板数据
        templateEngine.process(templateName, webContext, resp.getWriter());
    }
}

3) 在web.xml文件中添加配置
- 配置前缀 view-prefix
- 配置后缀 view-suffix

4) 使得我们的Servlet继承ViewBaseServlet,为了让子类使用processTemplate方法

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

<!-- 配置上下文参数 -->
    <context-param>
        <param-name>view-prefix</param-name>
        <param-value>/</param-value>
    </context-param>
    <context-param>
        <param-name>view-suffix</param-name>
        <param-value>.html</param-value>
    </context-param>
//Servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
    @Override
    public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
        FruitDAO fruitDAO = new FruitDAOImpl();
        List<Fruit> fruitList = fruitDAO.getFruitList();
        //保存到session作用域
        HttpSession session = request.getSession();
        session.setAttribute("fruitList",fruitList);
        //此处的视图名称是 index
        //那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
        //逻辑视图名称 :   index
        //物理视图名称 :   view-prefix + 逻辑视图名称 + view-suffix
        //所以真实的视图名称是:      /       index       .html
        super.processTemplate("index",request,response);
    }
}

在保存session处下断点查看

尚硅谷Java实战项目 尚硅谷javaweb_xml_45


尚硅谷Java实战项目 尚硅谷javaweb_java_46

6) 使用thymeleaf的标签
th:if , th:unless , th:each , th:text

在index.html文件里添加thymeleaf()

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="css/index.css">
</head>
<body>
<div id="div_container">
    <div id="div_fruit_list">
        <p class="center f30">欢迎使用水果库存后台管理系统</p>
        <table id="tbl_fruit">
            <tr>
                <th class="w20">名称</th>
                <th class="w20">单价</th>
                <th class="w20">库存</th>
                <th>操作</th>
            </tr>
            <tr th:if="${#lists.isEmpty(session.fruitList)}">
                <td colspan="4">对不起,库存为空!</td>
            </tr>
            <tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit : ${session.fruitList}">
                <td th:text="${fruit.fname}">苹果</td>
                <td th:text="${fruit.price}">5</td>
                <td th:text="${fruit.fcount}">20</td>
                <td><img src="imgs/del.jpg" class="delImg"/></td>
            </tr>
        </table>
    </div>
</div>
</body>
</html>

尚硅谷Java实战项目 尚硅谷javaweb_xml_47


尚硅谷Java实战项目 尚硅谷javaweb_tomcat_48

七、Servlet保存作用域与路径问题

原始情况下,保存作用域我们可以认为有四个: page(页面级别,现在几乎不用) , request(一次请求响应范围) , session(一次会话范围) , application(整个应用程序范围)
1) request:一次请求响应范围
2) session:一次会话范围有效
3) application: 一次应用程序范围有效

7.1 Request保存作用域

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_49

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_50

测试:

//演示request保存作用域(demo01和demo02)
@WebServlet("/demo01")
public class Demo01Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.向request保存作用域保存数据
        request.setAttribute("uname","lili");
        //2.客户端重定向
        response.sendRedirect("demo02");
        //3.服务器端转发
        //request.getRequestDispatcher("demo02").forward(request,response);
    }
}
@WebServlet("/demo02")
public class Demo02Servlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取request保存作用域里面保存的数据,key为uname
        Object uname = req.getAttribute("uname");
        System.out.println("uname = " + uname);
    }
}

重定向后输入demo01,刷新后是demo02

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_51


尚硅谷Java实战项目 尚硅谷javaweb_tomcat_52


服务器端转发方式

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_53

7.2 session保存作用域

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_54

无论是跳转还是服务器端转发都可以,因为是一次会话有效。代码没啥差距,只需要在set和get改一下即可

request.getSession().setAttribute("uname","lili");

request.getSession().getAttribute("uname");

7.3 application保存作用域

尚硅谷Java实战项目 尚硅谷javaweb_java_55

代码也只需要在set这里改动一点即可测试

//ServletContext:Servlet上下文(张三和李四聊的很开心,王五去问,张三和李四就介绍上下文给他)
        // tomcat已运行表示这一次的应用程序就开始启动了
        ServletContext application = request.getServletContext();
        application.setAttribute("uname","lili");
        
		ServletContext application = req.getServletContext();

7.4 路径问题

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_56

八、实现库存系统的功能

前提:此实现网址都放在web目录下,而不是web-inf下,尽管后者更为安全常见。此项目只为简单实现。

8.1 编辑和修改特定库存信息

首先我们先将前端页面写好,并针对特定信息形成特定网址,这样才可以对特定信息进行修改

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_57


尚硅谷Java实战项目 尚硅谷javaweb_xml_58


初始的index页面之前就已经写好。而修改这部分是由 th:href=“@{/edit.do(fid=${fruit.fid})}” 来进行实现。@{}的作用是在字符串前附加『上下文路径』

<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="utf-8">
		<link rel="stylesheet" href="css/index.css">
        <script language="JavaScript" src="js/index.js"></script>
	</head>
	<body>
		<div id="div_container">
			<div id="div_fruit_list">
				<p class="center f30">欢迎使用水果库存后台管理系统</p>
				<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
					<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
				</div>
				<table id="tbl_fruit">
					<tr>
						<th class="w20">名称1</th>
						<th class="w20">单价</th>
						<th class="w20">库存</th>
						<th>操作</th>
					</tr>
					<tr th:if="${#lists.isEmpty(session.fruitList)}">
						<td colspan="4">对不起,库存为空!</td>
					</tr>
					<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit : ${session.fruitList}">
						<!-- <td><a th:text="${fruit.fname}" th:href="@{'/edit.do?fid='+${fruit.fid}}">苹果</a></td> -->
						<td><a th:text="${fruit.fname}" th:href="@{/edit.do(fid=${fruit.fid})}">苹果</a></td>
						<td th:text="${fruit.price}">5</td>
						<td th:text="${fruit.fcount}">20</td>
						<!-- <td><img src="imgs/del.jpg" class="delImg" th:οnclick="'delFruit('+${fruit.fid}+')'"/></td> -->
                        <td><img src="imgs/del.jpg" class="delImg" th:onclick="|delFruit(${fruit.fid})|"/></td>
					</tr>
				</table>
			</div>
		</div>
	</body>
</html>

此时后端要对请求作出响应(ViewBaseServlet这个基类直接复制粘贴就好,后面都会被框架代替)

@WebServlet("/edit.do")
public class EditServlet 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);
            Fruit fruit = fruitDAO.getFruitByFid(fid);
            request.setAttribute("fruit",fruit);
            //此处的视图名称是 index
        	//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
        	//逻辑视图名称 :   index
        	//物理视图名称 :   view-prefix + 逻辑视图名称 + view-suffix
        	//所以真实的视图名称是:      /       index       .html
            super.processTemplate("edit",request,response);
        }
    }
}

因为常常需要判断是否为空,我们直接写一个判断是否是空的工具类

public class StringUtil {
    //判断字符串是否为null或者""
    public static boolean isEmpty(String str){
        return str==null || "".equals(str);
    }

    public static boolean isNotEmpty(String str){
        return !isEmpty(str);
    }
}

查询数据库的方法要建立

public interface FruitDAO {
    //获取所有的库存列表信息
    List<Fruit> getFruitList();

    //根据fid获取特定的水果库存信息
    Fruit getFruitByFid(Integer fid);
}
public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO {
    @Override
    public List<Fruit> getFruitList() {
        return super.executeQuery("select * from t_fruit");
    }

    @Override
    public Fruit getFruitByFid(Integer fid) {
        return super.load("select * from t_fruit where fid = ? " , fid);
    }
}

写好edit页面,将用户填入的信息以表单的形式发给后端。

<html xmlns:th="http://www.thymeleaf.org">
<head>
	<meta charset="utf-8">
	<link rel="stylesheet" href="css/edit.css">
</head>
<body>
<div id="div_container">
	<div id="div_fruit_list">
		<p class="center f30">编辑库存信息3</p>
		<form th:action="@{/update.do}" method="post" th:object="${fruit}">
			<!-- 隐藏域 : 功能类似于文本框 , 它的值会随着表单的发送也会发送给服务器,但是界面上用户看不到 -->
			<input type="hidden" name="fid" th:value="*{fid}"/>
			<table id="tbl_fruit">
				<tr>
					<th class="w20">名称:</th>
					<!-- <td><input type="text" name="fname" th:value="${fruit.fname}"/></td> -->
					<td><input type="text" name="fname" th:value="*{fname}"/></td>
				</tr>
				<tr>
					<th class="w20">单价:</th>
					<td><input type="text" name="price" th:value="*{price}"/></td>
				</tr>
				<tr>
					<th class="w20">库存:</th>
					<td><input type="text" name="fcount" th:value="*{fcount}"/></td>
				</tr>
				<tr>
					<th class="w20">备注:</th>
					<td><input type="text" name="remark" th:value="*{remark}"/></td>
				</tr>
				<tr>
					<th colspan="2">
						<input type="submit" value="修改" />
					</th>
				</tr>
			</table>
		</form>
	</div>
</div>
</body>
</html>

后端将数据库进行更新,然后跳转到index页面

@WebServlet("/update.do")
public class UpdateServlet extends ViewBaseServlet {
    private FruitDAO fruitDAO = new FruitDAOImpl();
    @Override
    protected void doPost(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);刚刚更新完,这条语句执行的是从session里面展示数据,但session里的数据是更新之前的。所以这只在数据库里面更新
        //此处需要重定向,目的是重新给IndexServlet发请求,重新获取furitList,然后覆盖到session中,这样index.html页面上显示的session中的数据才是最新的
        response.sendRedirect("index");
        //注意:这两个index是两码事(super的和这个的)。前者是会自动添加前后缀,后者是发给后端indexServlet来进行重新查询的
    }
}

尚硅谷Java实战项目 尚硅谷javaweb_java_59

尚硅谷Java实战项目 尚硅谷javaweb_xml_60

8.2 删除和添加

首先在index页面与删除的图片进行js绑定

<td><img src="imgs/del.jpg" class="delImg" th:onclick="|delFruit(${fruit.fid})|"/></td>

新建js文件

function delFruit(fid){
    if (confirm('是否确认删除?')){
        window.location.href = 'del.do?fid='+fid;
    }
}

编写对应的deleteServlet

@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");
        }
    }
}

尚硅谷Java实战项目 尚硅谷javaweb_java_61


尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_62


尚硅谷Java实战项目 尚硅谷javaweb_java_63

添加也是类似,在index页面里面添加

<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
	<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
</div>

然后创建AddServlet。但是你就会发现出现自己不想要的结果。

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_64

执行完后,应该是跳转到index页面才对。并且添加的数据没进入数据库,页面也不会更新。

我们在index页面里,这行这么写是错误的。点完超链接,直接跳转到对应页面,而没有经过Thymeleaf渲染的。

因为是没有通过Servlet的,访问的只是一个静态的页面罢了,并没有执行super.processTemplate(),也就是Thymeleaf没有生效。

尚硅谷Java实战项目 尚硅谷javaweb_tomcat_65

<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
	<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
</div>

解决方法:缺啥补啥

我们只要让此请求的页面资源经过Thymeleaf渲染即可。那么就是让页面执行super.processTemplate()方法。最简单的方式就是在AddServlet里面添加doget方法,里面就写个super.processTemplate()方法。同时在index.html里面替换掉add.html变成add.do

<a th:href="@{/add.do}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
@WebServlet("/add.do")
public class AddServlet extends ViewBaseServlet {

    private FruitDAO fruitDAO = new FruitDAOImpl();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        super.processTemplate("add",request,response);
    }

    @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");

    }
}

这样的流程就变味:点击新增库存信息,先跳到AddServlet方法里,执行将add渲染,变成动态页面,而不知直接请求一个add静态页面。然后再执行将表单记录到数据库和页面跳转。

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_66


尚硅谷Java实战项目 尚硅谷javaweb_java_67

写在前面:关于idea中的tomcat配置的URL和Application Context联系

URL指的是服务器默认打开路径。
Application Context是web应用的路径。
部署项目时若制定了Application Context,按以下路径访问自己的项目即可。

所以Application Context一定是/fruit2。而url我们为了方便,又添加了/index,这样就能直接访问我们的indexServlet,去数据库查询,便可以看到我们水果的表。

尚硅谷Java实战项目 尚硅谷javaweb_java_68

尚硅谷Java实战项目 尚硅谷javaweb_java_69

提示:若没有特殊情况,我们并不需要主动在idea中配置tomcat的url,因为设置的Application Context会被自动填写至url中,且url会随着Application的改变自动改变。这样一来,看起来似乎url是服务器默认打开的url,Application Context是默认打开的项目。

8.3 分页功能

sql语句:

@Override
    public List<Fruit> getFruitList(Integer pageNo) {
        return super.executeQuery("select * from t_fruit limit ? , 5" , (pageNo-1)*5);
    }

在index.html上添加分页对应图标。同时上一页和下一页都应该拿到当前所在页

<div style="width:60%;margin-left:20%;">
	<input type="button" value="首  页" class="btn" th:onclick="|page(1)|" th:disabled="${session.pageNo==1}"/>
	<input type="button" value="上一页" class="btn" th:onclick="|page(${session.pageNo-1})|" th:disabled="${session.pageNo==1}"/>
	<input type="button" value="下一页" class="btn" th:onclick="|page(${session.pageNo+1})|" th:disabled="${session.pageNo==session.pageCount}"/>
	<input type="button" value="尾  页" class="btn" th:onclick="|page(${session.pageCount})|" th:disabled="${session.pageNo==session.pageCount}"/>
</div>

page的js方法写在index.js

function page(pageNo){
    window.location.href = "index?pageNo="+pageNo;
}

mysql

@Override
    public int getFruitCount() {
        return ((Long)super.executeComplexQuery("select count(*) from t_fruit")[0]).intValue();
    }

indexServlet

//Servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
    @Override
    public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
        //把pageNo保存到session作用域,这样在页面上就能获取到
        Integer pageNo = 1;
        String pageNoStr = request.getParameter("pageNo");
        if (StringUtil.isNotEmpty(pageNoStr)){
            pageNo = Integer.parseInt(pageNoStr);
        }
        HttpSession session = request.getSession();
        session.setAttribute("pageNo",pageNo);

        FruitDAO fruitDAO = new FruitDAOImpl();
        List<Fruit> fruitList = fruitDAO.getFruitList(pageNo);
        session.setAttribute("fruitList",fruitList);
        //总记录条数
        int fruitCount = fruitDAO.getFruitCount();
        //总页数
        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);
    }
}

尚硅谷Java实战项目 尚硅谷javaweb_intellij-idea_70

8.4 关键字查询

通过sql的like进行模糊查询

<form th:action="@{/index}" method="post" style="float:left;width:60%;margin-left:20%;">
	<input type="hidden" name="oper" value="search"/>
	请输入关键字:<input type="text" name="keyword" th:value="${session.keyword}"/>
	<input type="submit" value="查询" class="btn"/>
</form>
@Override
    public List<Fruit> getFruitList(String keyword, Integer pageNo) {
        return super.executeQuery("select * from t_fruit where fname like ? or remark like ? limit ? , 5" , "%"+keyword+"%", "%"+keyword+"%", (pageNo-1)*5);
    }

    @Override
    public int getFruitCount(String keyword) {
        return ((Long)super.executeComplexQuery("select count(*) from t_fruit where fname like ? or remark like ?", "%"+keyword+"%", "%"+keyword+"%")[0]).intValue();
    }
}
//Servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    @Override
    public void doGet(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);
    }
}

尚硅谷Java实战项目 尚硅谷javaweb_尚硅谷Java实战项目_71