JavaWeb

JS部分

JSON

前端转换

// json转对象
var personStr = '{"name":"张三","age":"20"}';
var person = JSON.parse(personStr);

// 对象转json
var personStr2 = JSON.stringify(person);

后端转换

导入jackson包jackson-annotations-2.13.2.jarjackson-core-2.13.2.jarjackson-databind-2.13.2.jar

// 对象转json
Dog dog = new Dog("大黄");
Person person = new Person(20, "小明", dog);
ObjectMapper objectMapper = new ObjectMapper();
String personStr = objectMapper.writeValueAsString(person);

// json转对象
String personStr = "{\"age\":20,\"name\":\"小明\",\"dog\":{\"name\":\"大黄\"}}";
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(personStr, Person.class);

// Map、List、Array转json
Map data = new HashMap<>();
data.put("a", "valuea");
data.put("b", 20);

List data = new ArrayList<>();
data.add("a");
data.add("b");

String[] data = {"a", "b"};

ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(data);
// {"a":"valuea","b":20}
// ["a","b"]

事件

绑定

  • 一个事件可以绑定多个函数
  • 一个元素可以绑定多个事件
<input type="button" value="按钮" onclick="fun1(),fun2()" ondblclick="fun3()">

常见事件

  • 鼠标事件:onclickondbclickonmouseoveronmousemoveonmouseleave

  • 键盘事件:onkeydownonkeyup

  • 表单事件

    • onfocus:获得焦点
    • onblur:失去焦点
    • onchange:文本内容改变,失去焦点后触发;选项改变。通过如下方式获取value

    <input type="text" name="realName" onchange="testChange(this.value)">
    
    <select onchange="testChange(this.value)" >
        <option value="1">北京</option>
        <option value="2">上海</option>
        <option value="3">广州</option>
    </select>

    function testChange(value) {
        console.log(value);	// 文本框的值/option中value的值
    }

    • onsubmit:提交表单,绑定到form上

    <form action="./hello.html" method="get" onsubmit="testSubmit()">
    <form action="./hello.html" method="get" onsubmit="return testSubmit()">

    function testSubmit() {
        var flag = confirm("确认提交?");		// 返回布尔值
        if (!flag)  event.preventDefault();		// 阻止组件的默认行为
        return false;		// 也可以通过返回false来阻止提交(,这时onsubmit中要加return,如上第二个
    }

    • onreset:重置表单,绑定到form上
  • 页面加载事件:onload

DOM动态绑定

window.onload = function() {
    var btn = document.getElementById("btn");
    btn.onclick = function () {
        console.log("DOM绑定");
    }
}

BOM

Browser Object Model

window表示整个窗口对象,通过windos对象及其属性的API控制浏览器的属性和行为。windos对象由浏览器提供,无需创建。window.可以省略。

window对象API

三种弹窗

// 提示框
window.alert("hello");

// 输入框(返回输入的内容)
var res = window.prompt("请输入:");

// 确认框(返回true/false)
var res = window.confirm("确定要删除吗");

定时任务

window.setTimeout(function() {
    console.log("时间到");
}, 2000);

window对象属性

  • document:打开的html文档

  • history:历史

// 向前翻页(浏览器左上角的右箭头)
history.forward();
history.go(2);	// 向前翻2页

// 向后翻页(左箭头)
history.back();
  • location:地址栏
// 修改地址栏中的url,用于跳转
location.href = "https://shuusui.site";
  • sessionStorage:会话级存储,浏览器关闭数据清除

  • localStorage:持久级存储,浏览器关闭数据还在

// 存储
sessionStorage.setItem("keya", "valueA");
localStorage.setItem("keyb", "valueB");

// 读取
console.log(sessionStorage.getItem("keya"));
console.log(localStorage.getItem("keyb"));

// 删除
sessionStorage.removeItem("keya");
localStorage.removeItem("keyb");
  • console:控制台

  • screen:屏幕

  • navigator:浏览器软件本身

DOM

Document Object Model。使用document对象编程。

DOM树上的结点类型:

  • 元素结点(element):标签
  • 属性结点(attribute):属性
  • 文本结点(text):标签中的文字

获取元素

直接获取

// 通过id获取唯一元素
var el = document.getElementById("btn");

// 通过标签名获取多个元素
var els = document.getElementsByTagName("input");
for (var i = 0; i < els.length; i++)
    console.log(els[i]);

// 通过name属性值获取多个元素
var els = document.getElementsByName("aaa");
for (var i = 0; i < els.length; i++)
    console.log(els[i]);

