#1. Servlet

1. 获取参数

s1.用户发请求,action=add;

s2.项目中web.xml中找到url-pattern=/add 第12行

s3.找第11行的servlet-name = AddServlet

s4.找servlet-mapping中ervlet-name一致的servlet,找到第7行

s5.找第8行的servlet-class -> servlets.AddServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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>
<servlet-name>AddServlet</servlet-name>
<servlet-class>servlets.AddServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>

</web-app>

s6.用户发送的是post请求(method=post),因此tomcat会执行AddServlet中的doPost方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AddServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String fname = request.getParameter("fname");

String priceStr = request.getParameter("price");
String fcountStr = request.getParameter("fcount");

Integer price = Integer.parseInt(priceStr);
Integer fcount = Integer.parseInt(fcountStr);

String remark = request.getParameter("remark");

System.out.println("fname = " + fname);
System.out.println("price = " + price);
System.out.println("fcount = " + fcount);
System.out.println("remark = " + remark);
}
}

注意!

  • 出现这种问题,要关注读取配置文件的方式
1
2
3
4
//法一:
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
//法二:
InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
  • 原因:使用ClassLoder读取配置文件,该方式只能读取类路径下的配置文件,所以一般需要将配置文件放在类路径目录下,即resources下。
  • 而配置文件放在src下,则无法读取
  • 问题的关键就在于配置文件的位置,这会影响读取的方法

2.设置编码

  • post方式下,设置编码,防止中文乱码;get方式目前不需要设置编码
  • 注意,设置编码这一句代码必须在所有的获取参数动作之前
1
request.setCharacterEncoding("utf-8");

3. Servlet的继承关系

1. 继承关系

  • javax.servlet.Servlet接口

    ​ javax.servlet.GenericServlet抽象类

    ​ javax.servlet.http.HttpServlet抽象子类

2. 相关方法

  • javax.servlet.Servlet接口:

    • void init(config) - 初始化方法
    • void service(request, response) - 服务方法
    • void destroy() - 销毁方法
  • javax.servlet.GenericServlet抽象类:

    • void service(request, response) - 仍然是抽象的
  • javax.servlet.http.HttpServlet 抽象子类:

    • void service(request, response) - 不是抽象的

1.String method = req.getMethod(); 获取请求的方式:是post还是get还是其他什么

2.各种if判断,根据请求方式不同,调用不同的do方法

3.在HttpServlet这个抽象类中,do方法都差不多:

service()方法

  • 当有请求过来时,service方法会自动响应(其实是tomcat容器调用的)

    在HttpServlet中我们会去分析请求的方式:到底是get、post还是其他等等

    然后再决定调用的是哪个do开头的方法,这些方法都需要我们子类去实现对应的方法,否则会报405错误

    因此,我们在新建Servlet时,我们就会考虑请求方法,并重写相应的do方法,否则默认调用get方法并报405错误

4. Servlet的生命周期

  • 生命周期:对应Servlet中的三个方法:init(), service(), destroy()

  • 默认情况下:

    第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(init())、然后服务(service())

    从第二次请求开始,每一次都是服务

    当容器关闭时,其中所有的Servlet实例会被销毁,调用销毁方法(destroy)

  • Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。

    只有第一次接收请求,才会实例化、初始化,然后开始服务。

    优点:提高系统的启动速度;缺点:第一次请求时,耗时较长

  • 结论:如果需要提高系统的启动速度,这样就可以了;如果需要提高响应速度,我们应该设置Servlet的初始化时机。

Servlet的初始化时机

  • 默认是第一次接收请求时,实例化、初始化,

    我们可以通过在配置文件中设置<load-on-startup>来设置Servlet启动的先后顺序,

    数字越小,启动越靠前,最小值0

Servlet在容器中是:单例的,线程不安全的

  • 单例:所有的请求都是同一个实例去响应

    线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是中间某个时机,另一个线程改变了这个成员变量的值,导致第一个线程的执行路径发生了变化

  • 我们已经知道了Servlet是线程不安全的,给我们的启发是:尽量不要在Servlet中定义成员变量,如果不得不定义成员变量,那么不要去:①修改成员变量的值②根据成员变量的值进行逻辑判断

