静态资源
动态资源
生活举例
Servlet (server applet) 是运行在服务端(tomcat)的Java小程序,是sun公司提供一套定义动态资源规范; 从代码层面上来讲Servlet就是一个接口
请求响应与HttpServletRequest和HttpServletResponse之间的对应关系
校验注册时,用户名是否被占用. 通过客户端向一个Servlet发送请求,携带username,如果用户名是’atguigu’,则向客户端响应 NO,如果是其他,响应YES
步骤1 开发一个web类型的module
步骤2 开发一个UserServlet
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求中的参数
String username = req.getParameter("username");
if("atguigu".equals(username)){
//通过响应对象响应信息
resp.getWriter().write("NO");
}else{
resp.getWriter().write("YES");
}
}
}
步骤3 在web.xml为UseServlet配置请求的映射路径
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<servlet>
<!--给UserServlet起一个别名-->
<servlet-name>userServlet</servlet-name>
<servlet-class>com.atguigu.servlet.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<!--关联别名和映射路径-->
<servlet-name>userServlet</servlet-name>
<!--可以为一个Servlet匹配多个不同的映射路径,但是不同的Servlet不能使用相同的url-pattern-->
<url-pattern>/userServlet</url-pattern>
<!-- <url-pattern>/userServlet2</url-pattern>-->
<!--
/ 表示通配所有资源,不包括jsp文件
/* 表示通配所有资源,包括jsp文件
/a/* 匹配所有以a前缀的映射路径
*.action 匹配所有以action为后缀的映射路径
-->
<!-- <url-pattern>/*</url-pattern>-->
</servlet-mapping>
</web-app>
步骤4 开发一个form表单,向servlet发送一个get请求并携带username参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="userServlet">
请输入用户名:<input type="text" name="username" /> <br>
<input type="submit" value="校验">
</form>
</body>
</html>
启动项目,访问index.html ,提交表单测试
映射关系图
官方JAVAEEAPI文档下载地址
@WebServlet注解的源码阅读
package jakarta.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @since Servlet 3.0
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
/**
* The name of the servlet
* 相当于 servlet-name
* @return the name of the servlet
*/
String name() default "";
/**
* The URL patterns of the servlet
* 如果只配置一个url-pattern ,则通过该属性即可,和urlPatterns属性互斥
* @return the URL patterns of the servlet
*/
String[] value() default {};
/**
* The URL patterns of the servlet
* 如果要配置多个url-pattern ,需要通过该属性,和value属性互斥
* @return the URL patterns of the servlet
*/
String[] urlPatterns() default {};
/**
* The load-on-startup order of the servlet
* 配置Servlet是否在项目加载时实例化
* @return the load-on-startup order of the servlet
*/
int loadOnStartup() default -1;
/**
* The init parameters of the servlet
* 配置初始化参数
* @return the init parameters of the servlet
*/
WebInitParam[] initParams() default {};
/**
* Declares whether the servlet supports asynchronous operation mode.
*
* @return {@code true} if the servlet supports asynchronous operation mode
* @see jakarta.servlet.ServletRequest#startAsync
* @see jakarta.servlet.ServletRequest#startAsync( jakarta.servlet.ServletRequest,jakarta.servlet.ServletResponse)
*/
boolean asyncSupported() default false;
/**
* The small-icon of the servlet
*
* @return the small-icon of the servlet
*/
String smallIcon() default "";
/**
* The large-icon of the servlet
*
* @return the large-icon of the servlet
*/
String largeIcon() default "";
/**
* The description of the servlet
*
* @return the description of the servlet
*/
String description() default "";
/**
* The display name of the servlet
*
* @return the display name of the servlet
*/
String displayName() default "";
}
使用@WebServlet注解替换Servlet配置
@WebServlet(
name = "userServlet",
//value = "/user",
urlPatterns = {"/userServlet1","/userServlet2","/userServlet"},
initParams = {@WebInitParam(name = "encoding",value = "UTF-8")},
loadOnStartup = 6
)
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String encoding = getServletConfig().getInitParameter("encoding");
System.out.println(encoding);
// 获取请求中的参数
String username = req.getParameter("username");
if("atguigu".equals(username)){
//通过响应对象响应信息
resp.getWriter().write("NO");
}else{
resp.getWriter().write("YES");
}
}
}
什么是Servlet的生命周期
Servlet容器
Servlet主要的生命周期执行特点
生命周期 | 对应方法 | 执行时机 | 执行次数 |
---|---|---|---|
构造对象 | 构造器 | 第一次请求或者容器启动 | 1 |
初始化 | init() | 构造完毕后 | 1 |
处理服务 | service(HttpServletRequest req,HttpServletResponse resp) | 每次请求 | 多次 |
销毁 | destory() | 容器关闭 | 1 |
开发servlet代码
package com.atguigu.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ServletLifeCycle extends HttpServlet {
public ServletLifeCycle(){
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("service方法");
}
@Override
public void destroy() {
System.out.println("销毁方法");
}
}
配置Servlet
<servlet>
<servlet-name>servletLifeCycle</servlet-name>
<servlet-class>com.atguigu.servlet.ServletLifeCycle</servlet-class>
<!--load-on-startup
如果配置的是正整数则表示容器在启动时就要实例化Servlet,
数字表示的是实例化的顺序
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>servletLifeCycle</servlet-name>
<url-pattern>/servletLiftCycle</url-pattern>
</servlet-mapping>
略
源码及功能解释
接口及方法说明
源码
源码解释
源码
解释
继承关系图解
ServletConfig是什么
ServletConfig是一个接口,定义了如下API
package jakarta.servlet;
import java.util.Enumeration;
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}
方法名 | 作用 |
---|---|
getServletName() | 获取<servlet-name>HelloServlet</servlet-name>定义的Servlet名称 |
getServletContext() | 获取ServletContext对象 |
getInitParameter() | 获取配置Servlet时设置的『初始化参数』,根据名字获取值 |
getInitParameterNames() | 获取所有初始化参数名组成的Enumeration对象 |
ServletConfig怎么用,测试代码如下
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = this.getServletConfig();
// 根据参数名获取单个参数
String value = servletConfig.getInitParameter("param1");
System.out.println("param1:"+value);
// 获取所有参数名
Enumeration<String> parameterNames = servletConfig.getInitParameterNames();
// 迭代并获取参数名
while (parameterNames.hasMoreElements()) {
String paramaterName = parameterNames.nextElement();
System.out.println(paramaterName+":"+servletConfig.getInitParameter(paramaterName));
}
}
}
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = this.getServletConfig();
// 根据参数名获取单个参数
String value = servletConfig.getInitParameter("param1");
System.out.println("param1:"+value);
// 获取所有参数名
Enumeration<String> parameterNames = servletConfig.getInitParameterNames();
// 迭代并获取参数名
while (parameterNames.hasMoreElements()) {
String paramaterName = parameterNames.nextElement();
System.out.println(paramaterName+":"+servletConfig.getInitParameter(paramaterName));
}
}
}
<servlet>
<servlet-name>ServletA</servlet-name>
<servlet-class>com.atguigu.servlet.ServletA</servlet-class>
<!--配置ServletA的初始参数-->
<init-param>
<param-name>param1</param-name>
<param-value>value1</param-value>
</init-param>
<init-param>
<param-name>param2</param-name>
<param-value>value2</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>ServletB</servlet-name>
<servlet-class>com.atguigu.servlet.ServletB</servlet-class>
<!--配置ServletB的初始参数-->
<init-param>
<param-name>param3</param-name>
<param-value>value3</param-value>
</init-param>
<init-param>
<param-name>param4</param-name>
<param-value>value4</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ServletA</servlet-name>
<url-pattern>/servletA</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletB</servlet-name>
<url-pattern>/servletB</url-pattern>
</servlet-mapping>
略
ServletContext是什么
ServletContext怎么用
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<context-param>
<param-name>paramA</param-name>
<param-value>valueA</param-value>
</context-param>
<context-param>
<param-name>paramB</param-name>
<param-value>valueB</param-value>
</context-param>
</web-app>
package com.atguigu.servlet;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从ServletContext中获取为所有的Servlet准备的参数
ServletContext servletContext = this.getServletContext();
String valueA = servletContext.getInitParameter("paramA");
System.out.println("paramA:"+valueA);
// 获取所有参数名
Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
// 迭代并获取参数名
while (initParameterNames.hasMoreElements()) {
String paramaterName = initParameterNames.nextElement();
System.out.println(paramaterName+":"+servletContext.getInitParameter(paramaterName));
}
}
}
获取资源的真实路径
String realPath = servletContext.getRealPath("资源在web目录中的路径");
获取项目的上下文路径
String contextPath = servletContext.getContextPath();
域对象的相关API
后续我们会将三大域对象统一进行讲解和演示
,三大域对象都具有的API如下API | 功能解释 |
---|---|
void setAttribute(String key,Object value); | 向域中存储/修改数据 |
Object getAttribute(String key); | 获得域中的数据 |
void removeAttribute(String key); | 移除域中的数据 |
HttpServletRequest是什么
HttpServletRequest怎么用
API | 功能解释 |
---|---|
StringBuffer getRequestURL(); | 获取客户端请求的url |
String getRequestURI(); | 获取客户端请求项目中的具体资源 |
int getServerPort(); | 获取客户端发送请求时的端口 |
int getLocalPort(); | 获取本应用在所在容器的端口 |
int getRemotePort(); | 获取客户端程序的端口 |
String getScheme(); | 获取请求协议 |
String getProtocol(); | 获取请求协议及版本号 |
String getMethod(); | 获取请求方式 |
API | 功能解释 |
---|---|
String getHeader(String headerName); | 根据头名称获取请求头 |
Enumeration getHeaderNames(); | 获取所有的请求头名字 |
String getContentType(); | 获取content-type请求头 |
API | 功能解释 |
---|---|
String getParameter(String parameterName); | 根据请求参数名获取请求单个参数值 |
String[] getParameterValues(String parameterName); | 根据请求参数名获取请求多个参数值数组 |
Enumeration getParameterNames(); | 获取所有请求参数名 |
Map<String, String[]> getParameterMap(); | 获取所有请求参数的键值对集合 |
BufferedReader getReader() throws IOException; | 获取读取请求体的字符输入流 |
ServletInputStream getInputStream() throws IOException; | 获取读取请求体的字节输入流 |
int getContentLength(); | 获得请求体长度的字节数 |
API | 功能解释 |
---|---|
String getServletPath(); | 获取请求的Servlet的映射路径 |
ServletContext getServletContext(); | 获取ServletContext对象 |
Cookie[] getCookies(); | 获取请求中的所有cookie |
HttpSession getSession(); | 获取Session对象 |
void setCharacterEncoding(String encoding) ; | 设置请求体字符集 |
HttpServletResponse是什么
HttpServletRequest怎么用
API | 功能解释 |
---|---|
void setStatus(int code); | 设置响应状态码 |
API | 功能解释 |
---|---|
void setHeader(String headerName, String headerValue); | 设置/修改响应头键值对 |
void setContentType(String contentType); | 设置content-type响应头及响应字符集(设置MIME类型) |
API | 功能解释 |
---|---|
PrintWriter getWriter() throws IOException; | 获得向响应体放入信息的字符输出流 |
ServletOutputStream getOutputStream() throws IOException; | 获得向响应体放入信息的字节输出流 |
void setContentLength(int length); | 设置响应体的字节长度,其实就是在设置content-length响应头 |
API | 功能解释 |
---|---|
void sendError(int code, String message) throws IOException; | 向客户端响应错误信息的方法,需要指定响应码和响应信息 |
void addCookie(Cookie cookie); | 向响应体中增加cookie |
void setCharacterEncoding(String encoding); | 设置响应体字符集 |
MIME类型
文件拓展名 | MIME类型 |
---|---|
.html | text/html |
.css | text/css |
.js | application/javascript |
.png /.jpeg/.jpg/… … | image/jpeg |
.mp3/.mpe/.mpeg/ … … | audio/mpeg |
.mp4 | video/mp4 |
.m1v/.m1v/.m2v/.mpe/… … | video/mpeg |
什么是请求转发和响应重定向
请求转发和响应重定向是web应用中间接访问项目资源的两种手段,也是Servlet控制页面跳转的两种手段
请求转发通过HttpServletRequest实现,响应重定向通过HttpServletResponse实现
请求转发生活举例: 张三找李四借钱,李四没有,李四找王五,让王五借给张三
响应重定向生活举例:张三找李四借钱,李四没有,李四让张三去找王五,张三自己再去找王五借钱
请求转发运行逻辑图
请求转发特点(背诵)
请求转发测试代码
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求转发器
// 转发给servlet ok
RequestDispatcher requestDispatcher = req.getRequestDispatcher("servletB");
// 转发给一个视图资源 ok
//RequestDispatcher requestDispatcher = req.getRequestDispatcher("welcome.html");
// 转发给WEB-INF下的资源 ok
//RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
// 转发给外部资源 no
//RequestDispatcher requestDispatcher = req.getRequestDispatcher("http://www.atguigu.com");
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 向请求域中添加数据
req.setAttribute("reqKey","requestMessage");
// 做出转发动作
requestDispatcher.forward(req,resp);
}
}
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 获取请求域中的数据
String reqMessage = (String)req.getAttribute("reqKey");
System.out.println(reqMessage);
// 做出响应
resp.getWriter().write("servletB response");
}
}
http://localhost:8080/web03_war_exploded/servletA?username=atguigu
响应重定向运行逻辑图
响应重定向特点(背诵)
响应重定向测试代码
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 向请求域中添加数据
req.setAttribute("reqKey","requestMessage");
// 响应重定向
// 重定向到servlet动态资源 OK
resp.sendRedirect("servletB");
// 重定向到视图静态资源 OK
//resp.sendRedirect("welcome.html");
// 重定向到WEB-INF下的资源 NO
//resp.sendRedirect("WEB-INF/views/view1");
// 重定向到外部资源
//resp.sendRedirect("http://www.atguigu.com");
}
}
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求参数
String username = req.getParameter("username");
System.out.println(username);
// 获取请求域中的数据
String reqMessage = (String)req.getAttribute("reqKey");
System.out.println(reqMessage);
// 做出响应
resp.getWriter().write("servletB response");
}
}
http://localhost:8080/web03_war_exploded/servletA?username=atguigu
乱码问题产生的根本原因是什么
各个字符集的兼容性
设置项目文件的字符集要使用一个支持中文的字符集
当前视图文件的字符集通过 来告知浏览器通过什么字符集来解析当前文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
中文
</body>
</html>
在tomcat10.1.7这个版本中,修改 tomcat/conf/logging.properties中,所有的UTF-8为GBK即可
sout乱码问题,设置JVM加载.class文件时使用UTF-8字符集
GET请求方式乱码分析
GET请求方式乱码演示
GET请求方式乱码解决
POST请求方式乱码分析
POST方式乱码演示
POST请求方式乱码解决
响应乱码分析
响应乱码演示
响应乱码解决
方式1 : 手动设定浏览器对本次响应体解析时使用的字符集(不推荐)
方式2: 后端通过设置响应体的字符集和浏览器解析响应体的默认字符集一致(不推荐)
方式3: 通过设置content-type响应头,告诉浏览器以指定的字符集解析响应体(推荐)
相对路径和绝对路径
相对路径
绝对路径
应用场景
前端项目结构
相对路径情况1:web/index.html中引入web/static/img/logo.png
<img src="static/img/logo.png"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="static/img/logo.png">
</body>
</html>
相对路径情况2:web/a/b/c/test.html中引入web/static/img/logo.png
<img src="../../../static/img/logo.png"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- ../代表上一层路径 -->
<img src="../../../static/img/logo.png">
</body>
</html>
相对路径情况3:web/WEB-INF/views/view1.html中引入web/static/img/logo.png
@WebServlet("/view1Servlet")
public class View1Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
requestDispatcher.forward(req,resp);
}
}
<img src="static/img/logo.png"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="static/img/logo.png">
</body>
</html>
绝对路径情况1:web/index.html中引入web/static/img/logo.png
<img src="/web03_war_exploded/static/img/logo.png"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 绝对路径写法 -->
<img src="/web03_war_exploded/static/img/logo.png">
</body>
</html>
绝对路径情况2:web/a/b/c/test.html中引入web/static/img/logo.png
<img src="/web03_war_exploded/static/img/logo.png"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 绝对路径写法 -->
<img src="/web03_war_exploded/static/img/logo.png">
</body>
</html>
绝对路径情况3:web/WEB-INF/views/view1.html中引入web/static/img/logo.png
@WebServlet("/view1Servlet")
public class View1Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
requestDispatcher.forward(req,resp);
}
}
<img src="/web03_war_exploded/static/img/logo.png"/>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="/web03_war_exploded/static/img/logo.png">
</body>
</html>
base标签定义页面相对路径公共前缀
index.html 和a/b/c/test.html 以及view1Servlet 中的路径处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--定义相对路径的公共前缀,将相对路径转化成了绝对路径-->
<base href="/web03_war_exploded/">
</head>
<body>
<img src="static/img/logo.png">
</body>
</html>
项目上下文路径变化问题
解决方案
目标 :由/x/y/z/servletA重定向到a/b/c/test.html
@WebServlet("/x/y/z/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
@WebServlet("/x/y/z/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 相对路径重定向到test.html
resp.sendRedirect("../../../a/b/c/test.html");
}
}
访问ServletA的url为 : http://localhost:8080/web03_war_exploded/x/y/z/servletA
绝对路径的基准路径为 : http://localhost:8080
要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/a/b/c/test.html
ServletA重定向的路径 : /web03_war_exploded/a/b/c/test.html
寻找方式就是在基准路径(http://localhost:8080)后面拼接(/web03_war_exploded/a/b/c/test.html),得到( http://localhost:8080/web03_war_exploded/a/b/c/test.html)正是目标资源访问的正确路径
绝对路径中需要填写项目上下文路径,但是上下文路径是变换的
//绝对路径中,要写项目上下文路径
//resp.sendRedirect("/web03_war_exploded/a/b/c/test.html");
// 通过ServletContext对象动态获取项目上下文路径
//resp.sendRedirect(getServletContext().getContextPath()+"/a/b/c/test.html");
// 缺省项目上下文路径时,直接以/开头即可
resp.sendRedirect("/a/b/c/test.html");
目标 :由x/y/servletB请求转发到a/b/c/test.html
@WebServlet("/x/y/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
访问ServletB的url为 : http://localhost:8080/web03_war_exploded/x/y/servletB
当前资源为 : servletB
当前资源的所在路径为 : http://localhost:8080/web03_war_exploded/x/x/
要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/a/b/c/test.html
ServletA请求转发路径 : …/…/a/b/c/test/html
寻找方式就是在当前资源所在路径(http://localhost:8080/web03_war_exploded/x/y/)后拼接(…/…/a/b/c/test/html),形成(http://localhost:8080/web03_war_exploded/x/y/…/…/a/b/c/test/html)每个…/抵消一层目录,正好是目标资源正常获取的url(http://localhost:8080/web03_war_exploded/a/b/c/test/html)
@WebServlet("/x/y/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher requestDispatcher = req.getRequestDispatcher("../../a/b/c/test.html");
requestDispatcher.forward(req,resp);
}
}
请求转发只能转发到项目内部的资源,其绝对路径无需添加项目上下文路径
请求转发绝对路径的基准路径相当于http://localhost:8080/web03_war_exploded
在项目上下文路径为缺省值时,也无需改变,直接以/开头即可
@WebServlet("/x/y/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/a/b/c/test.html");
requestDispatcher.forward(req,resp);
}
}
此时需要注意,请求转发是服务器行为,浏览器不知道,地址栏不变化,相当于我们访问test.html的路径为http://localhost:8080/web03_war_exploded/x/y/servletB
那么此时 test.html资源的所在路径就是http://localhost:8080/web03_war_exploded/x/y/所以test.html中相对路径要基于该路径编写,如果使用绝对路径则不用考虑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
当前资源路径是 http://localhost:8080/web03_war_exploded/x/y/servletB
当前资源所在路径是 http://localhost:8080/web03_war_exploded/x/y/
目标资源路径=所在资源路径+src属性值
http://localhost:8080/web03_war_exploded/x/y/../../static/img/logo.png
http://localhost:8080/web03_war_exploded/static/img/logo.png
得到目标路径正是目标资源的访问路径
-->
<img src="../../static/img/logo.png">
</body>
</html>
MVC(Model View Controller)是软件工程中的一种**
软件架构模式
,它把软件系统分为模型
、视图
和控制器
**三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
M:Model 模型层,具体功能如下
V:View 视图层,具体功能如下
C:Controller 控制层,具体功能如下
MVC模式下,项目中的常见包
M:
C:
V:
非前后端分离的MVC
前后端分离的MVC
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- 创建日程表
-- ----------------------------
DROP TABLE IF EXISTS `sys_schedule`;
CREATE TABLE `sys_schedule` (
`sid` int NOT NULL AUTO_INCREMENT,
`uid` int NULL DEFAULT NULL,
`title` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`completed` int(1) NULL DEFAULT NULL,
PRIMARY KEY (`sid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- 插入日程数据
-- ----------------------------
-- ----------------------------
-- 创建用户表
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`uid` int NOT NULL AUTO_INCREMENT,
`username` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`user_pwd` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`uid`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- 插入用户数据
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'zhangsan', 'e10adc3949ba59abbe56e057f20f883e');
INSERT INTO `sys_user` VALUES (2, 'lisi', 'e10adc3949ba59abbe56e057f20f883e');
SET FOREIGN_KEY_CHECKS = 1;
使用lombok处理getter setter equals hashcode 构造器
//-----------------------------------------------------
package com.atguigu.schedule.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SysUser implements Serializable {
private Integer uid;
private String username;
private String userPwd;
}
//------------------------------------------------------
package com.atguigu.schedule.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SysSchedule implements Serializable {
private Integer sid;
private Integer uid;
private String title;
private Integer completed;
}
//------------------------------------------------------
导入JDBCUtil连接池工具类并准备jdbc.properties配置文件
package com.atguigu.schedule.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtil {
private static ThreadLocal<Connection> threadLocal =new ThreadLocal<>();
private static DataSource dataSource;
// 初始化连接池
static{
// 可以帮助我们读取.properties配置文件
Properties properties =new Properties();
InputStream resourceAsStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
properties.load(resourceAsStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*1 向外提供连接池的方法*/
public static DataSource getDataSource(){
return dataSource;
}
/*2 向外提供连接的方法*/
public static Connection getConnection(){
Connection connection = threadLocal.get();
if (null == connection) {
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
threadLocal.set(connection);
}
return connection;
}
/*定义一个归还连接的方法 (解除和ThreadLocal之间的关联关系) */
public static void releaseConnection(){
Connection connection = threadLocal.get();
if (null != connection) {
threadLocal.remove();
// 把连接设置回自动提交的连接
try {
connection.setAutoCommit(true);
// 自动归还到连接池
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/schedule_system
username=root
password=root
initialSize=5
maxActive=10
maxWait=1000
package com.atguigu.schedule.dao;
import com.atguigu.schedule.util.JDBCUtil;
import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class BaseDao {
// 公共的查询方法 返回的是单个对象
public <T> T baseQueryObject(Class<T> clazz, String sql, Object ... args) {
T t = null;
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
// 执行 查询
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
t = (T) resultSet.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
JDBCUtil.releaseConnection();
}
return t;
}
// 公共的查询方法 返回的是对象的集合
public <T> List<T> baseQuery(Class clazz, String sql, Object ... args){
List<T> list =new ArrayList<>();
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement=null;
ResultSet resultSet =null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
}
// 执行 查询
resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
// 将结果集通过反射封装成实体类对象
while (resultSet.next()) {
// 使用反射实例化对象
Object obj =clazz.getDeclaredConstructor().newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
Object value = resultSet.getObject(columnName);
// 处理datetime类型字段和java.util.Data转换问题
if(value.getClass().equals(LocalDateTime.class)){
value= Timestamp.valueOf((LocalDateTime) value);
}
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj,value);
}
list.add((T)obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null !=resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
return list;
}
// 通用的增删改方法
public int baseUpdate(String sql,Object ... args) {
// 获取连接
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement=null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
}
// 执行 增删改 executeUpdate
rows = preparedStatement.executeUpdate();
// 释放资源(可选)
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
// 返回的是影响数据库记录数
return rows;
}
}
//---------------------------------------------------
package com.atguigu.schedule.dao;
public interface SysUserDao {
}
//---------------------------------------------------
package com.atguigu.schedule.dao;
public interface SysScheduleDao {
}
//---------------------------------------------------
//------------------------------------------------------------------------------
package com.atguigu.schedule.dao.impl;
import com.atguigu.schedule.dao.BaseDao;
import com.atguigu.schedule.dao.SysUserDao;
public class SysUserDaoImpl extends BaseDao implements SysUserDao {
}
//------------------------------------------------------------------------------
package com.atguigu.schedule.dao.impl;
import com.atguigu.schedule.dao.BaseDao;
import com.atguigu.schedule.dao.SysScheduleDao;
public class SysScheduleDaoImpl extends BaseDao implements SysScheduleDao {
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
package com.atguigu.schedule.service;
public interface SysUserService {
}
//------------------------------------------------------------------------------
package com.atguigu.schedule.service;
public interface SysScheduleService {
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
package com.atguigu.schedule.service.impl;
import com.atguigu.schedule.service.SysUserService;
public class SysUserServiceImpl implements SysUserService {
}
//------------------------------------------------------------------------------
package com.atguigu.schedule.service.impl;
import com.atguigu.schedule.service.SysScheduleService;
public class SysScheduleServiceImpl implements SysScheduleService {
}
//------------------------------------------------------------------------------
package com.atguigu.schedule.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
public class BaseController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestURI = req.getRequestURI();
String[] split = requestURI.split("/");
String methodName =split[split.length-1];
// 通过反射获取要执行的方法
Class clazz = this.getClass();
try {
Method method=clazz.getDeclaredMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
// 设置方法可以访问
method.setAccessible(true);
// 通过反射执行代码
method.invoke(this,req,resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//----------------------------------------------------------------------------
package com.atguigu.schedule.controller;
import jakarta.servlet.annotation.WebServlet;
@WebServlet("/user/*")
public class UserController extends BaseController{
}
//----------------------------------------------------------------------------
package com.atguigu.schedule.controller;
import jakarta.servlet.annotation.WebServlet;
@WebServlet("/schedule/*")
public class SysScheduleController extends BaseController{
}
//----------------------------------------------------------------------------
package com.atguigu.schedule.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class MD5Util {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!!")
}
}
}
package com.atguigu.schedule.controller;
import com.atguigu.schedule.pojo.SysUser;
import com.atguigu.schedule.service.SysUserService;
import com.atguigu.schedule.service.impl.SysUserServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/user/*")
public class SysUserController extends BaseContoller {
private SysUserService userService =new SysUserServiceImpl();
/**
* 接收用户注册请求的业务处理方法( 业务接口 不是java中的interface )
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1 接收客户端提交的参数
String username = req.getParameter("username");
String userPwd = req.getParameter("userPwd");
// 2 调用服务层方法,完成注册功能
//将参数放入一个SysUser对象中,在调用regist方法时传入
SysUser sysUser =new SysUser(null,username,userPwd);
int rows =userService.regist(sysUser);
// 3 根据注册结果(成功 失败) 做页面跳转
if(rows>0){
resp.sendRedirect("/registSuccess.html");
}else{
resp.sendRedirect("/registFail.html");
}
}
}
package com.atguigu.schedule.service;
import com.atguigu.schedule.pojo.SysUser;
public interface SysUserService {
/**
* 用户完成注册的业务方法
* @param registUser 用于保存注册用户名和密码的对象
* @return 注册成功返回>0的整数,否则返回0
*/
int regist(SysUser registUser);
}
package com.atguigu.schedule.service.impl;
import com.atguigu.schedule.dao.SysUserDao;
import com.atguigu.schedule.dao.impl.SysUserDaoImpl;
import com.atguigu.schedule.pojo.SysUser;
import com.atguigu.schedule.service.SysUserService;
import com.atguigu.schedule.util.MD5Util;
public class SysUserServiceImpl implements SysUserService {
private SysUserDao userDao =new SysUserDaoImpl();
@Override
public int regist(SysUser sysUser) {
// 将用户的明文密码转换为密文密码
sysUser.setUserPwd(MD5Util.encrypt(sysUser.getUserPwd()));
// 调用DAO 层的方法 将sysUser信息存入数据库
return userDao.addSysUser(sysUser);
}
}
package com.atguigu.schedule.dao;
import com.atguigu.schedule.pojo.SysUser;
public interface SysUserDao {
/**
* 向数据库中增加一条用户记录的方法
* @param sysUser 要增加的记录的username和user_pwd字段以SysUser实体类对象的形式接收
* @return 增加成功返回1 增加失败返回0
*/
int addSysUser(SysUser sysUser);
}
package com.atguigu.schedule.dao.impl;
import com.atguigu.schedule.dao.BaseDao;
import com.atguigu.schedule.dao.SysUserDao;
import com.atguigu.schedule.pojo.SysUser;
public class SysUserDaoImpl extends BaseDao implements SysUserDao {
@Override
public int addSysUser(SysUser sysUser) {
String sql ="insert into sys_user values(DEFAULT,?,?)";
return baseUpdate(sql,sysUser.getUsername(),sysUser.getUserPwd());
}
}
package com.atguigu.schedule.controller;
import com.atguigu.schedule.pojo.SysUser;
import com.atguigu.schedule.service.SysUserService;
import com.atguigu.schedule.service.impl.SysUserServiceImpl;
import com.atguigu.schedule.util.MD5Util;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/user/*")
public class SysUserController extends BaseContoller {
private SysUserService userService =new SysUserServiceImpl();
/**
* 接收用登录请求,完成的登录业务接口
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 接收用户名和密码
String username = req.getParameter("username");
String userPwd = req.getParameter("userPwd");
//2 调用服务层方法,根据用户名查询用户信息
SysUser loginUser =userService.findByUsername(username);
if(null == loginUser){
// 跳转到用户名有误提示页
resp.sendRedirect("/loginUsernameError.html");
}else if(! MD5Util.encrypt(userPwd).equals(loginUser.getUserPwd())){
//3 判断密码是否匹配
// 跳转到密码有误提示页
resp.sendRedirect("/loginUserPwdError.html");
}else{
//4 跳转到首页
resp.sendRedirect("/showSchedule.html");
}
}
}
package com.atguigu.schedule.service;
import com.atguigu.schedule.pojo.SysUser;
public interface SysUserService {
/**
* 根据用户名获得完整用户信息的方法
* @param username 要查询的用户名
* @return 如果找到了返回SysUser对象,找不到返回null
*/
SysUser findByUsername(String username);
}
package com.atguigu.schedule.service.impl;
import com.atguigu.schedule.dao.SysUserDao;
import com.atguigu.schedule.dao.impl.SysUserDaoImpl;
import com.atguigu.schedule.pojo.SysUser;
import com.atguigu.schedule.service.SysUserService;
import com.atguigu.schedule.util.MD5Util;
public class SysUserServiceImpl implements SysUserService {
private SysUserDao userDao =new SysUserDaoImpl();
@Override
public SysUser findByUsername(String username) {
// 调用服务层方法,继续查询
return userDao.findByUsername(username);
}
}
package com.atguigu.schedule.dao;
import com.atguigu.schedule.pojo.SysUser;
public interface SysUserDao {
/**
* 根据用户名获得完整用户信息的方法
* @param username 要查询的用户名
* @return 如果找到了返回SysUser对象,找不到返回null
*/
SysUser findByUsername(String username);
}
import com.atguigu.schedule.dao.BaseDao;
import com.atguigu.schedule.dao.SysUserDao;
import com.atguigu.schedule.pojo.SysUser;
import java.util.List;
public class SysUserDaoImpl extends BaseDao implements SysUserDao {
@Override
public SysUser findByUsername(String username) {
String sql ="select uid,username, user_pwd userPwd from sys_user where username = ?";
List<SysUser> userList = baseQuery(SysUser.class, sql, username);
return null != userList&& userList.size()>0? userList.get(0):null;
}
}