// 通过class属性获取多个元素
var els = document.getElementsByClassName("aaa");
for (var i = 0; i < els.length; i++)
    console.log(els[i]);

间接获取

// 通过父元素获取所有子元素
var div = document.getElementById("div1");
var children = div.children;
for (var i = 0; i < children.length; i++)
    console.log(children[i]);

var first = div.firstElementChild;	// 获取第一个子元素
var last = div.lastElementChild;	// 获取第二个子元素

// 通过子元素获取父元素
var btn = document.getElementById("btn");
var div = btn.parentElement;

// 获取兄弟元素
var btn = document.getElementById("btn");
var brother1 = btn.previousElementSibling;	// 获取前一个兄弟元素
var brother2 = btn.nextElementSibling;		// 获取后一个兄弟元素

操作元素

  • 操作属性元素.属性名
var btn = document.getElementById("btn");
// 读取属性
console.log(btn.type, btn.value);
// 修改属性
btn.type = "text";
btn.value = "hello";
  • 操作样式元素.style.样式名
var btn = document.getElementById("btn");
btn.style.color = "red";
btn.style.borderRadius = "5px";	// 原样式中的-要转换成驼峰式
  • 操作文本元素.innerText只识别文本,元素.innerHTML可以识别html代码
var div = document.getElementById("div1");
console.log(div.innerText);
div.innerText = "hi";
div.innerHTML = "<h1>hi</h1>";
  • 创建元素document.createElement("元素名")
  • 添加元素父元素.appendChild(子元素)父元素.insertBefore(新元素, 参照元素)
  • 替换元素父元素.replaceChild(新元素, 被替换元素)
  • 删除元素要删除的元素.remove()
// 创建元素
var csli = document.createElement("li");

// 修改元素
csli.id = "sh";
csli.innerText = "上海";

// 在父元素中追加子元素
var city = document.getElementById("city");
city.appendChild(csli);

// 在某个元素前添加子元素
var sz = document.getElementById("sz");
city.insertBefore(csli, sz);

// 替换子元素
city.replaceChild(csli, sz);

// 删除元素
sz.remove();
city.innerHTML = "";	// 将父元素的innerHTML赋值为空也即删除了所有子元素,也可以循环删除子元素

XML和Tomcat

XML

介绍

  • 第一行必须是:<?xml version="1.0" encoding="xxx" ?>
  • 根标签只有一个,如下面的<jdbc>
  • xml有约束,约束用于限定xml内部能编写的内容,如web.xml中所使用的
    • dtd约束:简单,不够细致
    • schema约束:复杂,细致
<?xml version="1.0" encoding="UTF-8" ?>
<jdbc>
    <dev>
        <username>root</username>
        <password>root</password>
        <driver>com.mysql.cj.jdbc.Driver</driver>
        <url>jdbc:mysql://localhost:3306/atguigu</url>
    </dev>
    <test>
        <username>zhangsan</username>
        <password>abc123</password>
        <driver>com.mysql.cj.jdbc.Driver</driver>
        <url>jdbc:mysql://xxx:3306/atguigu</url>
    </test>
</jdbc>

DOM4J解析

导入dom4j-1.6.1.jar。字节码根路径如红框所示:

// 创建SAXReader对象
SAXReader saxReader = new SAXReader();
// 通过类加载器获得指向字节码根路径下的指定文件的输入流
InputStream resourceAsStream = Dom4jTest.class.getClassLoader().getResourceAsStream("jdbc.xml");
// 通过输入流获得配置文件,解析成一个dom对象
Document document = saxReader.read(resourceAsStream);

// 读取xml
Element rootElement = document.getRootElement();    // 获取根元素
System.out.println(rootElement.getName());

List<Element> elements = rootElement.elements();    // 获取子元素
for (Element element: elements) {
    System.out.println("\t" + element.getName());
    // 获取属性
    Attribute idAttribute = element.attribute("id");
    System.out.println("\t\t" + idAttribute.getName() + "=" + idAttribute.getValue());
    // 继续获取子元素
    List<Element> eles = element.elements();
    for (Element ele: eles) {
        System.out.println("\t\t" + ele.getName() + ":" + ele.getText());
    }
}

Tomcat

安装配置

需要在环境变量中配置JAVA_HOME,其值为jdk路径,如C:\Program Files\Java\jdk-17

conf/logging.properties文件中找到如下行,修改编码为GBK,否则命令行会有乱码。

java.util.logging.ConsoleHandler.encoding = GBK

WebAPP结构

app
	index.html
	static
		img
		js
		css
	WEB-INF		# 受保护的文件夹,不可以通过url访问
		web.xml		# 项目配置文件
        classes		# 字节码根路径
        lib			# 外部库

