Java Web
Web开发
英文中web表示“网”的意思,它用于表示web服务器(主机)供浏览器访问的资源
;web是一种基于互联网的服务,提供了一种图形化、易于访问的界面,使用户可以在互联网上查找和浏览信息。
web是由很多html网页组成的,网页由链接互相关联形成网络结构。
互联网、万维网和因特网
- 互联网:多个设备能彼此通信而组成的网络,internet;最广泛的概念。
- 因特网:是互联网中的一种,使用tcp/ip协议让不同设备进行通信,由成千上万台计算机组成的网络,Internet
- 万维网:world wide web,是基于互联网的信息服务系统,通过HTTP传输数据,由大量网页链接关联而成的网络资源。
互联网 > 因特网 > 万维网
web服务器上提供外界访问的web资源分为:
- 静态资源(html css 图片等),数据始终不变,所有的用户看到的都是相同的内容。
- 动态资源,能够根据用户交互动态生成内容
在Java Web中,Web指的是一种基于互联网的服务,使用Java技术来实现动态Web的功能。
服务器:通常是一台高性能的计算机,在网络环境下为客户端提供某种服务的专用计算机。具备强大的处理能力、高速的存储和网络连接,可以处理大量的请求并保证服务的稳定性和可靠性。
Tomcat
Tomcat是一个开源的、轻量级web应用服务器。
核心功能是接受客户端的请求,并传递给相应的Servlet进行处理,再将处理结果返回给客户端进行响应。在这个过程中tomcat有两个重要的角色:
- 连接器:tomcat监听端口接受外界请求,并将请求处理后传递给容器做业务处理。(前端和后台交互的桥梁,通过HTTP(s)服务)
- 容器:tomcat是Servlet的容器,负责存放和管理Servlet,提供了Servlet的运行环境和生命周期管理。处理Servlet的初始化、请求处理和销毁等操作。使开发者专注于实现业务逻辑。
注意:一般服务器比如 Apache、Nginx、IIS 等,它们的功能往往都比较单一,只能提供 http(s) 服务,让用户访问静态资源,它们不能执行任何编程语言,所以一般服务器只能部署静态网站;要想部署动态网站还需要一个运行环境(RunTime),以支持编程代码的运行。
部署动态网站的组件:
注:上面tomcat软件自带了web服务器模块,提供了基本的HTTP服务又有Servlet的执行环境,简化了部署流程。
Tomcat 目录:
Servlet
为什么会出现Servlet?
如果使用现有的html,css,javascript来获取后台数据库数据可以吗?Answer:不行,上面三个技术只能操作前端的静态数据,不能操作数据库。数据库是存储在服务器端的数据集合,要实现前后端数据的交互,就需要一个能够处理动态数据的技术,Servlet就是为此而生的。
Web服务器(如Tomcat、Weblogic)本身只能处理静态资源的请求,即直接返回已存在的文件内容。但当需要根据用户请求动态生成内容时(例如,从数据库中查询数据并展示在网页上),就需要Servlet这样的组件来处理这些动态资源的请求。
什么是Servlet?
Servlet = server + applet(即服务端程序),是运行在服务器上的Java程序,用来接收、处理客户端请求并动态的生成web内容返回给浏览器。
实现Servlet的三种方式
- 实现 javax.servlet.Servlet 接口,重写其全部方法。
- 继承 javax.servlet.GenericServlet 抽象类,重写 service() 方法。
(GenericServlet 是一个通用 的 Servlet 类,并没有针对某种场景进行特殊处理,尤其是 HTTP 协议,我们必须手动分析和封装 HTTP 协议的请求信息和响应信息。) - 继承 javax.servlet.http.HttpServlet 抽象类,重写 doGet() 或 doPost() 方法。(一般常用这个实现Servlet)
servlet的配置文件(web.xml):
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>net.biancheng.www.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/MyServlet</url-pattern>
</servlet-mapping>
</web-app>
说明:
<web-app>: 根元素。
<servlet> :用于注册 Servlet,即给 Servlet 起一个独一无二的名字。
<servlet> 包含两个主要的子元素 <servlet-name> 和 <servlet-class>,分别用于指定 Servlet 的名称和 Servlet 的完整限定名(包名+类名)。
<servlet-mapping> :用于定义 Servlet 与 URL 之间的映射。
<servlet-mapping> 包含两个子元素 <servlet-name> 和 <url-pattern>,分别用于指定 Servlet 的名称和虚拟路径。
- 即访问 http://localhost:8080/项目名/MyServlet 可以映射到servlet 为MyServlet的类上处理请求
Servlet注解:
- 为了简化 Servlet 的配置,Servlet 3.0 中增加了注解支持,例如:@WebServlet、@WebInitParm 、@WebFilter 和 @WebLitener 等,这使得 web.xml 从 Servlet 3.0 开始不再是必选项了。
@WebServlet(urlPatterns="/MyServlet")
public class MyServlet extend HttpServlet{
...
}
Servlet生命周期
Servlet 的生命周期就是 Servlet 从创建到销毁的过程。Servlet 的生命周期由 Servlet 容器管理,主要分为以下 3 个阶段。
初始化阶段 init()
运行时阶段 service()
销毁阶段 destory()
生命周期的作用:
资源管理和初始化:例如,在启动一个Web应用程序时,我们可以初始化数据库连接、加载配置文件等。
这些资源可以在整个应用程序生命周期内重复使用,提高应用程序的性能和效率。
日志记录和监控:通过Servlet的生命周期,我们可以方便地进行日志记录和监控。
例如,在Servlet的初始化阶段,我们可以记录应用程序启动的时间和状态;
在处理请求时,我们可以记录请求的详细信息和响应的结果;在Servlet的销毁阶段,我们可以记录应用程序关闭的时间和状态。
通过这些日志记录,我们可以监控应用程序的性能和健康状况,及时发现并解决问题。
load-on-startup
-
load-on-startup 是 servlet 元素的子元素,用来标记 Servlet 容器启动时是否初始化当前 Servlet,以及当前 Servlet 的初始化顺序。
-
load-on-startup 元素取值规则如下:
它的取值必须是一个整数;
值< 0 或者没有指定时,则表示容器在该 Servlet 被首次请求时才会被加载;
值大于 >= 0 ,表示容器在启动时就加载并初始化该 Servlet,取值越小,优先级越高;
当取值相同时,容器就会自行选择顺序进行加载。
Servlet多重路径
- 配置多个urlPatterns
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>net.biancheng.www.MyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/myServlet</url-pattern>
<url-pattern>/myServlet3</url-pattern>
</servlet-mapping>
- 注解方式使用字符串数组
@WebServlet(urlPatterns = {"/uploadFile", "/uploadFile2"})
public class MyServlet extends HttpServlet {
...
}
ServletConfig对象
Servlet容器初始化Servlet时会为该Servlet创建一个对象ServletConfig,通过该对象可以获取Servlet的初始化信息。
获取ServletConfig对象
在doGet()/doPOST()/service()方法中使用 this.getServletConfig()
获取初始化参数
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>net.biancheng.www.MyServlet</servlet-class>
<!-- Servlet 初始化参数 -->
<init-param>
<param-name>name</param-name>
<param-value>编程帮</param-value>
</init-param>
</servlet>
Enumeration<String> initParameterNames = this.getServletConfig().getInitParameterNames();
// 遍历集合获取初始化参数名称
while (initParameterNames.hasMoreElements()) {
// 获取初始化参数名称
String initParamName = initParameterNames.nextElement();
// 获取相应的初始参数的值
String initParamValue = config.getInitParameter(initParamName);
}
ServletContext对象
- Servlet 容器启动时,会为每个 Web 应用(webapps 下的每个目录都是一个 Web 应用)创建一个唯一的 ServletContext 对象,该对象一般被称为“Servlet 上下文”。
ServletContext 对象的生命周期从 Servlet 容器启动时开始,到容器关闭或应用被卸载时结束。
//获取方式
//通过HttpServletRequest
1: ServletContext servletContext = request.getServletContext();
//通过servletConfig对象获取
2: ServletContext context = this.getServletConfig().getServletContext();
//通过父类GenericServlet获取
3:ServletContext context = super.getServletContext();
//通过Session获取
4: ServletContext conxtext = request.getSession().getServletContext();
ServletContext 的应用主要有以下 3 个:
- 获取上下文初始化参数
<!--在web-app下设置全局初始化参数 -->
<context-param>
<param-name>name</param-name>
<param-value>编程帮</param-value>
</context-param>
<context-param>
<param-name>url</param-name>
<param-value>www.biancheng.net</param-value>
</context-param>
ServletContext context = super.getServletContext();
// 返回 context 上下文初始化参数的名称
Enumeration<String> initParameterNames = context.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
// 获取初始化参数名称
String initParamName = initParameterNames.nextElement();
// 获取相应的初始参数的值
String initParamValue = context.getInitParameter(initParamName);
}
- 实现不同Servlet 之间的数据通讯
ServletContext context = this.getServletConfig().getServletContext();
//将一个Java对象与name绑定,并将其作为属性保存到servletContext中
context.setAttribute(String name,Object object);
//根据name获取ServletContext中存储的值
context.getAttribute(String name);
//根据指定的name删除servletContext中的值
context.removeAttribute(String name);
demo: 编写一个统计页面访问量的案例
在某个Servlet中:
@Override
public void init() throws ServletException {
//设置页面访问次数的变量
super.getServletContext().setAttribute("visitCount",0);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Exception {
resp.setContentType("text/html;charset=utf-8");
ServletContext context = super.getServletContext();
context.setAttribute("visitCount",((int)context.getAttribute("visitCount"))+1);
resp.getWriter().write("<h1>访问次数:"+ context.getAttribute("visitCount") +"</h1>");
}
- 读取 Web 应用下的资源文件
在src/main/resources下创建db.properties配置文件
name=编程帮
url=www.biancheng.net
desc=编程帮,欢迎你
// 获取相对路径中的输入流对象
InputStream ins = getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
// 获取输入流
Properties pro = new Properties();
// 加载
pro.load(ins);
// 获取文件中的内容
String name = pro.getProperty("name");
String url = pro.getProperty("url");
String desc = pro.getProperty("desc");
HttpServletRequest和HttpServletResponse
-
Servlet 容器接收到来自客户端的 HTTP 请求后,容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletReponse 对象。
-
容器将 HttpServletRequest 对象和 HttpServletReponse 对象以参数的形式传入 service() 方法内,并调用该方法。
-
在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息。
-
请求处理完成后,将响应信息封装到 HttpServletReponse 对象中,容器再将响应信息返回给客户端。之后,HttpServletRequest 对象和 HttpServletReponse 对象被销毁。
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("请求方式:" + request.getMethod() + "<br/>" +
"客户端的 IP 地址:" + request.getRemoteAddr() + "<br/>" +
"应用名字(上下文):" + request.getContextPath() + "<br/>" +
"URI:" + request.getRequestURI() + "<br/>" +
"请求字符串:(url的?之后的内容)" + request.getQueryString() + "<br/>" +
"Servlet所映射的路径:" + request.getServletPath() + "<br/>" +
"客户端的完整主机名:" + request.getRemoteHost() + "<br/>"
);
}
HttpServletRequest获取表单请求中的参数:
<form action="/submitData" method="post">
<input type="text" name="username" /> 这里的name属性必须要有,供服务器获取该参数时使用
<input type="checkbox" name="language" value="JAVA" />JAVA
<input type="checkbox" name="language" value="C" />C语言
<select name="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
<input type="submit" value="提交" />
</form>
public class getParamServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp){
String username = req.getParameter("username"); //根据表单的name属性获取参数
String city= req.getParameter("city");
String[] languages = req.getParameter("language"); //复选框可以选中多个,所以用数组来接受数据
}
}
form表单数据中的乱码问题
根据请求的方式不同,乱码一般是get请求乱码和post请求乱码。
- get请求乱码(tomcat8以后的版本中乱码已经被解决)
tomcat没有设置字符编码时默认是ISO-8859-1格式进行url编码,所以不支持中文;
// 获取请求中的参数
String username = req.getParameter("username");
//中文乱码解决
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");
- post请求乱码问题
post请求时会将请求参数放在请求体中,一般设置中文编码utf-8,request对象接收请求以后会将数据放入request缓冲区中,缓冲区的编码格式默认为ISO-8859-1,编码不同导致乱码。
解决方法:将request缓冲区编码设置为utf-8
request.setCharacterEncoding("utf-8");
HttpServletResponse
HttpServletResponse继承至ServletResponse接口,用来封装请求处理之后的响应数据并返回给客户端。
response中的输出流可以向客户端输出数据;包含
- 字符流: PrintWriter out = response.getWriter();
out.write("<h1>hello world</h1>");
- 字节流:ServletOutoutStream out = response.getOutoutStream();
out.write("编程帮 www.biancheng.net".getBytes("UTF-8"));
用来输出二进制数据(一般在下载某个二进制文件时使用)
response中的中文乱码:
- 字符流乱码:一定导致乱码,因为通过字符流输出的数据会放在response缓冲区中,缓冲区的默认编码是ISO-8859-1,不支持中文,所以需要设置缓冲区中的编码为utf-8
response.setContentType("text/html;charset=UTF-8");
- 字节流乱码
字节流是否乱码,取决于中文转字节数组采用的编码和浏览器的编码是否一致。
response.setContentType("text/html;charset=UTF-8");
response.getOutoutStream().write("你好".getBytes("UTF-8"));
请求转发和重定向
请求转发和重定向都是用来进行页面间跳转的,但是实现跳转的方式不同:
- 请求转发(forward):
web应用在处理客户端请求时通常会需要多个资源(如Servlet)之间进行共同协作才可完成;比如用户在登陆时需要servlet验证用户名和密码是否正确,如果正确在跳转到首页界面,首页界面需要用户名展示。(这个场景必须要验证和跳转界面共同完成才行)
- 请求转发:servlet容器接收到请求以后,会先对请求做一些预处理,然后再将请求传递给其他资源(如Servlet、jsp)而无需客户端发起新的请求。
- 请求转发发生在服务端,整个过程只有一次请求与响应,所以不同资源之间可以共享请求中的数据;由于是一次请求所以浏览器url不会发生变化。
- 请求转发不支持跨域请求,只能跳转到当前应用中的资源。
request.getRequestDispatcher(String path).forward(request, response);
- 重定向(redirect):
重定向属于客户端行为。服务器在收到客户端请求后,会通知客户端浏览器重新向另外一个 URL 发送请求,这称为请求重定向。它本质上是两次 HTTP 请求。- 重定向是两次请求,请求的url会发生变化,支持跨域请求。
- 服务端会以状态码为302的响应信息返回给浏览器,该响应的含义是:通知浏览器再次发送请求访问另外一个URL资源; 该响应中的参数location存储重定向的url
response.sendRedirect(String path);
会话跟踪技术
HTTP协议是基于请求响应模式的无状态协议,这个无状态指:
- Http对于事务处理没有记忆能力,无法维护用户的上下文信息,不能保存用户的状态;
- Http请求都是相互独立的,不会受到上一次请求的影响,也不会影响下一次请求。
在web应用中一般都需要用户的状态,比如在下单之后,需要根据当前用户名获取下单的商品支付,原生的HTTP中无法保存用户名,这就引进了会话跟踪技术。
会话跟踪技术,从打开某个浏览器访问网站开始,到关闭浏览器时结束称为一次会话;该技术用来帮助服务器记录用户状态和数据
。
常用的会话技术有:
- Cookie 客户端会话技术
- Cookie 属于客户端会话技术,是服务器发送给浏览器的一小段文本,存储在客户端浏览器的内存或硬盘中;当浏览器保存了Cookie后再一次请求时会将Cookie放在请求头中回传给服务器。
- Session 服务端会话技术
- Session属于服务端会话技术,是浏览器访问Web应用时,服务器为客户浏览器创建一个Session对象,每个浏览器独占一个Session;用户可以将数据存储在的Session中,当用户再次访问资源时可以从Session中获取。
Cookie
Cookie有两种:
- 会话级别Cookie(默认): Cookie保存在浏览器内存中,浏览器一旦关闭Cookie就失效;
- 持久化Cookie:Cookie以文本文件形式保存在硬盘上。
工作流程:客户端访问服务器时,服务器通过在HTTP响应中添加set-Cookie字段保存Cookie信息,在发送给浏览器;浏览器得到响应再将Cookie信息保存在内存或硬盘上;用户再次请求服务器时会将Cookie放在请求头中回传给服务器,服务器获取请求头中的Cookie来跟踪用户的状态。
创建Cookie对象,Servlet的http包中定义了Cookie类,通过其带参构造方法创建:
Cookie只能保存文本(字符串)信息,所以Cookie的value只能是String
Cookie c1 = new Cookie("username","张三");
Cookie的获取和保存:
这里的response,request 是HttpServletResponse和HttpServletRequest
// 添加Cookie(在响应中添加set-Cookie字段)
response.addCookie(c1);
//获取Cookie信息
Cookie[] cookies = req.getCookies();
操作Cookie常用方法:
- getMaxAge() /setMaxAge() 用于获取/设置Cookie 的最大有效时间/秒。默认情况下取值为 -1,表示该 Cookie 保留到浏览器关闭为止;为 0 时,表示删除该 Cookie
- getName() 用于获取 Cookie 的名称。
- getPath()/setPath() 用以获取/设置Cookie路径
- getValue()/setValue() 用于获取/设置Cookie的值
删除Cookie方法:
- 关闭浏览器时自动删除Cookie
- 使用 setMaxAge(0) 手动删除 Cookie,需要使用 setPath 方法指定 Cookie 的路径,且该路径必须与创建 Cookie 时的路径保持一致。
Session
Session虽然是属于服务端技术,但是他的实现离不开Cookie的支持,实现原理如下:
- 当客户端第一次访问服务器时,服务器会创建一个 Session 对象,并为该 Session 对象分配一个唯一的 SessionID(用来标识这个 Session 对象);
- 服务器将 SessionID 以 Cookie(Cookie 名称为:“JSESSIONID”,值为 SessionID 的值)的形式发送给客户端浏览器;
- 客户端浏览器再次发送 HTTP 请求时,会将携带 SessionID 的 Cookie 随请求一起发送给服务器;
- 服务器从请求中读取 SessionID,然后根据 SessionID 找到对应的 Session 对象。
// 获取Session对象
HttpSession session = request.getSession();
Session常用API
- getCreationTime() 返回创建 Session 的时间;
- getId() 返回 Seesion 的 ID;
- getLastAccessedTime() 返回客户端上一次发送与此 Session 关联的请求的时间;
- getMaxInactiveInterval()/setMaxInactiveInterval() 获取/设置Session过期时间/秒;
- invalidate() 使 Session 失效
- getServletContext() 返回 Session 所属的 ServletContext 对象
Session的过期时间,Session驻留在服务器中一段时间后没有被使用就会自动销毁,Session的过期时间有以下两种设置方法:
1.在web.xml中设置
<session-config>
<session-timeout>10</session-timeout> <!--单位是分钟 必须为整数,if <=0 永不过期-->
</session-config>
2.在代码中设置
//设置会话的过期时间
request.getSession().setMaxInactiveInterval(120); //单位秒
Session 的生命周期
-
Session 对象创建
Session 对象在容器第一次调用 request.getSession() 方法时创建。值得注意的是,当客户端访问静态资源时,服务器不会创建 Session 对象。 -
Session 对象销毁
Session 对象在如下 3 种情况下会被销毁:- Session 过期;
- 调用 session.invalidate() 方法,手动销毁 Session;
- 服务器关闭。
Servlet的三大域对象
request域(HttpServletRequest)
:
生命周期:一次请求中有效。
作用范围:整个请求链(转发有效,重定向无效)。session域(HttpSession)
:
生命周期:一次会话中有效
作用范围:同一次会话中的所有请求application域(ServletContext
):
生命周期:整个Web应用的生命周期(从Web应用启动到停止)。
作用范围:整个Web应用(所有的用户都可以共享ServletContext中的数据)。
这三大域对象都提供了用于存储和获取属性的方法
setAttribute(String name, Object object):用于存储一个对象到域中,与指定的名称关联。
getAttribute(String name):用于从域中获取指定名称关联的对象。
removeAttribute(String name):用于从域中移除指定名称关联的对象。
Servlet记录上次访问的时间
@WebServlet("/visit")
//记录上次访问的时间
public class VisitedServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置页面的编码格式
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
// 获取当前时间
Date date = new Date();
// 设置时间格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
HttpSession session = req.getSession();
out.write("<h1>欢迎访问本网站!</h1>");
out.write("<h3>创建Session时间:"+sdf.format(session.getCreationTime())+"</h3>");
out.write("<h3>当前会话的SessionID:"+session.getId()+"</h3>");
out.write("<h3>上次访问Session的时间:"+sdf.format(session.getLastAccessedTime())+"</h3>");
if (session.getAttribute("lastVisitTime")==null){
out.write("<h3>这是首次访问!</h3>");
}else out.write("<h3>上次访问网站的时间:"+session.getAttribute("lastVisitTime")+"</h3>");
// 将当前时间保存到session中
session.setAttribute("lastVisitTime",sdf.format(date));
}
}
Servlet Filter过滤器
Filter过滤器是Servlet中定义的拦截请求,过滤响应的规范(接口)。
是Servlet中最实用的组件,常用来做用户的权限控制、过滤敏感词、设置统一编码格式
等。
实现Filter方式,实现Filter接口即可
注册Filter:使用@WebFilter注解 / 在web.xml中配置
<filter>
<filter-name>ValidFilter</filter-name>
<filter-class>com.servlet.ValidFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ValidFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Filter 工作流程:
demo:拦截所有请求并打印
@WebFilter("/*")
public class ValidFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
System.out.println("请求路径:" + req.getServletPath());
// 可以在此处添加其他过滤逻辑
// 继续传递请求
chain.doFilter(req, resp);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
过滤器链:在 Web 应用中,可以部署多个 Filter,若这些 Filter 都拦截同一目标资源,则它们就组成了一个 Filter 链(也称过滤器链)。客户端的请求在这些过滤器之间传递,直到传递给目标资源。
Filter 链中 Filter 的执行顺序
通过 web.xml 配置的 Filter 过滤器,执行顺序由 <filter-mapping> 标签的配置顺序决定。<filter-mapping> 靠前,则 Filter 先执行,靠后则后执行
通过 @WebFilter 注解配置的 Filter 过滤器,无法进行排序
案例:过滤敏感词
有一个评论页面,可以对提交的评论数据进行敏感词过滤功能
// 给定敏感词:滚,他妈的,去死,...
jsp评论页面
<body>
<h1 style="text-align: center">评论!</h1>
<form action="${pageContext.request.contextPath}/comment" method="post">
<textarea name="comment"></textarea>
<input type="submit" value="提交评论"/>
</form>
<br/>
<span style="color: red">${errorInfo}</span>
</body>
filter处理
@WebFilter(urlPatterns = "/comment",initParams = @WebInitParam(name="forbidden",value="滚,他妈的,去死,卧槽"))
public class CommentFilter implements Filter {
private String[] forbiddens = null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String forbidden = filterConfig.getInitParameter("forbidden");
forbiddens = forbidden.split(",");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
String comment = request.getParameter("comment");
for (String forbidden : forbiddens) {
if (comment.contains(forbidden)){
request.setAttribute("errorInfo","你输入的内容有不当评论");
request.getRequestDispatcher("/header.jsp")
.forward(request,response);
return;
}
}
chain.doFilter(request,response);
}
@Override
public void destroy() { }
}
Servlet Listener监听器
Listener监听器是实现特定监听接口的Java程序,这个程序专门用于监听另一个Java对象的状态变化(对象的创建销毁、方法调用、属性改变),当被监听对象的状态变化时监听器中的方法立即执行。
Servlet 规范中定义了 8 个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期和属性变化事件。
监听器 Listener 按照监听的事件划分,可以分为 3 类:
- 监听对象创建和销毁的监听器(ServletContextListener,HttpSessionListener,ServletRequestListener)
- 监听对象中属性变更的监听器(ServletContextAttributeListener,HttpSessionAttributeListener,ServletRequestAttributeListener)
- 监听 HttpSession 中的对象状态改变的监听器(HttpSessionBindingListener)
比如对于ServletRequestListener监听请求的创建和销毁,可用于监听某个IP访问网站的频率,如果频率过高做一些限制访问;也可以做日志记录,记录用户访问了哪些资源…
注册 Servlet 监听器有 2 种方式,分别是:
- 在 web.xml 中注册监听器;
<listener>
<listener-class>全限定类名</listener-class>
</listener>
- 使用 @WebListener 注册监听器。
Servlet监听器统计网站在线人数
登录界面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="/showLoginItems" method="get">
<input type="text" name="username" placeholder="请输入用户名" />
<input type="submit" value="登录" />
</form>
</body>
</html>
- 使用 HttpSessionListener 和 HttpSessionAttributeListener 实现
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form action="/showLoginItems" method="get">
<input type="text" name="username" placeholder="请输入用户名" />
<input type="submit" value="登录" />
</form>
</body>
</html>
展示所有用户列表
@WebServlet("/showLoginItems")
public class ShowLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求响应编码格式
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
//获取表单中的数据
String username = req.getParameter("username");
out.write("<h2>欢迎你!"+username+"</h2>");
//查看当前会话是否有用户登录
String logined = (String) req.getSession().getAttribute("username");
if (logined != null && !"".equals(logined)){
out.write("<h1>编程帮 www.biancheng.net</h1>"
+ "<h3>您好,您已经登录了账户:" + logined + "</h3>"
+ "如要登录其他账号,请先退出当前账号重新登录!");
}else {// 将当前账号加入会话中
req.getSession().setAttribute("username", username);
}
// 获取已经登录的用户集合
List<String> loginedUsers = (List<String>) super.getServletContext().getAttribute("loginedUsers");
if (loginedUsers != null){
out.write("<table border='1' cellspacing='0' cellpadding='3'><tr><td>当前在线人数:"+loginedUsers.size()+"</td></tr>\r\n");
for (String loginedUser : loginedUsers) {
out.write("<tr><td>用户:" + loginedUser + "</td><tr>\r\n");
}
out.write("</table><br/>" + "<a href='/LogoutServlet'>退出登录</a>");
}
}
}
监听Session和ServletContext的Attribute
@WebListener
public class OnlineListener implements HttpSessionListener, HttpSessionAttributeListener{
// 监听用户登录
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
// 从上下文中获取已经登录的用户集合
List<String> loginedUsers = (List<String>) event.getSession().getServletContext().getAttribute("loginedUsers");
loginedUsers = loginedUsers==null || loginedUsers.size()==0 ? new ArrayList<>():loginedUsers;
String userName = (String) event.getSession().getAttribute("username");
// 将用户添加到已登录集合中
loginedUsers.add(userName);
System.out.println("用户:"+userName+"加入在线用户了");
event.getSession().getServletContext().setAttribute("loginedUsers",loginedUsers);
}
// 监听用户退出
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
String username = (String) session.getAttribute("username");
ServletContext context = session.getServletContext();
List<String> loginedUsers = (List<String>) context.getAttribute("loginedUsers");
if(username!=null && loginedUsers!=null && loginedUsers.size()>0){
// 从在线列表中删除当前用户名
loginedUsers.remove(username);
System.out.println("用户:"+username+"退出了");
System.out.println("-----------会话已经销毁了-----------");
}
}
}
用户登出:
@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//退出登录操作,将此次session进行销毁
//触发HttpSessionListener监听器的sessionDestroyed方法
request.getSession().invalidate();
//跳转回登录页面
response.sendRedirect("/login.jsp");
}
}
结果:
chrome中登录:
Edge中登录:
- 上面代码监听时也可以使用HttpSessionBindingListener , 用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件
- valueBound ( event) 当对象被绑定(添加)到 HttpSession 对象中时触发
- valueUnbound event) 当对象从 HttpSession 对象中解除绑定(移除)时触发
JSP
jsp = Java server page, 是一种动态开发web网页技术,就是在传统的html文件中插入Java代码和jsp标签。
jsp是servlet的扩展,可以在jsp中使用servlet的所有功能,jsp的本质就是servlet,只不过jsp更注重于页面网页的设计。用户访问 JSP 页面时,JSP 代码会被翻译成 Servlet 代码,最终,以字符串的形式向外输出 HTML 代码。所以,JSP 只是在 Servlet 的基础上做了进一步封装。
设计jsp的目的: servlet不能镶嵌 HTML 代码,因此输出 HTML 比较困难,jsp就是开发者为了客服这些而产生的。
jsp相对于servlet的优点:
- 易于维护
相对于 Servlet 来说,JSP 更易于管理。在 JSP 中,可以将业务逻辑与网页设计分开,而在 Servlet 技术中,它们是混合在一起的。 - 快速开发:无需重新编译和部署
JSP 页面被修改后,不需要重新编译和部署项目。而 Servlet 被修改后,需要重新编译和部署。
JSP编译
当浏览器请求 JSP 时,JSP 容器会首先检查是否需要编译页面。如果该页面从未被编译过,或者自上次编译以来对其进行了修改,则编译该页面。
编译过程包括 3 个步骤:
- 解析 JSP:容器解析 JSP 文件,查看是否有语法错误
- 翻译 JSP:容器把 JSP 文件翻译为 Servlet 类
- 编译 Servlet
jsp页面基本格式:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%= "Hello JSP" %>
</body>
</html>
其中:<%@ page %> 用来指定该页面的配置信息;contentType指定该网页类型为html以及字符编码为UTF-8; language=“java” 指定网页编程语言为Java;
<%= %> 等价于 <%out.print();%>
,是 JSP 中的输出语句;从格式上看jsp就是在HTML中添加一个头部声明。
JSP脚本:在jsp中可以通过jsp脚本写入Java代码,标准格式如下:
<jsp:scriptlet>
Java代码
</jsp:scriptlet>
----------------------------
等价于:
<% Java代码 %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%= "Hello JSP" %>
<%
String name = request.getParameter("name");
out.println("hello :"+name);
%>
</body>
JSP 声明 语句用于声明一个或多个变量、方法,以供后面的 Java 代码使用。
<jsp:declaration>
声明语句
</jsp:declaration>
--------------------
等价于:
<%! 声明变量、方法等 %>
<%!
int num = 0;
Cricle c = new Cricle(2.0);
%>
jsp指令
目的:告诉web服务器如何处理jsp页面的请求和响应;以<%@
开始,%>
结尾。
常见的三个jsp指令:
- <%@ page… %> 定义与页面相关的属性,比如脚本语言、页面编码、页面类型等
- <%@ include… %> 引入其他jsp文件
- <%@ taglib… %> 声明并导入其他标签库
page指令中常见的属性:
- contentType : 声明页面的编码格式,页面类型
- import : 导入Java的类库,比如<%@ import=“java.util.Date” %>
- language: 指定脚本语言
使用import导入多个类库时加上逗号以分割:
<%@ page contentType="text/html;charset=UTF-8"
language="java"
import="java.util.Date,java.text.SimpleDateFormat"
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<%!
int num=112;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
%>
<%
out.println("my age is "+num);
out.println("当前时间是:"+sdf.format(new Date()));
%>
</body>
</html>
include指令的属性:
- file 引入外部文件
<%@ include file="URL" %>
header.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1 style="text-align: center">这是头部!</h1>
</body>
</html>
---------------------------------
另一个jsp文件
<body>
<%@ include file="header.jsp" %>
<%!
int num=112;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
%>
<%
out.println("my age is "+num);
out.println("当前时间是:"+sdf.format(new Date()));
%>
</body>
taglib指令
语法:<%@ taglib="taglibUrl" prefix="taglibPre" %>
url :指标签库的引用地址,prefix指定标签库的前缀
Jsp动作:
<jsp:forward>
动作用来将请求转发到另一个页面中,请求的参数数据会被一起转发到目标页面。
<jsp:forward page="url"/>
<jsp:param>
动作用来传递请求参数信息:
<jsp:forward page="index.jsp">
<jsp:param name="name" value="编程" />
<jsp:param name="age" value="443" />
</jsp:forward>
<jsp:useBean>
用于获取 Bean 对象。
<jsp:useBean id = "name" class = "package.class" scope= "page | request | session | application" />
其中id表示创建对象的变量名,class表示对象的全限定名,scope表示对象的可用范围
Jsp内置对象
JSP 内置对象又称为隐式对象,它们由容器实现和管理。
- request : 获取用户请求信息
- response: 可将处理的信息封装返回给客户端
- session :保存/获取用户信息
- out : 输出内容到html
- application : 共享所有用户的信息
- config : 用户的初始化信息
- page : 当前的页面,类似Java中的this
- pageContext : 当前页面的页面容器,用于访问 page、request、application 和 session 的属性
- exception:JSP 文件执行时发生的错误和异常(在 JSP 页面的 page 指令中指定 isErrorPage=true才可使用)
其中out对象常用方法:
print() 将内容直接打印在 HTML 标签中
println() 类似于 print,唯一区别是 println 方法添加了换行符
newLine() 输出换行字符
clear() 清除页面缓冲区
jsp el表达式
使用jsp<% %>
脚本输出变量传递数据,降低页面的可读性,为了简化页面jsp提供了el表达式,可以以更简洁、方便的形式来访问变量和参数。
el表达式语法:
${变量/表达式}
demo:
@WebServlet(value = "/elTest")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
Student stu = new Student("张三", 18, "1班");
req.setAttribute("stu", stu);
String[] arr = {"语文:99","数学:123"};
req.setAttribute("score", arr);
req.setAttribute("name", "Brett");
req.getRequestDispatcher("/firstPage.jsp").forward(req, resp);
}
}
<%
request.setAttribute("age",33);
%>
<div>
<span>${name}</span> <!-- 获取普通属性 -->
<span>${age}</span>
<br>班级:<span>${stu.cls}</span> <!-- 获取对象属性 -->
<br><span>成绩:${score[0]}</span> <!-- 获取数组属性 -->
<br><span>成绩:${score[1]}</span>
<br>学生:<span>${empty stu? "空对象": stu.name}</span> <!--判断对象是否为空-->
</div>
获取项目的应用程序名
<form action="${pageContext.request.contextPath}/xxxServlet"></form>
jstl 核心库
jstl : jsp通用标签库,它封装了 JSP 应用的通用核心功能。
使用前需要引入标签库 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
标签属性使用:
<c:set>
设置对象/变量属性值
<c:set var="names" value="佳妮"></c:set>
<div>
<span>my name is ${names}</span>
</div>
<c:if>
条件判断
<body>
<%
request.setAttribute("age",33);
%>
<c:if test="${age>=18}"> <!-- test表示判断条件 -->
<p>你已经成年了</p>
</c:if>
<c:if test="${age<18}">
<p>你还是未成年人</p>
</c:if>
</body>
<c:choose>
条件匹配,类似于Java中switch-case
<%
request.setAttribute("age",33);
%>
<c:choose>
<c:when test="${40>age and age>=18}">
<div>成年人</div>
</c:when>
<c:when test="${age<18}">
<div>未成年</div>
</c:when>
<c:otherwise> <!-- 两个条件都不满足时输出 -->
<div>少年</div>
</c:otherwise>
</c:choose>
<c:forEach>
循环遍历集合中的数据
Servlet中:
protected void doGet(HttpServletRequest req, HttpServletResponse resp{
String[] arr = {"语文:99","数学:123","英语:91"};
req.setAttribute("arr",arr);
req.getRequestDispatcher("/firstPage.jsp").forward(req, resp);
}
jsp中:其中items是遍历的集合对象,var是当前对象的别名
<c:forEach var="item" items="${arr}">
<div>${item}</div>
</c:forEach>
什么是ThreadLocal?
ThreadLocal是jdk中一个非常有用的类,从名字上来看叫做线程变量