初始化

  • Servlet中的初始化方法有两个:init(), init(ServletConfig config)

    其中带参数的方法代码如下:

    public void init(ServletConfig config) throws ServletException{

    ​ this.config = congfig;

    ​ init();

    }

    另一个无参的方法如下:

    public void init() throws ServletException{

    }

    如果我们想在Servlet初始化时做一些准备工作,我们可以重写init()方法

    我们可以通过如下步骤去获取初始化设置的数据

    -获取config对象:ServletConfig config = getServletConfig();

    -获取初始化参数值:config.getInitParameter(key);

  • 在web.xml文件中配置Servlet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?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>
    <servlet-name>Demo01Servlet</servlet-name>
    <servlet-class>servlet.Demo01Servlet</servlet-class>
    <init-param>
    <param-name>hello</param-name>
    <param-value>world</param-value>
    </init-param>
    <init-param>
    <param-name>uname</param-name>
    <param-value>jim</param-value>
    </init-param>
    </servlet>
    <servlet-mapping>
    <servlet-name>Demo01Servlet</servlet-name>
    <url-pattern>/demo01</url-pattern>
    </servlet-mapping>
    </web-app>
  • 也可以通过注解的方式进行配置:

    1
    2
    3
    4
    5
    @WebServlet(urlPatterns = {"/demo01"},
    initParams = {
    @WebInitParam(name="hello", value="world"),
    @WebInitParam(name="uname", value="jim")
    })
  • 学习Servlet中的ServletContext和<context-param>

    1.获取ServletContext,有很多方法

    在初始化方法中,ServletContext servletContext = getServletContext();

    在服务方法中也可以通过req对象获取,也可以通过session获取

    2.获取初始化值:

    servletContext.getInitParameter();

5. Http协议

  • HTTP: Hyper Text Transfer Protocol 超文本传输协议。HTTP最大的作用就是确定了请求和响应数据的格式。
  • HTTP无状态:服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的
  • HTTP请求响应包含两个部分:请求和响应

1. 请求

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

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

  3. 请求体,三种情况:

    get方式,没有请求体,但是有一个queryString

    post方式,有请求体,form data

    json格式,有请求体,request payload

2. 响应

  • 响应包含三个部分:1. 响应行; 2. 响应消息头; 3. 响应体
  1. 响应行包含三个信息:1. 协议; 2. 响应状态码(200); 响应状态(ok)
  • 响应状态码:

    200:正常响应

    404:找不到资源

    405:请求方式不支持

    500:服务器内部错误

  1. 响应消息头:包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)
  2. 响应体:响应的实际内容(比如请求add.html页面时,响应的内容就是<form…)

6. 会话

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

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

  • 会话跟踪技术:

    客户端第一次发请求给服务器,服务器获取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的非激活间隔时长

    session.invalidate() -> 强制性让会话立即失效

  • session保存作用域

    session.setAttribute(“uname”, “lina”); -> 向当前session保存作用域保存一个数据”lina”,对应的key为”uname”;

    session.getAttribute(“uname”); -> 从当前session保存作用域获取指定的key”uname’对应的value值

    void removeAttribute(k);

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

7. 服务器内部转发以及客户端重定向

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

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

    地址栏没有变化

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

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

    地址栏有变化

8. 保存作用域

  • 原始情况下,保存作用域有四个:page(页面级别,现在几乎不用), request(一次请求响应范围), session(一次会话范围), application(整个应用程序范围)
1
2
3
4
5
6
req.setAttribute("uname", "adomais");//request保存作用域

req.getSession().setAttribute("uname", "adomais");//session保存作用域

ServletContext application = req.getServletContext();//application保存作用域
application.setAttribute("uname", "lili");

9. 路径问题

  • ../:返回上一级目录

  • 绝对路径:http://localhost:8080/contextroot/

  • <base href="http://localhost:8080/pro/" />的作用是:当前页面上的所有的路径都以这个为基础

    <link href="css/shopping.css">

  • th:href=”@{}” 表示基于当前根目录

    <link th:href="@{/css/shopping.css}">

10. 什么是业务层

  • MVC: Model 模型,View 视图, Controller 控制器

    • 视图层:用于做数据展示以及和用户交互的一个界面
    • 控制层:能够接受客户端的请求,具体的业务功能还是需要模型来完成
    • 模型层:模型分为很多种:有比较简单的pojo/vo(value object),有业务模型组件,有数据访问层组件
    • pojo/vo:值对象
    • DAO:数据访问对象
    • BO:业务对象
  • 区分业务对象和数据访问对象

    1.DAO中的方法都是单精度方法或者称为细粒度方法。一个方法只考虑一个操作

    2.BO中的方法属于业务方法,实际的业务是比较复杂的,因此业务方法的粒度是比较粗的

    注册这个功能属于业务功能,也就是说注册这个方法属于业务方法

    那么这个业务方法中包含了多个DAO方法。也就是说注册这个业务功能需要通过多个DAO方法的组合调用,从而完成注册功能开发。

