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.jar
、jackson-core-2.13.2.jar
、jackson-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()">
常见事件
鼠标事件:
onclick
、ondbclick
、onmouseover
、onmousemove
、onmouseleave
键盘事件:
onkeydown
、onkeyup
表单事件
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数据就会被释放。可以通过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域监听器:
ServletContextListener
、ServletContextAttributeListener
- session域监听器:
HttpSessionListener
、HttpSessionAttributeListener
、HttpSessionBindingListener
、HttpSessionActivationListener
- request域监听器:
ServletRequestListener
、ServletRequestAttributeListener
(除会话域监听器的后两个)不带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,通过读取后面的add
或update
或delete
来分发给不同的函数处理,使用了反射的方法。具体处理servlet的类只需要继承BaseController,然后直接写add
或update
或delete
方法即可。
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();
}
}
}