部署项目

  • 方法一:将项目放到webapps目录下

  • 方法二:在/conf/Catalina/localhost/下创建<项目名>.xml,写入

    <Context path="/项目名" docBase="项目路径" />

  • idea部署方法:创建tomcat副本,包含项目相关配置文件,路径位于C:\Users\用户名\AppData\Local\JetBrains\IntelliJIdea202x.x\tomcat\,里面其实用的就是方法二

访问manager和host-manager

/conf/tomcat-users.xml中加入以下内容:

<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
	<role rolename="admin-gui"/>
	<role rolename="admin-script"/>
	<role rolename="manager-gui"/>
	<role rolename="manager-script"/>
	<role rolename="manager-jmx"/>
	<role rolename="manager-status"/>
	<user username="admin" 
          password="admin"
          roles="admin-gui,admin-script,manager-gui,manager-script,manager-jmx,manager-status"
	/>
</tomcat-users>

访问/manager/host-manager,输入配置的用户名、密码即可访问tomcat管理界面。

IDEA中使用

  • 设置项目jdk版本17
  • Settings->Build,Execution,Deployment->Application Servers中添加tomcat10
  • 创建项目
  • Project Structure->Modules->选择要添加的模块->Dependencies中添加tomcat10(Library)
  • 模块右键,Add Frameworks Support,选择Web Application(注意版本应为5.0及以上,否则就是没配好上一步)
  • 编写网站
  • 在运行配置中添加Tomcat Server,选择tomcat10,配置Deployment中的项目和上下文,配置Server中的浏览器、jre等

Servlet

运行逻辑

  • 将请求放入对象。

    • tomcat接收到请求后,会将请求报文的信息转换为一个HttpServletRequest对象,该对象包含了请求中的所有信息(请求行、请求头、请求体)。

    • tomcat同时创建一个HttpServletResponse对象,用于盛放要响应给客户端的信息, 之后该对象会被转换成响应报文(响应行、响应头、响应体)。

  • 找到对应的Servlet类(比如Servlet1,它实现了Servlet),tomcat自动调用它的service方法,并传入(HttpServletRequest request, HttpServletResponse response)两个对象。

  • 编写逻辑(只有这一步需要自己写):从request获取信息;生成要返回的数据;将数据放入response

  • 由tomcat将HttpServletResponse处理成响应报文返回。

编写流程

  • 创建javaWeb项目,将tomcat添加为当前项目的依赖

    <form method="get" action="verify">
        用户名:<input type="text" name="username"><br>
        <input type="submit" value="校验">
    </form>

  • 重写service方法:service(HttpServletRequest req, HttpServletResponse resp)

  • 在service方法中进行业务处理

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 从req中获取请求中的信息
        String username = req.getParameter("username"); // 根据参数名获取参数值,不论是url后的参数还是请求头中的参数
    
        // 2. 业务代码
        String info = "yes";
        if ("atguigu".equals(username)) {
            info = "no";
        }
    
        // 3. 将要响应的数据放入resp
        resp.setContentType("text/html");   // 设置Content-Type
    	// 或 resp.setHeader("Content-Type", "text/html");
        PrintWriter writer = resp.getWriter();  // 返回向响应体中打印字符串的打印流
        writer.write(info);
    }

  • 在web.xml中配置路径

    <servlet>
        <!-- 用于关联请求的映射路径 -->
        <servlet-name>userServlet</servlet-name>
        <!-- 对应的Servlet类 -->
        <servlet-class>servlet.UserServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>userServlet</servlet-name>
        <url-pattern>/verify</url-pattern>
    </servlet-mapping>

一些问题

servlet-api.jar导入

servlet所使用的类来自tomcat/lib提供的servlet-api.jar,在编码时,只需要在idea中的module中导入Tomcat的library(这样构建时不会携带。不需要手动导入该包然后添加为library),在tomcat服务器上lib中的包所有项目都可以使用。

Content-Type

conf/web.xml:记录了几乎所有文件类型对应的MIME类型。

设置Content-Type:

resp.setContentType("text/html");