11. IOC

  • 耦合/依赖

    依赖指的是某某某离不开某某某

    在软件系统中,层与层是存在依赖的。我们也称之为耦合

    我们系统架构或者是设计的一个原则是:高内聚低耦合

    层内部的组成应该是高度聚合的,而层与层之间的关系应该是低耦合的,最理想的情况0耦合

  • IOC - 控制反转 / DI - 依赖注入

  • 控制反转:

    1. 之前在Servlet中,我们创建service对象,FruitService fruitService = new FruitServiceImpl();

      这种写法如果出现在Service中的某个方法内部,那么这个fruitService的作用域应该就是这个方法级别;如果这种写法出现在Service的类中,那么这个fruitService的作用域就是这个Service实例级别

    2. 之后我们在applicationContext.xml中定义了这个fruitService。然后通过解析XML,产生fruitService实例,存放在beanMap中,这个beanMap在一个BeanFactory中。

      因此我们转移了之前的service实例、dao实例等等他们的生命周期。控制权从程序员转移到BeanFactory。这个现象我们称之为控制反转

  • 依赖注入:

    1. 之前我们在控制层出现代码:FruitService fruitService = new FruitServiceImpl();

      那么,控制层和service层存在耦合。

    2. 之后我们将代码修改成FruitService fruitService = null;

      然后在配置文件中配置:

      <bean id="fruit" class="FruitController">

      <property name="fruitService" ref="fruitService"/>

      </bean>

      这就是依赖注入。

12. 过滤器Filter

  • Filter也属于Servlet规范

  • Filter开发步骤:新建类实现Filter接口,然后实现其中的三个方法:init/doFilter/destroy

    配置Filter,可以使用注解@WebFilter,也可以使用xml文件

  • Filter配置时,也可以配置通配符。例如@WebFilter(“*.do”)表示拦截所有以.do结尾的请求

  • 过滤器链:

    1. 如果采取注解的方式进行配置,那么过滤器的拦截顺序是按照全类名的先后顺序排序的
    2. 如果采取的是xml的方式进行配置,那么按照配置的先后顺序进行排序

13. 监听器

  • ServletContextListener - 监听ServletContext对象的创建和销毁
  • HttpSessionListener - 监听HttpSession对象的创建和销毁
  • ServletRequestListener - 监听ServletRequest对象的创建和销毁
  • ServletContextAttributeListener - 监听ServletContext的保存作用域的改动(add, remove, replace)
  • HttpSessionAttributeListener - 监听HttpSession的保存作用域的改动(add, remove, replace)
  • ServletRequestAttributeListener - 监听ServletRequest的保存作用域的改动(add, remove, replace)
  • HttpSessionBindingListener - 监听某个对象在Session域中的创建和移除
  • HttpSessionActivationListener - 监听某个对象在Session域中的序列化和反序列化时

13. 事务管理

  • service操作应该是一个整体。事务管理不能以DAO层的单精度方法为单位,而应该以业务层的方法为单位

  • ThreadLocal

    -get(), set(obj)

    -ThreadLocal称为本地线程,我们可以通过set方法在当前线程上存储数据,通过get方法在当前线程上获取数据

    -set方法源码分析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void set(T value){
    Thread t = Thread.currentThread();//获取当前的线程
    ThreadLocal map = getMap(t);//每一个线程都维护一个容器(ThreadLocalMap)
    if(map != null)
    map.set(this, value);
    //this代表当前的ThreadLocal,因为我们的组件中需要传输的对象可能有多个,map中可以存放多个ThreadLocal
    //一个线程对应一个ThreadLocalMap,一个ThreadLocalMap对应多个TreadLocal,一个ThreadLocal对应一个需要同步的值
    else
    creatMap(t, value);
    }

    -get方法源码分析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Thread t = Thread.currentThread();//获取当前的线程
    ThreadLocalMap map = getMap(t);//每一个线程都维护一个容器(ThreadLocalMap)
    if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
    @SuppressWarnings("unchecked")
    T result = (T)e.value;
    return result;
    }
    }
    return setInitialValue();

#2. Thymeleaf - 视图模板技术

  • Servlet从3.0版本开始支持注解方式的注册,不需要写xml文件了
  1. 添加thymeleaf的jar包
  2. 新建一个Servlet类VieBaseServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package 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;

/**
* @author adomais
* @create 2022-08-04-16:58
*/
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());
}
}
  1. 在web.xml文件中添加配置

    配置前缀 view - prefix

    配置后缀 view - suffix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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">
<!-- 配置上下文参数 -->
<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>
</web-app>
  1. 让我们的Servlet继承ViewBaseServlet
  2. 根据逻辑视图名称 得到 物理视图名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package fruit.servlets;

import fruit.bean.Fruit;
import fruit.dao.FruitDAO;
import fruit.dao.impl.FruitDAOImpl;
import myssm.myspringMVC.ViewBaseServlet;

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.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

/**
* @author adomais
* @create 2022-08-04-16:06
*/
//Servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
FruitDAO fruitDAO = new FruitDAOImpl();

List<Fruit> list = fruitDAO.getFruitList();
//保存到session作用域
HttpSession session = req.getSession();
session.setAttribute("list", list);
//此处的视图名称是 index
//那么thymeleaf会将这个逻辑视图名称对应到 物理视图 名称上去
//逻辑视图名称: index
//物理视图名称: view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是:/ index .html
super.processTemplate("index", req, resp);
}
}

  1. 使用thymeleaf的标签

    th:if

    th:unless

    th:each

    th:text