【JavaWeb】JavaWeb笔记 Servlet
文章目录
- 简介
- 动态资源和静态资源
- Servlet简介
- Servlet 运行过程
- Servlet 开发流程
- servlet-api.jar包 导入问题
- Content-Type响应头问题
- Servlet注解方式配置
- @WebServlet注解使用
- @WebServlet注解源码
- Servlet生命周期
- 生命周期简介
- 测试生命周期的代码
- 在xml中设置容器启动时就实例化Servlet
- 总结
- Servlet继承结构
- Servlet 接口
- GenericServlet 抽象类(侧重除了service方法的其他方法的处理)
- HttpServlet 抽象类(侧重service方法的处理)
- 自定义 Servlet
- ServletConfig和ServletContext
- ServletConfig(了解)
- ServletConfig简介
- ServletConfig API
- ServletConfig的使用
- ServletContext(熟练掌握)
- ServletContext简介
- ServletContext使用
- ServletContext其他重要API
- HttpServletRequest
- 简介
- 常见API
- HttpServletResponse
- 简介
- 常见API
- MIME类型
- 请求转发和响应重定向
- 简介
- 请求转发
- 请求转发特点(背诵)
- 请求转发测试代码
- 响应重定向
- 响应重定向特点(背诵)
- 响应重定向测试代码
- web乱码和路径问题
- 乱码问题
- 乱码问题产生的根本原因
- 各个字符集的兼容性
- 乱码问题分类(需要解决时自行查找)
- 请求乱码问题
- GET请求乱码
- 原因
- 解决
- POST请求乱码
- 原因
- 解决
- 响应乱码问题
- 原因
- 解决
- 路径问题
- 相对路径和绝对路径
- 前端路径问题
- 相对路径情况分析
- 绝对路径情况分析
- base标签的使用——解决绝对路径的缺点(方式:将相对路径转化为绝对路径)
- ==缺省项目上下文路径==
- 重定向中的路径问题(几乎和前端路径问题相同)
- 相对路径写法
- 绝对路径写法
- 请求转发中的路径问题
- 相对路径写法
- 绝对路径写法
- MVC架构模式
- 开闭原则的好处
- 实现方式
- 示例
简介
动态资源和静态资源
- 静态资源:无需在程序运行时通过代码运行生成的资源,在程序运行之前就写好的资源. 例如:html css js img ,音频文件和视频文件
- 动态资源:需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据,运行时动态生成,例如Servlet,Thymeleaf … …动态资源指的不是视图上的动画效果或者是简单的人机交互效果
Servlet简介
- 从代码层面上来讲,Servlet就是一个接口
- 用来接收、处理客户端请求、响应给浏览器的动态资源。在整个Web应用中,Servlet主要负责接收处理请求、协同调度功能以及响应数据。我们可以把Servlet称为Web应用中的控制器
- 不是所有的JAVA类都能用于处理客户端请求,能处理客户端请求并做出响应的一套技术标准就是Servlet
- Servlet是运行在服务端的,所以 Servlet必须在WEB项目中开发且在Tomcat这样的服务容器中运行
Servlet 运行过程
Servlet 开发流程
-
创建 JavaWeb 项目,同时将Tomcat添加为当前项目的依赖
-
重写 service 方法
-
在 service 方法中,定义业务处理代码
- 从 request 对象中获取请求中的任何信息(参数)
- 处理业务的代码
- 将要响应的数据放入 response
public class UserServlet extends HttpServlet {@Overrideprotected 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");}} }
- 自定义一个类,要继承HttpServlet类
- 重写service方法,该方法主要就是用于处理用户请求的服务方法
- HttpServletRequest 代表请求对象,是有请求报文经过tomcat转换而来的,通过该对象可以获取请求中的信息
- HttpServletResponse 代表响应对象,该对象会被tomcat转换为响应的报文,通过该对象可以设置响应中的信息
- Servlet对象的生命周期(创建,初始化,处理服务,销毁)是由tomcat管理的,无需我们自己new
- HttpServletRequest HttpServletResponse 两个对象也是有tomcat负责转换,在调用service方法时传入给我们用的
-
配置 xml 文件:配置Servlet类,并起一个别名
- servlet-class 告诉Tomcat对应的要实例化的Servlet类
- servlet-name 用于关联请求的映射路径
<?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><!-- 这里写处理送来的数据报的service所在的类名 --><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>
- Servlet并不是文件系统中实际存在的文件或者目录,所以为了能够请求到该资源,我们需要为其配置映射路径
- servlet的请求映射路径配置在web.xml中
- servlet-name作为servlet的别名,可以自己随意定义,见名知意就好
- url-pattern标签用于定义Servlet的请求映射路径
- 一个servlet可以对应多个不同的url-pattern
- 多个servlet不能使用相同的url-pattern
- url-pattern中可以使用一些通配写法
- / 表示通配所有资源,不包括jsp文件
- /* 表示通配所有资源,包括jsp文件
- /a/* 匹配所有以a前缀的映射路径
- *.action 匹配所有以action为后缀的映射路径
servlet-api.jar包 导入问题
在进行servlet开发时,不需要导入servlet-api.jar包,原因是其只是在编码的时候需要,运行的时候,在服务器的环境中,由服务软件(Tomcat)提供。因此,在JavaWeb项目中,在打包/构建的时候,无需携带这个 jar 包
Content-Type响应头问题
MIME类型(媒体类型,文件类型,响应的数据类型)的响应头,告诉客户端响应的数据是什么类型的数据,客户端以此类型决定用什么方式解析响应体,这个响应头中的Content-Type就指明了这个类型,如Content-Type = “text/html” 说明其是html文件
在处理业务的代码时,应该设置Content-Type响应头
response.setHeader("Content-Type","text/html");
//或者:
response.setContentType("text/html");
Servlet注解方式配置
@WebServlet注解使用
使用@WebServlet注解替换Servlet配置
@WebServlet(name = "userServlet", //别名//value = "/user",urlPatterns = {"/userServlet1","/userServlet2","/userServlet"}, //配置的url-patterninitParams = {@WebInitParam(name = "encoding",value = "UTF-8")}, //初始化参数loadOnStartup = 6 //加载时实例化,优先级为6
)
public class UserServlet extends HttpServlet {@Overrideprotected 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");}}
}
@WebServlet注解源码
通过查看注解的源码,可以了解到这个注解是怎么替代xml文件的配置的
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 "";}
Servlet生命周期
生命周期简介
-
生命周期,就是对象在容器中从开始创建到销毁的过程
-
Servlet对象是Servlet容器创建的,生命周期方法都是由容器(目前使用的是Tomcat)调用的
-
Servlet主要的生命周期执行特点
生命周期 对应方法 执行时机 执行次数 构造对象 构造器 第一次请求或者容器启动 1 初始化 init() 构造完毕后 1 处理服务 service(HttpServletRequest req,HttpServletResponse resp) 每次请求 多次 销毁 destory() 容器关闭 1
测试生命周期的代码
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("构造器");}@Overridepublic void init() throws ServletException {System.out.println("初始化方法");}@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("service方法");}@Overridepublic void destroy() {System.out.println("销毁方法");}
}
在xml中设置容器启动时就实例化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>
总结
- 通过生命周期测试我们发现Servlet对象在容器中是单例的
- 容器是可以处理并发的用户请求的,每个请求在容器中都会开启一个线程
- 多个线程可能会使用相同的Servlet对象,所以在Servlet中,不要轻易定义一些容易经常发生修改的成员变量
- load-on-startup中定义的正整数表示实例化顺序,如果数字重复了,容器会自行解决实例化顺序问题,但是应该避免重复
- Tomcat容器中,已经定义了一些随系统启动实例化的servlet,自定义的servlet的load-on-startup尽量不要占用数字1-5
Servlet继承结构
在idea可以追源码。追踪HttpServlet
,可以知道其继承了GenericServlet
,追踪GenericServlet
,知道其实现了Servlet
、ServletConfig
、Serializable
接口
Servlet 接口
Servlet 接口是 Servlet功能的顶级接口、规范接口,所有的Servlet必须实现以下功能:
- public void init(ServletConfig config) throws ServletException;
- 初始化方法,容器在构造servlet对象后,自动调用的方法,容器负责实例化一个ServletConfig对象,并在调用该方法时传入
- ServletConfig对象可以为Servlet 提供初始化参数
- public ServletConfig getServletConfig();
- 获取ServletConfig对象的方法,后续可以通过该对象获取Servlet初始化参数
- public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
- 处理请求并做出响应的服务方法,每次请求产生时由容器调用
- 容器创建一个ServletRequest对象和ServletResponse对象,容器在调用service方法时,传入这两个对象
- public String getServletInfo();
- 获取ServletInfo信息的方法
- public void destroy();
- Servlet实例在销毁之前调用的方法
GenericServlet 抽象类(侧重除了service方法的其他方法的处理)
GenericServlet 抽象类是对Servlet接口一些固定功能的粗糙实现,以及对service方法的再次抽象声明,并定义了一些其他相关功能方法
- private transient ServletConfig config;
- 初始化配置对象作为属性
- public GenericServlet() { }
- 构造器,为了满足继承而准备
- public void destroy() { }
- 销毁方法的平庸实现:将抽象方法重写为普通方法,在方法内部没有任何实现代码
- public String getInitParameter(String name)
- 获取初始参数的快捷方法
- public Enumeration getInitParameterNames()
- 返回所有初始化参数名的方法
- public ServletConfig getServletConfig()
- 获取初始Servlet初始配置对象ServletConfig的方法
- public ServletContext getServletContext()
- 获取上下文对象ServletContext的方法
- public String getServletInfo()
- 获取Servlet信息的平庸实现
- public void init(ServletConfig config) throws ServletException()
- 初始化方法的实现,并在此调用了init的重载方法
- Tomcat在调用init方法时,会读取配置信息进入一个ServletConfig对象并将该对象传入init方法
- public void init() throws ServletException
- 重载init方法,为了让我们自己定义初始化功能的方法
- 这个是我们另外重写的init方法(无参),因为如果重写上一个有参的init方法,还需要处理ServletConfig对象,比较麻烦
- public void log(String msg)
- public void log(String message, Throwable t)
- 打印日志的方法及重载
- public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
- 服务方法再次抽象声明
- public String getServletName()
- 获取ServletName的方法
HttpServlet 抽象类(侧重service方法的处理)
abstract class HttpServlet extends GenericServlet HttpServlet抽象类,除了基本的实现以外,增加了更多的基础功能
- private static final String METHOD_DELETE = “DELETE”;
- private static final String METHOD_HEAD = “HEAD”;
- private static final String METHOD_GET = “GET”;
- private static final String METHOD_OPTIONS = “OPTIONS”;
- private static final String METHOD_POST = “POST”;
- private static final String METHOD_PUT = “PUT”;
- private static final String METHOD_TRACE = “TRACE”;
- 上述属性用于定义常见请求方式名常量值
- public HttpServlet() {}
- 构造器,用于处理继承
- public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
- 对服务方法的实现
- 在该方法中,将请求和响应对象转换成对应HTTP协议的HttpServletRequest HttpServletResponse对象
- 调用重载的service方法
- public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
- 重载的service方法,被重写的service方法所调用
- 在该方法中,通过请求方式判断,调用具体的
do***
方法完成请求的处理**(故意响应405——请求方式不允许)**
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- 对应不同请求方式的处理方法
- 除了doOptions和doTrace方法,其他的do*** 方法都在故意响应错误信息
- 如果继承HttpServlet时没有重写service方法或者
do***
方法,则会调用HttpServlet抽象类中的do***
方法,提示用户发生405错误
自定义 Servlet
自定义Servlet中(继承HttpServlet后),必须要对处理请求的方法进行重写
- 要么重写service方法
- 要么重写doGet/doPost方法
注:
- 部分人推荐在servlet中重写 do*** 方法请求,是因为HttpServlet类的service方法可能做了一些处理,如果直接重写service的话,HttpServlet中的service方法内的一些处理会失效
- 现在直接重写service方法也没有问题
- 之后使用SpringMVC框架后,无需继承HttpServlet,处理请求的方法也不是service do***
- 如果doGet和doPost方法中处理请求的代码都一样,可以让一个方法直接调用另一个方法
继承关系图解,左边为重写service方法,右边为重写do***
方法:
ServletConfig和ServletContext
ServletConfig(了解)
ServletConfig简介
ServletConfig是什么:
- 为Servlet提供初始配置参数的一种对象,每个Servlet都有自己独立唯一的ServletConfig对象
- 容器会为每个Servlet实例化一个ServletConfig对象,并通过Servlet生命周期的init方法传入给Servlet作为属性
ServletConfig API
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的使用
- 配置xml(为配置初始化内容)
- 注:也可以直接用@WebServlet注解配置,格式见前
- 注意要写在里面
<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>
- Servlet中service操作
public class ServletA extends HttpServlet {@Overrideprotected 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();// 迭代并获取参数名//hasMoreElements 判断有没有下一个元素,如果有返回true,没有返回false//nextElement 1.取出下一个元素 2.向下移动游标while (parameterNames.hasMoreElements()) {String paramaterName = parameterNames.nextElement();System.out.println(paramaterName+":"+servletConfig.getInitParameter(paramaterName));}}
}public class ServletB extends HttpServlet {@Overrideprotected 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));}}
}
ServletContext(熟练掌握)
ServletContext简介
ServletContext是什么:
- ServletContext对象有称呼为上下文对象,或者叫应用域对象(后面统一讲解域对象)
- 容器会为每个app创建一个独立的唯一的ServletContext对象
- ServletContext对象为所有的Servlet所共享
- ServletContext可以为所有的Servlet提供初始配置参数
ServletContext使用
- xml配置
- 注意不必写在里面
- 这里的service代码已经用注解配置
<?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>
- service
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 {@Overrideprotected 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));}}
}
ServletContext其他重要API
获取资源的真实路径
String realPath = servletContext.getRealPath("资源在web目录中的路径");
- 例如我们的目标是需要获取项目中某个静态资源的路径,不是工程目录中的路径,而是部署目录中的路径;我们如果直接拷贝其在我们电脑中的完整路径的话其实是有问题的,因为如果该项目以后部署到公司服务器上的话,路径肯定是会发生改变的,所以我们需要使用代码动态获取资源的真实路径. 只要使用了servletContext动态获取资源的真实路径,那么无论项目的部署路径发生什么变化,都会动态获取项目运行时候的实际路径,所以就不会发生由于写死真实路径而导致项目部署位置改变引发的路径错误问题
获取项目的上下文路径(项目的访问路径,项目的部署路径,如 /demo01)
String contextPath = servletContext.getContextPath();
- 项目的部署名称,也叫项目的上下文路径,在部署进入tomcat时所使用的路径,该路径是可能发生变化的,通过该API动态获取项目真实的上下文路径,可以帮助我们解决一些后端页面渲染技术或者请求转发和响应重定向中的路径问题
- 后续使用绝对路径的时候,需要动态获取项目的上下文路径
域对象的相关API
- 域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同(举例:快递柜子、快递驿站、菜鸟快递、公司、大厦、两个大厦)
- ServletContext代表应用,所以ServletContext域也叫作应用域,是webapp中最大的域,可以在本应用内实现数据的共享和传递*【习惯将ServletContext的对象命名为application】*
- webapp中的三大域对象,分别是应用域,会话域,请求域
- 后续我们会将三大域对象统一进行讲解和演示,三大域对象都具有的API如下
API | 功能解释 |
---|---|
void setAttribute(String key,Object value); | 向域中存储/修改数据 |
Object getAttribute(String key); | 获得域中的数据 |
void removeAttribute(String key); | 移除域中的数据 |
HttpServletRequest
简介
- HttpServletRequest是一个接口,其父接口是ServletRequest
- HttpServletRequest是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入
- HttpServletRequest代表客户端发来的请求,所有请求中的信息都可以通过该对象获得
常见API
-
获取请求行信息相关(方式,请求的url,协议及版本)
-
注:URI 统一资源标识符 资源定位的要求和规范 动物类 如
demo01/a.html
URL 统一资源定位符 具体某个资源的路径 哺乳动物类 如
http://ip:port/demo01/a.html
URI (统一资源标识符): 这是一个通用的概念,用于唯一标识一个资源。它可以是任何字符串,只要它能够唯一地确定某个资源即可。URI不仅包括URL,还包括URN(Uniform Resource Name,统一资源名称),后者并不提供定位信息,只是简单地命名资源。
URL (统一资源定位符): 这是URI的一个子集,它不仅标识了资源,还提供了该资源在网络上的位置以及如何访问这个资源的信息。例如,一个URL会包含协议(如HTTP、HTTPS、FTP等)、服务器地址、端口号、路径等信息。
举个例子,
https://www.example.com/index.html
是一个URL,它是一个具体的URI,同时也告诉我们这个资源是一个网页,通过HTTPS协议从www.example.com
获取,并且路径是/index.html
。总结来说,所有的URL都是URI,但并不是所有的URI都是URL。URI是一个更广泛的概念,而URL则特指那些具有定位信息的URI。
用通俗的话来说:
URI 就像是一个人的名字,它是用来唯一识别某样东西的。比如“李华”这个名字可以用来识别一个人,但并不告诉你这个人在哪里。
URL 则更像是一个具体的地址,比如“北京市朝阳区某某路123号”。它不仅能告诉你这是哪个地方,还能让你知道如何去找到这个地方。
所以,URL是一种特殊的URI,它不仅告诉你“这是什么”,还告诉你“在哪里”以及“怎么去那里找到它”。当你在网上看到一个链接,可以直接点击去访问的,那通常就是一个URL。
API | 功能解释 |
---|---|
StringBuffer getRequestURL(); | 获取客户端请求的url |
String getRequestURI(); | 获取客户端请求项目中的具体资源 |
int getServerPort(); | 获取客户端发送请求时的端口(存在代理时,和所在容器端口号不同) |
int getLocalPort(); | 获取本应用在**所在容器(如Tomcat)**的端口 |
int getRemotePort(); | 获取客户端程序的端口 |
String getScheme(); | 获取请求协议 |
String getProtocol(); | 获取请求协议及版本号 |
String getMethod(); | 获取请求方式 |
- 获得请求头信息相关
API | 功能解释 |
---|---|
String getHeader(String headerName); | 根据头名称(单独)获取(某个)请求头 |
Enumeration getHeaderNames(); | 获取所有的请求头名字 |
String getContentType(); | 获取content-type请求头 |
- 获得请求参数相关(注:前四个为获取键值对形式的参数,无论其在URL后还是在请求体中)
- 注:GET方式提交的请求也有请求体,只不过同时放到了URL中
- 获得请求体中的非键值对数据?——JSON串 文件(后三个)
API | 功能解释 |
---|---|
String getParameter(String parameterName); | 根据请求参数名获取请求单个参数值 |
String[] getParameterValues(String parameterName); | 根据请求参数名获取请求多个参数值数组 |
Enumeration getParameterNames(); | 获取所有请求参数名(通过这种方式输出时,最好直接用String[] getParameterValues(String parameterName); ,因为不知道这个参数有几个值) |
Map<String, String[]> getParameterMap(); | 获取所有请求参数的键值对集合 |
BufferedReader getReader() throws IOException; | 获取读取请求体的字符输入流 |
ServletInputStream getInputStream() throws IOException; | 获取读取请求体的字节输入流 |
int getContentLength(); | 获得请求体长度的字节数 |
- 其他API
API | 功能解释 |
---|---|
String getServletPath(); | 获取请求的Servlet的映射路径 |
ServletContext getServletContext(); | 获取ServletContext对象 |
Cookie[] getCookies(); | 获取请求中的所有cookie |
HttpSession getSession(); | 获取Session对象 |
void setCharacterEncoding(String encoding) ; | 设置请求体字符集 |
HttpServletResponse
简介
- HttpServletResponse是一个接口,其父接口是ServletResponse
- HttpServletResponse是Tomcat预先创建的,在Tomcat调用service方法时传入
- HttpServletResponse代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对象我们可以设置响应信息
常见API
- 设置响应行相关
API | 功能解释 |
---|---|
void setStatus(int code); | 设置响应状态码(200/404/405/…) |
- 设置响应头相关
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
API | 功能解释 |
---|---|
void sendError(int code, String message) throws IOException; | 向客户端响应错误信息的方法,需要指定响应码和响应信息 |
void addCookie(Cookie cookie); | 向响应体中增加cookie |
void setCharacterEncoding(String encoding); | 设置响应体字符集 |
MIME类型
- MIME类型,可以理解为文档类型**,用户表示传递的数据是属于什么类型的文档**
- 浏览器可以根据MIME类型决定该用什么样的方式解析接收到的响应体数据
- 可以这样理解: 前后端交互数据时,告诉对方发给对方的是 html/css/js/图片/声音/视频/… …
- tomcat/conf/web.xml中配置了常见文件的拓展名和MIMIE类型的对应关系
- 常见的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实现
-
请求转发生活举例: 张三找李四借钱,李四没有,李四找王五,让王五借给张三(张三不知道李四找了王五,还以为是李四借给他钱)
-
响应重定向生活举例:张三找李四借钱,李四没有,李四让张三去找王五,张三自己再去找王五借钱
请求转发
请求转发特点(背诵)
- 请求转发通过HttpServletRequest对象的getRequestDispatcher方法获取请求转发器实现
- 请求转发是服务器内部的行为,对客户端是屏蔽的
- 客户端只发送了一次请求,客户端地址栏不变
- 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源
- 因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
- 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源(html页面)以实现页面跳转
- 请求转发可以转发给WEB-INF下受保护的资源,也是访问WEB-INF资源的唯一方式(不是客户端直接访问)
- 请求转发不能转发到本项目以外的外部资源,即使是的URL(www.baidu.com)
请求转发测试代码
- ServletA
@WebServlet("/servletA")
public class ServletA extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 获取请求转发器// 转发给servlet okRequestDispatcher 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);}
}
- ServletB
@WebServlet("/servletB")
public class ServletB extends HttpServlet {@Overrideprotected 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"); }
}
响应重定向
响应重定向特点(背诵)
- 响应重定向通过HttpServletResponse对象的sendRedirect方法实现
- 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的,客户端的行为
- 客户端至少发送了两次请求,客户端地址栏是要变化的
- 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
- 因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递
- 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
- 重定向不可以到WEB-INF下受保护的资源(原理是客户端直接访问受保护的资源,这是不可以的)
- 重定向可以到本项目以外的外部资源
响应重定向测试代码
-
ServletA
@WebServlet("/servletA") public class ServletA extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 获取请求参数String username = req.getParameter("username");System.out.println(username);// 向请求域中添加数据req.setAttribute("reqKey","requestMessage");// 响应重定向// 重定向到servlet动态资源 OKresp.sendRedirect("servletB");// 重定向到视图静态资源 OK//resp.sendRedirect("welcome.html");// 重定向到WEB-INF下的资源 NO//resp.sendRedirect("WEB-INF/views/view1");// 重定向到外部资源//resp.sendRedirect("http://www.atguigu.com");} }
-
ServletB
@WebServlet("/servletB") public class ServletB extends HttpServlet {@Overrideprotected 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");} }
注:如果请求转发和响应重定向都可以用,优先使用响应重定向(仅仅是页面跳转时),除非是一些特殊情况,例如要传递res、resp对象的数据,需要用请求转发
web乱码和路径问题
乱码问题
乱码问题产生的根本原因
- 数据的编码和解码使用的不是同一个字符集
- 使用了不支持某个语言文字的字符集
各个字符集的兼容性
- 由上图得知,上述字符集都兼容了ASCII
- ASCII中有什么? 英文字母和一些通常使用的符号,所以这些东西无论使用什么字符集都不会乱码
乱码问题分类(需要解决时自行查找)
- HTML乱码问题
- Tomcat控制台乱码
- sout乱码问题,设置JVM加载.class文件时使用UTF-8字符集
- 请求乱码问题
- 响应乱码问题
请求乱码问题
GET请求乱码
原因
- GET方式提交参数的方式是将参数放到URL后面,如果使用的不是UTF-8,那么会对参数进行URL编码处理
- HTML中的 影响了GET方式提交参数的URL编码
- tomcat10.1.7的URI编码默认为 UTF-8
- 当GET方式提交的参数URL编码和tomcat10.1.7默认的URI编码不一致时,就会出现乱码
解决
- 方式1 :设置GET方式提交的编码和Tomcat10.1.7的URI默认解析编码一致即可 (推荐)
- 方式2 : 设置Tomcat10.1.7的URI解析字符集和GET请求发送时所使用URL转码时的字符集一致即可,修改conf/server.xml中 Connecter 添加 URIEncoding=“GBK” (不推荐)
POST请求乱码
原因
- POST请求将参数放在请求体中进行发送
- 请求体使用的字符集受到了 的影响
- Tomcat10.1.7 默认使用UTF-8字符集对请求体进行解析
- 如果请求体的URL转码和Tomcat的请求体解析编码不一致,就容易出现乱码
解决
- 方式1 : 请求时,使用UTF-8字符集提交请求体 (推荐)
- 方式2 : 后端在获取参数前,设置解析请求体使用的字符集和请求发送时使用的字符集一致 (不推荐)
响应乱码问题
原因
- 在Tomcat10.1.7中,向响应体中放入的数据默认使用了工程编码 UTF-8
- 浏览器在接收响应信息时,使用了不同的字符集或者是不支持中文的字符集就会出现乱码
解决
-
方式1 : 手动设定浏览器对本次响应体解析时使用的字符集(不推荐)
- edge和 chrome浏览器没有提供直接的比较方便的入口,不方便
-
方式2: 后端通过设置响应体的字符集和浏览器解析响应体的默认字符集一致(不推荐)
-
方式3: 通过设置content-type响应头,告诉浏览器以指定的字符集解析响应体(推荐)【一般再设置响应体使用UFT-8编码】
路径问题
相对路径和绝对路径
-
相对路径
-
相对路径的规则是: 以当前资源所在的路径为出发点去寻找目标资源
-
相对路径不以 / 开头
-
在file协议下,使用的是磁盘路径
-
在http协议下,使用的是url路径
-
相对路径中可以使用 ./表示当前资源所在路径,可以省略不写
-
相对路径中可以使用…/表示当前资源所在路径的上一层路径,需要时要手动添加
-
缺点:目标资源路径受当前资源路径的影响,不同的位置,相对路径写法不同
-
写相对路径时,不能全部都按文件目录结构来写!因为如果存在请求转发,相对路径是从第一次请求的路径去定位,而不是转发后的路径去定位:(目标资源内相对路径处理)
-
此时需要注意,请求转发是服务器行为,浏览器不知道,地址栏不变化,相当于我们访问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.pnghttp://localhost:8080/web03_war_exploded/static/img/logo.png得到目标路径正是目标资源的访问路径 --> <img src="../../static/img/logo.png"> </body> </html>
-
-
-
绝对路径
- 绝对路径的规则是: 使用以一个固定的路径做出出发点去寻找目标资源,和当前资源所在的路径没有关系
- 绝对路径要以/ 开头
- 绝对路径的写法中,不以当前资源的所在路径为出发点,所以不会出现 ./ 和…/
- 不同的项目和不同的协议下,绝对路径的基础位置可能不同,要通过测试确定
- 绝对路径优点:无论当前资源位置在哪,寻找目标资源路径的写法都一致
- 缺点:绝对路径要补充项目的上下文,而项目上下文是可以改变的(如/demo01改为/d1,暂时解决方案:base标签的使用)
-
应用场景
- 前端代码中,href src action 等属性
- 请求转发和重定向中的路径
前端路径问题
前端项目结构:
相对路径情况分析
相对路径情况1:web/index.html中引入web/static/img/logo.png
- 访问index.html的url为 : http://localhost:8080/web03_war_exploded/index.html
- 当前资源为 : index.html
- 当前资源的所在路径为 : http://localhost:8080/web03_war_exploded/
- 要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/static/img/logo.png
- index.html中定义的了 :
<img src="static/img/logo.png"/>
- 寻找方式就是在当前资源所在路径(http://localhost:8080/web03_war_exploded/)后拼接src属性值(static/img/logo.png),正好是目标资源正常获取的url(http://localhost:8080/web03_war_exploded/static/img/logo.png)
相对路径情况2:web/a/b/c/test.html中引入web/static/img/logo.png
- 访问test.html的url为 : http://localhost:8080/web03_war_exploded/a/b/c/test.html
- 当前资源为 : test.html
- 当前资源的所在路径为 : http://localhost:8080/web03_war_exploded/a/b/c/
- 要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/static/img/logo.png
- test.html中定义的了 :
<img src="../../../static/img/logo.png"/>
- 寻找方式就是在当前资源所在路径(http://localhost:8080/web03_war_exploded/a/b/c/)后拼接src属性值(…/…/…/static/img/logo.png),其中 …/可以抵消一层路径,正好是目标资源正常获取的url(http://localhost:8080/web03_war_exploded/static/img/logo.png)
相对路径情况3:web/WEB-INF/views/view1.html中引入web/static/img/logo.png
- view1.html在WEB-INF下,需要通过Servlet请求转发获得
@WebServlet("/view1Servlet")
public class View1Servlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");requestDispatcher.forward(req,resp);}
}
- 访问view1.html的url为 : http://localhost:8080/web03_war_exploded/view1Servlet
- 当前资源为 : view1Servlet
- 当前资源的所在路径为 : http://localhost:8080/web03_war_exploded/
- 要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/static/img/logo.png
- view1.html中定义的了 :
<img src="static/img/logo.png"/>
- 寻找方式就是在当前资源所在路径(http://localhost:8080/web03_war_exploded/)后拼接src属性值(static/img/logo.png),正好是目标资源正常获取的url(http://localhost:8080/web03_war_exploded/static/img/logo.png
绝对路径情况分析
绝对路径情况1:web/index.html中引入web/static/img/logo.png
- 访问index.html的url为 : http://localhost:8080/web03_war_exploded/index.html
- 绝对路径的基准路径为 : http://localhost:8080
- 要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/static/img/logo.png
- index.html中定义的了 :
<img src="/web03_war_exploded/static/img/logo.png"/>
- 寻找方式就是在基准路径(http://localhost:8080)后面拼接src属性值(/web03_war_exploded/static/img/logo.png),得到的正是目标资源访问的正确路径
绝对路径情况2:web/a/b/c/test.html中引入web/static/img/logo.png
- 访问test.html的url为 : http://localhost:8080/web03_war_exploded/a/b/c/test.html
- 绝对路径的基准路径为 : http://localhost:8080
- 要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/static/img/logo.png
- test.html中定义的了 :
<img src="/web03_war_exploded/static/img/logo.png"/>
- 寻找方式就是在基准路径(http://localhost:8080)后面拼接src属性值(/web03_war_exploded/static/img/logo.png),得到的正是目标资源访问的正确路径
绝对路径情况3:web/WEB-INF/views/view1.html中引入web/static/img/logo.png
- view1.html在WEB-INF下,需要通过Servlet请求转发获得
@WebServlet("/view1Servlet")
public class View1Servlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");requestDispatcher.forward(req,resp);}
}
- 访问view1.html的url为 : http://localhost:8080/web03_war_exploded/view1Servlet
- 绝对路径的基准路径为 : http://localhost:8080
- 要获取的目标资源url为 : http://localhost:8080/web03_war_exploded/static/img/logo.png
- view1.html中定义的了 :
<img src="/web03_war_exploded/static/img/logo.png"/>
- 寻找方式就是在基准路径(http://localhost:8080)后面拼接src属性值(/static/img/logo.png),得到的正是目标资源访问的正确路径
base标签的使用——解决绝对路径的缺点(方式:将相对路径转化为绝对路径)
base标签定义页面相对路径公共前缀
- base 标签定义在head标签中,用于定义相对路径的公共前缀
- base 标签定义的公共前缀只在相对路径上有效,绝对路径中无效
- 如果相对路径开头有 ./ 或者…/修饰,则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>
缺省项目上下文路径
- 通过 base标签虽然解决了相对路径转绝对路径问题,但是base中定义的是项目的上下文路径
- 项目的上下文路径是可以随意变化的
- 一旦项目的上下文路径发生变化,所有base标签中的路径都需要改
- 将项目的上下文路径进行缺省设置,设置为 /,所有的绝对路径中就不必填写项目的上下文了,直接就是/开头即可
- 实际开发中,用的就是这种方法
重定向中的路径问题(几乎和前端路径问题相同)
目标 :由/x/y/z/servletA重定向到a/b/c/test.html
相对路径写法
- 访问ServletA的url为 : http://localhost:8080/web03_war_exploded/x/y/z/servletA
- 当前资源为 : servletA
- 当前资源的所在路径为 : http://localhost:8080/web03_war_exploded/x/y/z/
- 要获取的目标资源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/z/)后拼接(…/…/…/a/b/c/test/html),形成(http://localhost:8080/web03_war_exploded/x/y/z/…/…/…/a/b/c/test/html)每个…/抵消一层目录,正好是目标资源正常获取的url(http://localhost:8080/web03_war_exploded/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)正是目标资源访问的正确路径
-
绝对路径中需要填写项目上下文路径,但是上下文路径是变换的
- 可以通过 ServletContext的getContextPath()获取上下文路径
- 可以将项目上下文路径定义为 / 缺省路径,那么路径中直接以/开头即可
//绝对路径中,要写项目上下文路径 //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
相对路径写法
-
访问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)
绝对路径写法
-
请求转发只能转发到项目内部的资源,其绝对路径无需添加项目上下文路径(注意不同)
-
请求转发绝对路径的基准路径相当于http://localhost:8080/web03_war_exploded
-
在项目上下文路径为缺省值时,也无需改变,直接以/开头即可
MVC架构模式
MVC(Model View Controller)是软件工程中的一种**
软件架构模式
,它把软件系统分为模型
、视图
和控制器
**三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
项目代码不能乱放,MVC是一个放代码的规范
原则:高内聚低耦合、开闭原则
“高内聚低耦合”是软件工程和系统设计中的一个基本原则,通常用于描述模块或组件之间的关系。这个原则提倡的是模块内部应该高度相关(高内聚),而不同模块之间应该尽可能地减少相互依赖(低耦合)。
高内聚:指的是一个模块或者组件内部的功能应该是紧密相关的,也就是说模块内的各个部分共同完成一个明确的任务。这样的模块更容易理解和维护,因为它们的功能聚焦且边界清晰。例如,一个处理用户认证的模块应该只包含与认证相关的功能,如登录、注销、权限检查等,而不应该包含其他不相关的功能,比如文件管理或邮件发送等。
低耦合:意味着不同的模块之间的依赖性要尽可能地少。当一个模块发生变化时,不应该影响到其他模块。这样可以降低系统的复杂性,提高可维护性和可扩展性。为了实现低耦合,通常会使用接口或抽象类来定义模块间通信的方式,并尽量减少直接引用其他模块的具体实现。
遵循高内聚低耦合的原则有助于创建出更加健壮、易于修改和重用的软件系统。这在面向对象编程、微服务架构以及其他许多软件开发方法论中都是一个重要的指导思想。
开闭原则(Open-Closed Principle, OCP)是由著名软件工程师伯纳德·维克利(Bertrand Meyer)提出的设计原则之一,是面向对象设计的基本原则之一。开闭原则表述如下:
软件实体(类、模块、函数等)应该是可以扩展的(open for extension),但是不可以被修改的(closed for modification)。
简单来说,开闭原则要求我们在设计系统时,使得系统能够容易地适应需求的变化,通过增加新的代码来扩展原有功能,而不是修改已经存在的代码。
开闭原则的好处
- 提高可维护性:遵循开闭原则的系统更容易维护,因为对现有功能的更改不会影响到已有的代码。
- 提高可复用性:设计良好的类或模块可以在不同的上下文中复用,因为它们对外部的变化封闭。
- 提高可测试性:由于系统的一部分不会轻易改变,因此可以更方便地进行单元测试和回归测试。
实现方式
要实现开闭原则,通常需要结合其他面向对象的设计原则和技术,比如:
- 继承:通过继承基类来扩展功能。
- 多态:利用多态特性,让子类覆盖或实现父类的方法,从而改变行为。
- 接口或抽象类:定义清晰的接口或抽象类,使得具体实现可以灵活变化而不影响调用者。
- 依赖注入:通过依赖注入(DI)或依赖查找(Dependency Lookup)等方式,在运行时动态选择具体实现。
示例
假设有一个简单的系统,需要计算不同类型商品的价格。最初只有书籍,后来又增加了音乐CD。如果我们直接修改原有的处理书籍价格的代码来加入对音乐CD的支持,那么就是违反了开闭原则。相反,如果我们在设计之初就定义了一个
PriceCalculator
接口,并且为每种类型的商品都提供一个实现了该接口的具体类,那么当我们添加新的商品类型时,只需要新增相应的实现类即可,而不需要改动现有的代码。开闭原则鼓励开发者在设计时就考虑到未来可能的变化,使系统更加灵活和稳定。
-
M:Model 模型层,具体功能如下(数据增删改查和业务的处理)
- 存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的VO对象
- 存放一些对数据进行逻辑运算操作的的一些业务处理代码
-
V:View 视图层,具体功能如下
- 存放一些视图文件相关的代码 html css js等
- 在前后端分离的项目中,后端已经没有视图文件,该层次已经衍化成独立的前端项目
-
C:Controller 控制层,具体功能如下
- 接收客户端请求,获得请求数据
- 将准备好的数据响应给客户端
MVC模式下,项目中的常见包
-
M:
- 实体类包(pojo /entity /bean) 专门存放和数据库对应的实体类和一些VO对象
- 数据库访问包(dao/mapper) 专门存放对数据库不同表格CURD方法封装的一些类
- 服务包(service) 专门存放对数据进行业务逻辑运算的一些类
-
C:
- 控制层包(controller)
-
V:
- web目录下的视图资源 html css js img 等
- 前端工程化后,在后端项目中已经不存在了