url-pattern

  • 一个servlet-name可以对应多个url-pattern(也可以对应多个servlet-mapping,但是没必要)。
  • url-pattern
    • 精确匹配:/aaa
    • 模糊匹配:*作为通配符
      • /:匹配全部,不包含jsp文件
      • /*:匹配全部,包含jsp文件
      • /a/*:匹配前缀,后缀模糊(如匹配/a/xxx
      • *.action:匹配后缀,前缀模糊
<servlet>
    <servlet-name>userServlet</servlet-name>
    <servlet-class>servlet.UserServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>userServlet</servlet-name>
    <url-pattern>/aaa</url-pattern>
    <url-pattern>/xxx</url-pattern>
    <url-pattern>/abc/*</url-pattern>
</servlet-mapping>

注解方式配置

@WebServlet(value="/aaa")
@WebServlet("/aaa")				// 只有value可以省略
@WebServlet(urlPatterns="/aaa")	// 与value等价
@WebServlet({"/aaa", "/bbb"})	// value与urlPatterns可以是数组,这里省略了value

生命周期

过程 调用的函数 什么时候调
实例化 构造器 第一次请求时/服务启动
初始化 init方法 第一次请求时,实例化后
接收请求,处理请求 service方法 每次请求
销毁 destory方法 关闭服务器

Servlet对象在Tomcat中是单例的,Servlet的成员变量在多个线程栈中是共享的,不建议在service方法中修改成员变量,否则并发时会有线程安全问题。

实例化的时间

  • 默认第一次请求时实例化,下面两种方式的参数默认值为-1

    @WebServlet(value = "/aaa", loadOnStartup = -1)

    <servlet>
        <servlet-name>userServlet</servlet-name>
        <servlet-class>servlet.UserServlet</servlet-class>
        <load-on-startup>-1</load-on-startup>
    </servlet>

  • 如果填一个正整数,tomcat启动时会实例化,指定的值为启动顺序(越小越先实例化),值一样则tomcat会自动处理。

    /conf/web.xml中的配置的占用了5个,建议从6开始。

default-servlet

当所有定义的servlet都匹配不上时就会调用default-servlet,所有的静态资源都是通过default-servlet获取的。

继承结构

UserServlet 继承 HttpServlet
HttpServlet 继承 GenericServlet
GenericServlet 实现了 Servlet 接口

下面的代码省去了不重要的逻辑

顶级Servlet接口

public interface Servlet {
    // 初始化方法,构造完毕后,由tomcat自动调用
    void init(ServletConfig var1) throws ServletException;
    
	// 获得ServletConfig对象
    ServletConfig getServletConfig();

    // 接收请求,响应信息
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    // 返回Servlet字符串形式描述信息的方法
    String getServletInfo();

    // Servlet回收前由tomcat调用的销毁方法,往往用于资源释放
    void destroy();
}

抽象类GenericServlet

侧重除service方法以外的处理。

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    
    private transient ServletConfig config;
    
    // 将抽象方法重写为普通方法,内部没有实现代码,平庸实现
    public void destroy() {}
    
    // 将读取的配置信息放入ServletConfig对象传入init方法
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();	// 调用下面重载的无参init
    }
    // 重写init时重写的是这个无参的init方法
    public void init() throws ServletException {}
    
    // 简单的返回this.config
    public ServletConfig getServletConfig() {
        return this.config;
    }
    
    // 再次抽象声明service方法
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}

抽象类HttpServlet

侧重service方法的处理。

public abstract class HttpServlet extends GenericServlet {
    
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // 参数父转子,ServletRequest -> HttpServletRequest,这样能调用更多的api
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
		
        // 调用重载的service方法
        this.service(request, response);
    }
    
    // 重载的service方法
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的方式
        String method = req.getMethod();	// GET、POST、……
        
        // 根据请求方式调用对应的do...方法
        if (method.equals("GET")) {
            this.doGet(req, resp);
        } else if (method.equals("HEAD")) {
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            resp.sendError(501, errMsg);
        }
    }
    
    // doGet故意响应405 请求方式不允许
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = "http.method_get_not_supported";
        resp.sendError(405, msg);
    }
    // doPost也是故意响应405
}

自定义Servlet

public class UserServlet extends HttpServlet {

    // 直接重写service
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
    
    // 或重写doGet、doPost方法
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
    }
}

总结:要么重写service,要么重写doGet,doPost

ServletConfig

每个Servlet提供配置参数。

xml中配置

配置在<servlet>标签里。

<servlet>
    <servlet-name>servlet1</servlet-name>
    <servlet-class>servlet.Servlet1</servlet-class>
    <!--配置初始参数-->
    <init-param>
        <param-name>keya</param-name>
        <param-value>valueA</param-value>
    </init-param>
    <init-param>
        <param-name>keyb</param-name>
        <param-value>valueB</param-value>
    </init-param>
</servlet>

注解中配置

@WebServlet(
        urlPatterns = "/aaa",
        initParams = {
                @WebInitParam(name = "keya", value = "valueA"),
                @WebInitParam(name = "keyb", value = "valueB")
        }
)

获取参数

service方法中

// 获取初始配置信息
ServletConfig servletConfig = getServletConfig();
// 根据参数名获取参数值
System.out.println(servletConfig.getInitParameter("keya"));
// 获取所有初始参数的名字
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
    System.out.println(initParameterNames.nextElement());
}

ServletContext

所有Servlet提供配置参数,单例。

xml中配置

直接配置在根标签中。

<context-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
</context-param>

<context-param>
    <param-name>username</param-name>
    <param-value>zhangsan</param-value>
</context-param>

获取参数

// 获取ServletContext,下面三种方式都可以,获取到的是一样的
ServletContext servletContext1 = getServletContext();
ServletContext servletContext2 = servletConfig.getServletContext();
ServletContext servletContext3 = req.getServletContext();
assert servletContext1 == servletContext2 &&
    servletContext2 == servletContext3;

// 根据参数名获取参数值
String encoding = servletContext1.getInitParameter("encoding");
System.out.println(encoding);

// 获取所有参数名
Enumeration<String> initParameterNames = servletContext1.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
    System.out.println(initParameterNames.nextElement());
}

获取路径

获取指向项目部署位置下的文件/目录的真实路径

ServletContext servletContext = getServletContext();
String path = servletContext.getRealPath("upload");
System.out.println(path);
// D:\programming\JavaWeb\workspace\web-all\out\artifacts\demo03_config_context_war_exploded\upload

获取项目部署的上下文路径(访问路径)

ServletContext servletContext = getServletContext();
String contextPath = servletContext.getContextPath();
System.out.println(contextPath);	// /demo03

不会被url中的参数干扰,如localhost:8080/demo03/aaa?username=aaa,也会返回/demo03

域对象

ServletContext代表应用,也称应用域,是webapp中最大的域,可以实现本应用内数据的共享和传递(有的程序员把ServletContext对象命名为application)。比如两个Servlet之间不能直接传递数据,就可以通过一个Servlet把数据写进域,另一个Servlet读的方式来传递。

webapp中的三大域对象:应用域、会话域、请求域

域对象都有有如下三个API:

API 功能
void setAttribute(String key, Object value); 向域中存储/修改数据
Object getAttribute(String key); 获得域中数据(没有则返回null)
void removeAttribute(String key); 移除域中数据
// Servlet1中
ServletContext servletContext = getServletContext();
servletContext.setAttribute("keya", "valueA");  // 存储
servletContext.setAttribute("keya", "valueB");  // key相同则修改

// Servlet2中:可以获取到Servlet1中存储的内容
String value = (String) servletContext.getAttribute("keya");
System.out.println(value);
servletContext.removeAttribute("keya");     // 移除

HttpServletRequest

请求行

System.out.println(req.getMethod());        // 请求方式:GET
System.out.println(req.getScheme());        // 请求协议:http
System.out.println(req.getProtocol());      // 请求协议及版本:HTTP/1.1
System.out.println(req.getRequestURI());    // uri 资源路径:/demo03/servlet3
System.out.println(req.getRequestURL());    // url 完整资源路径:http://localhost:8080/demo03/servlet3
System.out.println(req.getServletPath());	// servlet路径:/servlet3

System.out.println(req.getLocalPort());     // 本应用容器的端口号:8080
System.out.println(req.getServerPort());    // 客户端向服务器发请求时使用的端口号(有代理的话可能和上面的不同):8080
System.out.println(req.getRemotePort());    // 客户端软件的端口号:52664

请求头

// 根据名字单独获取某个请求头
System.out.println(req.getHeader("Accept"));

// 获取所有请求头
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
    String hname = headerNames.nextElement();
    System.out.println(hname + ":" + req.getHeader(hname));
}

请求参数

键值对形式的参数

// 根据参数名获取单个参数值
String username = req.getParameter("username");

// 根据参数名获取多个参数值
String[] hobbies = req.getParameterValues("hobby");

// 获取所有的参数名
Enumeration<String> names = req.getParameterNames();
while (names.hasMoreElements()) {
    String name = names.nextElement();
    ......
}

// 获取所有参数的Map集合
Map<String, String[]> parameterMap = req.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
    String key = entry.getKey();
    String[] value = entry.getValue();
}

非键值对形式的参数

// 获取字符输入流,如读json
BufferedReader reader = req.getReader();
// 获取字节输入流,如读文件
ServletInputStream inputStream = req.getInputStream();

HttpServletResponse

// 响应行
resp.setStatus(200);

// 响应头
String s = "abcd";
resp.setHeader("Content-Type", "text/html");
resp.setContentType("text/html");
resp.setContentLength(s.getBytes().length);

// 响应体
// 获得向响应体输入文本的字符输入流
PrintWriter writer = resp.getWriter();
writer.write(s);
// 获得向响应体输入二进制信息的字节输入流
ServletOutputStream outputStream = resp.getOutputStream();

请求转发

  • 通过HttpServletRequest对象实现;
  • 是服务器内部的行为,对客户端屏蔽;
  • 客户端只产生了一次请求,服务端只产生了一对req和resp对象;
  • 客户端地址栏不变;
  • 参数可以继续传递;
  • 目标资源可以是servlet动态资源,也可以是html静态资源,也可以是WEB-INF下的受保护资源(访问WEB-INF下资源的唯一方式),但不能是外部资源(如别的网站)。
// /servletA -> /servletB
RequestDispatcher dispatcher = req.getRequestDispatcher("servletB");    // 获得请求转发器
// getRequestDispatcher("a.html")
// getRequestDispatcher("WEB-INF/b.html")
dispatcher.forward(req, resp);      // 做出转发

响应重定向

  • 通过HttpServletResponse对象实现;
  • 是在服务端的提示下的客户端的行为;
  • 客户端地址栏会变化;
  • 客户端产生了多次请求;
  • 因为多次请求,所以后端有多个req和resp对象;
  • 参数不能传递;
  • 目标资源可以是servlet动态资源、html静态资源、外部资源,但不能是WEB-INF下的受保护资源。
// 响应重定向:设置响应状态码为302,同时设置location响应头
resp.sendRedirect("servletB");

总结:实现页面跳转时,能用重定向就优先用重定向。

乱码问题

  • 请求乱码

    • GET方式:参数在uri里,编码受html中的charset影响,通过设置tomcat的web.xml文件修改;

    • POST方式:参数在请求体中,编码受html中的charset影响,通过下面的代码修改

      // 设置请求体解码使用的字符集
      req.setCharacterEncoding("UTF-8");

  • 响应乱码

    // 设置向客户端响应的编码(不推荐,因为不知道客户端使用的编码)
    resp.setCharacterEncoding("UTF-8");
    
    // 告诉客户端应该用指定的字符集解码(推荐)
    resp.setContentType("text/html;charset=UTF-8");
    // (最好把上面两行都写上,保证tomcat响应的是utf-8)

路径问题

  • 前端相对路径:对于前端来说,是以当前资源所在的路径为出发点去寻找目标资源的。

    • test.html要访问logo.png,当前资源所在的路径为http://localhost:8080/a/b/c/,因此使用以下相对路径

      <img src="../../../static/img/logo.png"/>

    • view1.html要访问logo.png(view1.html在WEB-INF下,只能通过请求转发来访问),当前资源所在的路径为http://localhost:8080/某个Servlet/,因此直接使用以下相对路径

      <img src="static/img/logo.png"/>

  • 前端绝对路径:绝对路径以http://localhost:8080为出发点。如/demo01/static/img/logo.png表示http://localhost:8080/demo01/static/img/logo.png,绝对路径需要包含项目的上下文(/demo01)。

  • 后端相对路径:同前端相对路径。如servletA的完整路径为http://localhost:8080/demo1/servletA,那么其所在路径为http://localhost:8080/demo1/,因此重定向或请求转发到servletB可以直接写

    resp.sendRedirect("servletB");
    req.getRequestDispatcher("servletB").forward(req, resp);

    如果servletA的完整路径为http://localhost:8080/demo1/xxx/servletA,则上面的代码访问不到。

  • 后端绝对路径重定向同前端,以http://localhost:8080为出发点。

    resp.sendRedirect("/demo1/servletB");

    请求转发的出发点包括项目上下文,即以http://localhost:8080/demo1/为出发点。

    req.getRequestDispatcher("/servletB").forward(req, resp);

实际工作中的解决方法:不设置项目上下文,即设置为/,这样不论是绝对路径还是相对路径都不用操心上下文了。

MVC模式

  • M(Model,模型层):业务代码(service),数据库对象实体类(bean/pojo/entity),操作数据库(DAO);
  • V(View,视图层):前端;
  • C(Controller,控制层):接收请求,返回数据(servlet)

会话、过滤器、监听器

会话

  • cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息;
  • session是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息。
  • 默认情况下Cookie的有效期是一次会话,浏览器关闭内存中的Cookie数据就会被释放。可以通过cookie的setMaxAge()方法让Cookie持久化保存到浏览器上。
  • 访问互联网资源时不能每次都把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,可以通过cookie的setPath(String path) 对cookie的路径进行设置,这样只有访问该资源时才携带该cookie。
Cookie cookie1 = new Cookie("keya", "valuea");   // 创建cookie
Cookie cookie2 = new Cookie("keya", "valuea");   // 创建cookie
cookie1.setMaxAge(3600);                // 设置保存时间(秒)
cookie1.setPath("/demo1/servletB");     // 设置cookie的提交路径
resp.addCookie(cookie1);        // 放入resp
resp.addCookie(cookie2);        // 放入resp
Cookie[] cookies = req.getCookies();    // 获取请求中的cookie,没有则为null
if (cookies != null) {
    for (Cookie cookie : cookies) {
        System.out.println(cookie.getName() + ": " + cookie.getValue());
    }
}

Session

存数据

// 获取请求中的参数
String username = req.getParameter("username");
// 获取session对象
HttpSession session = req.getSession();
// 获取Session的ID
String jSessionId = session.getId();
System.out.println(jSessionId);
// 判断session是不是新创建的session
boolean isNew = session.isNew();
System.out.println(isNew);
// 向session对象中存入数据
session.setAttribute("username",username);

读数据

// 获取session对象
HttpSession session = req.getSession();
// 获取Session的ID
String jSessionId = session.getId();
System.out.println(jSessionId);
// 判断session是不是新创建的session
boolean isNew = session.isNew();
System.out.println(isNew);
// 从session中取出数据
String username = (String)session.getAttribute("username");
System.out.println(username);

getSession的逻辑

时效性

可以在web.xml(项目的或是tomcat/conf下的)中设置,单位是分钟。

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

也可以通过java代码设置:

// 设置最大闲置时间
session.setMaxInactiveInterval(60);
// 直接让session失效
session.invalidate();

三大域对象

域对象 Java对象 数据传递范围 应用
请求域 HttpServletRequest 一次请求之内及请求转发 一般放本次请求业务有关的数据
会话域 HttpSession 一次会话之内,可以跨多个请求 一般放本次会话的客户端有关的数据
应用域 ServletContext 本应用之内,可以跨多个会话 一般放本程序应用有关的数据

域对象的API

API 功能
void setAttribute(String name,String value) 向域对象中添加/修改数据
Object getAttribute(String name) 从域对象中获取数据
removeAttribute(String name) 移除域对象中的数据
// 向请求域中放入数据
req.setAttribute("request","request-message");
//req.getRequestDispatcher("servletB").forward(req,resp);

// 向会话域中放入数据
HttpSession session = req.getSession();
session.setAttribute("session","session-message");

// 向应用域中放入数据
ServletContext application = getServletContext();
application.setAttribute("application","application-message");

过滤器

  • Filter的工作位置是项目中获取目标资源之前,容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法;
  • Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续;如果拒绝,则请求到此为止,由过滤器直接响应;
  • Filter也可以在目标资源做出响应前,对响应再次进行处理。

使用

public class LoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        
        // 请求到达目标之前的代码
        ......
            
        // 放行请求
        filterChain.doFilter(request, response);
        
        // 响应之前的代码

    }
}

web.xml中配置

<!--配置filter,并为filter起别名-->
<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
</filter>

<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <!--通过映射路径确定过滤资源-->
    <url-pattern>/servletA</url-pattern>
    <!--通过后缀名确定过滤资源-->
    <url-pattern>*.html</url-pattern>
    <!--通过servlet别名确定过滤资源-->
    <servlet-name>servletBName</servlet-name>
</filter-mapping>
  • url-pattern通过映射路径确定过滤范围,可以配置多个。

    • /servletA:精确匹配资源;

    • *.html:对以.html结尾的路径进行过滤;

    • /*:对所有资源进行过滤。

  • servlet-name通过servlet别名(注解中的name属性)确定过滤范围,前提是有别名。

生命周期

阶段 对应方法 执行时机 执行次数
创建 构造器 web应用启动时 1
初始化 void init(FilterConfig filterConfig) 构造完毕 1
过滤请求 void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 每次请求 多次
销毁 default void destroy() web应用关闭时 1

初始化的filterConfig参数在web.xml中配置。

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
    <init-param>
        <param-name>keya</param-name>
        <param-value>valueA</param-value>
    </init-param>
</filter>

过滤器链

可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链。

Java代码和之前一样,主要是配置过滤器顺序。

  • 如果是在web.xml中配置,filter-mapping标签的顺序决定了过滤器的先后,如下过滤器执行的顺序是321;

    <filter-mapping>
        <filter-name>filter3</filter-name>
        <url-pattern>/servletC</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>filter2</filter-name>
        <url-pattern>/servletC</url-pattern>
    </filter-mapping>
    
    <filter-mapping>
        <filter-name>filter1</filter-name>
        <url-pattern>/servletC</url-pattern>
    </filter-mapping>

  • 如果是注解方式,则先后顺序取决于类名的字典序。

    @WebFilter("/servletA")

注解配置

@WebFilter(
    filterName = "loggingFilter",	// 对应<filter-name>
    initParams = {		// 对应<init-param>
        @WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")
    },
    urlPatterns = {"/servletA","*.html"},	// 与value互为别名
    servletNames = {"servletBName"}		// 对应<servlet-name>
)
@WebFilter("/servletA")		// 直接写相当于配置urlPatterns

监听器

三大域对象的监听,有如下接口:

  • application域监听器:ServletContextListenerServletContextAttributeListener
  • session域监听器:HttpSessionListenerHttpSessionAttributeListenerHttpSessionBindingListenerHttpSessionActivationListener
  • request域监听器:ServletRequestListenerServletRequestAttributeListener

(除会话域监听器的后两个)不带Attribute的监听器是监听域的创建销毁,带Attribute都监听器是监听向域中增删改属性

HttpSessionBindingListener:监听当前监听器对象在Session域中的增加与移除,需要手动为session绑定解绑(几乎不用)。

HttpSessionActivationListener :监听某个对象在Session中的钝化(存入磁盘)与活化(取到内存),需要在META-INF下创建Context.xml并配置(略)才会有钝化活化行为(几乎不用)。

@WebListener	// 记得写注解(否则需要在web.xml中配置)
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {
    // 监听初始化
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        System.out.println("application"+application.hashCode()+" initialized");
    }
    // 监听销毁
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        System.out.println("application"+application.hashCode()+" destroyed");
    }

    // 监听数据增加
    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        System.out.println("application"+application.hashCode()+" add:"+name+"="+value);
    }

    // 监听数据移除
    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        System.out.println("application"+application.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        Object newValue = application.getAttribute(name);
        System.out.println("application"+application.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }
}

项目实战

创建实体类

pojo目录下。

使用idea的lombok插件帮助我们生成:getter、setter、全参构造、无参构造、equals 、hashcode等。

lombok使用步骤 - 检查idea是否已经安装了lombok插件 - settings -> Build,Execution,Deployment -> Compiler -> Annotation Processors -> Enable annotation processing - 导入lombok的依赖lombok-x.x.x.jar

/*
* 1 实体类的类名和表格名称应该对应  (对应不是一致)
* 2 实体类的属性名和表格的列名应该对应
* 3 每个属性都必须是私有的
* 4 每个属性都应该具备 getter setter
* 5 必须具备无参构造器
* 6 应该实现序列化接口(缓存、分布式项目数据传递可能会将对象序列化)
* 7 应该重写类的hashcode和equals方法
* 8 toString是否重写都可以
*/

@AllArgsConstructor // 添加了全参构造
@NoArgsConstructor  // 添加了无参构造
@Data // getter setter equals hashcode toString
//@Data = @Getter @Setter @EqualsAndHashCode @ToString
public class SysUser implements Serializable {
    private Integer uid;
    private String username;
    private String userPwd;

}

DAO

dao目录下,参考JDBC。

Service

service目录下,具体逻辑自己实现。

以上三个对应MVC中的M。

Controller

controller目录下。对应MVC中的C。

下面使用BaseController继承HttpServlet,BaseContoller通过读取uri对请求进行分发。如有/user/add/user/update/user/delete等servlet,通过读取后面的addupdatedelete来分发给不同的函数处理,使用了反射的方法。具体处理servlet的类只需要继承BaseController,然后直接写addupdatedelete方法即可。

public class BaseContoller extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String requestURI = req.getRequestURI(); // /schedule/add
        String[] split = requestURI.split("/");
        String methodName = split[split.length-1];
        // 使用 反射 通过方法名获取下面的方法
        Class aClass = this.getClass();
        // 获取方法
        try {
            Method declaredMethod = aClass.getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
            //暴力 破解方法的访问修饰符的限制
            declaredMethod.setAccessible(true);
            // 执行方法
            declaredMethod.invoke(this,req,resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JavaWeb
https://shuusui.site/blog/2024/08/12/javaweb-1/
作者
Shuusui
发布于
2024年8月12日
更新于
2024年9月24日
许可协议