1.Java EE WEB新篇章:
-
基础从Broswer(浏览器)向Server(服务器)发送数据,称为请求,英文名:request
从Server(服务器)向Broswer(浏览器)发送数据,称为相应,英文名:response
-
Tomcat服务器目录
- bin:这个目录是Tomcat服务器的命令文件存放的目录,比如:启动,关闭tomcat
- conf:这个目录是Tomcat服务器的配置文件存放目录。
- lib:这个目录是Tomcat服务器的核心程序目录。
- logs:Tomcat服务器的日志目录。
- temp:Tomcat服务器的临时目录,存储临时文件。
- webapps:这个目录用来存放大量的webapp。(web application:web应用)
- work:这个目录是用来存放JSP文件翻译之后的java文件和编译过后的class文件。
3.Tomcat需要配置的环境变量:
- CATALINA_HOME
- JAVA_HOME
- Tomcat目录下的bin目录
2.开发一个带有Servlet的webapp(重点)
开发步骤是怎样的?
第一步:在webapps下新建一个新目录,起项目名字。
- 注意:crm就是这个webapp的根目录
第二步:在crm下创建一个目录:WEB-INF
- 注意:这个目录的名字是servlet规范规定的,必须全部大写
第三步:在WEB-INF目录下新建一个目录:classes
- 注意:目录必须全小写,这是servlet规范中规定的,另外这个目录存放的就是java编译后的class文件(字节码文件)
第四步:在WEB-INF目录下新建一个目录:lib(非必须)
- 注意:如果一个webapp需要第三方的jar包的话,这个jar包要放到lib目录下,名字也不能随便写,必须是全小写的,例如java语言连接数据库需要数据库的驱动,name这个jar包就要放到lib目录下,这是servlet规范规定的。
第五步:在WEB-INF目录下新建一个文件:web.xml
-
注意:这个文件是必须的,名字必须叫做web.xml,这个文件必须放在这里,这是个配置文件,存放路径和类名的对应关系。
-
这个文件最好从别的webapp中拷贝,最好别手写,没必要。
-
<?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" metadata-complete="true"> </web-app>
-
第六步:编写一个java小程序,这个java小程序也不能随便写,必须实
现servlet接口。- 这个Servlet接口不在JDK当中。(不是JavaSE了)
- Servlet接口(Servlet.class)是Oracle提供的
- Servlet接口是JavaEE的规范中的一员。
- Tomcat服务器实现了Servlet规范,所以Tomcat服务器也需要使用Servlet接口。
- java程序源代码可以在任何地方,只要编译后的class文件放到WEB-INF目录下的classes文件夹中即可。
-
第七步:编译我们写的servlet程序。
- 环境变量配置CLASSPATH=.;D:\dev\apache-tomcat-10.0.20\lib\servlet-api.jar
- 等号后面是servlet-api.jar包所在的位置
- 前面要加.;
-
第八步:编译后拷贝到WEB-INF目录下的classes文件夹。
-
第九步:编写web.xml配置信息
- 专业术语:在web.xml中注册servlet
-
第十步:编译
-
第十一步:拷贝class文件及其目录到classes中。
3.关于JavaEE版本
- JavaEE目前最高版本是JavaEE8
- Oracle将JavaEE规范捐献给Apache了
- Apache把JavaEE换名了,以后不叫JavaEE了,以后叫做jakartaEE
- 以后没有JavaEE了,叫做jakartaEE
- JavaEE8对应的Servlet类名是:javax.servlet.Servlet
- JakartaEE9对应的Servlet类名是:jakarta.servlet.Servlet
4.解决Tomcat乱码问题
- 找到Tomcat根目录下conf文件夹,修改loggin.properties文件
- 搜索UTF-8改成GBK(…handler…)
5.在servlet中连接数据库
-
<servlet> <servlet-name>first</servlet-name> <servlet-class>lapTop.Servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>first</servlet-name> <url-pattern>/first</url-pattern> </servlet-mapping>
- 以上是xml文件的配置,叫做注册servlet。
<servlet-name>中随便写
<servlet-class>非常重要,这里填java小程序所在的路径
<url-pattern>以/开头,在浏览器地址栏输入,以上的是:http://127.0.0.1:8080/oa/first
- oa是javaweb项目的名字
6.Servlet对象的生命周期
- Servlet对象都是由Tomcat服务器管理的。
- Tomcat服务器又叫做WEB容器。
- 默认情况下服务器启动时不会创建Servlet对象。
- 怎么让服务器启动的时候创建Servlet对象呢?
- 在web.xml中的
<servlet></servlet>中添加<load-on-startup></load-on-startup>
- 里面填一个整数,数字越小优先级越高
- 在web.xml中的
- 向服务器端发送请求时,先调用无参构造方法(因为此时对象还没被创建出来),再调用init方法(此时对象已经创建)。仅在第一次发送时调用init方法,之后都是用已经创建出来的对象。这说明了Tomcat的servlet是单例模式(假单例)
- 之后调用多少次,就会调用多少次service方法
- 销毁时,调用destroy方法,此时对象还未被销毁,调用完方法之后才被销毁。
- 当我们在Servlet当中编写有参构造函数会导致:
- 如果没有无参构造方法,会导致浏览器提示发生500错误。
- 500错误一般指的是服务器端发生的错误。
- 在Servlet开发当中,不建议程序员创建构造方法。
- init和Servlet构造方法创建时间差不多,效果都一样,那么Servlet构造方法能代替init方法吗?
- 不能。因为Javaweb开发规范规定,java程序员不建议手动编写构造方法,因为很可能导致无参构造方法消失,导致错误。
- 适配器设计模式
- 要一次性实现接口的所有方法,代码看起来不美观
- 可以设置一个适配器,将不常用的方法留空,核心方法改为abstract抽象类型,再把适配器类也设为abstract。
- 以后调用的时候用 extends xxxAdapter
- 编写一个GenericServlet类,这个类是一个抽象类,实现了service方法
- 以后编写的所有servlet类继承GenericServlet,重写service方法。
- GenericServlet类如何改造更利于子类程序的编写?
- 在有参的init方法里执行无参的init()方法,让子类重写的是无参init方法,这样有参init()方法里的代码就能保留下来。
- init方法里将参数ServletConfig的局部变量用成员变量储存
- 在getServletConfig()方法中返回成员变量的ServletConfig。
7.ServletConfig
- 直接用this就可以调用ServletConfig对象的方法。(继承了GenericServlet)
- ServletConfig中常用方法:
- getInitParameterNames() 获取所有init元素的名字
- getInitParameterName(String name)输入字符串name获得init元素的值。
- getServletName() 获得当前Servlet的名字
- getServletContext()
7.Extension:Enumeration
- 常用方法
- hasMoreElements()判断是否有更多元素
- nextElement()返回当前元素的值并移动指针到下一个元素
8.ServletContext
-
一个webapp只有一个ServletContext对象。
-
Tomcat在启动时创建ServletContext对象,关闭时销毁
-
ServletContext常用方法
- getInitParameterNames(),获取一个context集合,里面储存了所有context-param内容,返回enumeration
<String>
- getInitParameter(String name),通过context的name获取value。返回String。
getContextPath()
获取应用的根路径(项目名)
//获取web目录下某个文件的真实路径。注意:这个文件是在web目录下的 String realPath = application.getRealPath("/index.jsp");
- getInitParameterNames(),获取一个context集合,里面储存了所有context-param内容,返回enumeration
-
要调用ServletContext中的方法只能通过ServletContext对象来调用。
-
ServletContext常用方法(二)
application.setAttribute(String name,Object obj);
设置ServletContext属性,相当于缓存,全局共享。
context.getAttribute(String name);
获取ServletContext属性,只要在一个Servlet里创建了,那么在任何一个Servlet中都可以访问到这个属性的值。
9.HTTP协议
- HTTP协议是由W3C制定的一种超文本传输协议。
- 什么是超文本?:不是普通文本,比如流媒体:声音,图片,视频等。
- HTTP不但支持普通文本,还可以传输流媒体:声音,图片,视频等。
- HTTP的请求协议(B->S)浏览器向服务器发送叫 请求,请求包括四部分
- 请求行
- 请求头
- 空白行
- 请求体
- HTTP的响应协议(S->B) 服务器接收浏览器的请求叫 响应,响应包括四部分
- 状态行
- 响应头
- 空白行
- 响应体
9.Extension:模板方法设计模式
- 模板类的模板方法:
- 定义核心算法骨架,具体的方法实现可以推迟到子类中实现。
- 模板类一般都是抽象类。
- 模板方法一般要加final修饰,防止核心算法被修改。
- 模板类一般都是抽象类。
- 定义核心算法骨架,具体的方法实现可以推迟到子类中实现。
- 单例模式:
- 抽象工厂模式:
10.HttpServlet
- http包下都有哪些类?
- jakarta.servlet.http.HttpServlet
- jakarta.servlet.http.HttpServletRequest
- jakarta.servlet.http.HttpServletResponse
11.设置WEB的欢迎页
-
默认会在WEB文件夹下寻找index.html/index.jsp/index.htm
-
或者在web.xml手动配置:
<welcome-file-list> <welcome-file>XXX.html</welcome-file> </welcome-file-list>
(默认从webapp目录根下寻找)
-
可以设置多个欢迎页,欢迎页之间越靠上面的越高优先级。
-
可以设置动态的Sevlet为欢迎页
- 注意:welcome-file里填Servlet的请求路径,头部不需要斜杠。
<welcome-file-list> <welcome-file>http</welcome-file> </welcome-file-list> <servlet> <servlet-name>http</servlet-name> <servlet-class>com.httpservlet.HttpServletTest</servlet-class> </servlet> <servlet-mapping> <servlet-name>http</servlet-name> <url-pattern>/http</url-pattern> </servlet-mapping>
12.HttpServletRequest接口
-
前端提交数据的格式:username=abc&userpwd=abc&hobby=1&hobby=2&hobby=3
-
采用
Map<String,String[]>
的方式储存前端的key和value -
如果是以
Map<String,String>
的格式储存,那么后面key的value会覆盖前面的。 -
前端表单提交的数据类型都是String
-
HTTPServletRequest接口常用方法:
-
Map<String,String[]> getParameterMap() //获取Map Enumeration<String> getParameterNames() //获取Map集合中所有的key String[] getParameterValues(String name)//根据key获取Map集合的value String getParameter(String name) //获取value这个一维数组中的第一个元素 用的最多
-
-
请求跳转:
-
Servlet对象不能由我们java程序员来new,必须使用HttpServletRequest对象的请求转发器:
-
RequestDispatcher getRequestDispatcher(String path)
返回RequestDispatcher类型 -
RequestDispatcher对象有forward(request,response)方法可以传递当前servlet的request和response对象
-
这样就能共享请求域中的内容。
-
结合一起,最终版本:
request.getRequestDispatcher(String path).forward(request,response);
-
path里面的是web.xml里配置的servlet-mapping中的url-pattrn。
-
转发的内容不一定是servlet,可以是html…等等
-
注意:path以"/"开始,不加项目名。
-
-
String getParameter(String name) 和 Object getAttribute(String name) 的区别是什么? 第一个是:浏览器中用户发送过来的数据。 第二个是:servlet请求域中绑定的数据。
-
ServletRequest其他方法:
getRemoteAddr() 获取客户端IP地址
getContextPath()获取应用的根路径
getMethod()获取请求方式
getRequestURI()获取请求的URI:/项目名/…
getServletPath()获取servlet path: 不带项目名
13.设计一个纯Servlet单页面(oa项目)
-
1、设计数据库:
- 使用scott中的dept表
-
2、设计页面:
- index.html 主页面
- list.html 列表页面
- detail.html 详细页
- modify.html 修改页面
- add.html 新增页面
-
3、功能:
- 删除
- 新增
- 列表
- 详情
- 修改
-
4、实现功能:
-
EZ for me
14.web应用转发和重定向
- 转发:
- request.getRequestDispatcher(“/路径”).forward(request,response);
- 请求转发之后浏览器地址栏路径不变。
- 重定向:
- response.sentRedirect(“/项目名/路径”);
- 转发后浏览器的地址会发生变化。
- 重定向时浏览器向服务器发送了两次请求,所以最后的路径会发生改变。
- 注意:request.getContextPath()可以获取应用的根路径。
15.基于注解开发Servlet
- 第一个注解:@WebServlet
import jakarta.servlet.annotation.WebServlet;
- @WebServlet属性:
- name属性:用来指定servlet名字,等同于
<servlet-name>
- urlPatterns属性:用来指定Servlet的映射路径,可以指定多个字符串
<url-pattern>
- loadOnStartUp属性:用来指定在服务器启动时是否加载该Servlet,等同于
<load-on-startup>
- 重要:value属性:相当于urlPatterns属性,指定映射路径,但是在注解中,属性名等于value的可以省略不写,也就是说,我们可以这样写Servlet映射:
- @WebServlet(“/url”)
- name属性:用来指定servlet名字,等同于
- 注解对象的使用方式:@注解对象(属性名 = 属性值,属性名 = 属性值)
16.使用模板方法设计模式优化oa项目
- 解决类爆炸的问题,使用一个DeptServlet完成所有的功能。
- 根据前端返回的路径执行不同的功能,例如doList,doAdd等等。
17.分析使用纯粹Servlet开发web应用的缺陷
高耦合,需要在java程序中写前端代码。
- 出错无法及时得到提示。
- 代码不美观,易出错。
解决:
- 让某个程序翻译前端代码生成servlet代码,这就是接下来的技术:JSP
17.1关于B/S结构的会话机制
-
会话,英文名:session
-
session的原理:
- 浏览器端储存一个sessionid,服务器端储存一个sessionid,两个id如果匹配的话,就能找到一个session。
- 第一次调用request.getSession(),没有sessionid,新建一个sessionid,这个sessionid储存在浏览器缓存中,然后将sessionid发送给浏览器。
- 第二次调用:浏览器端发送sessionid与服务器端进行匹配,找到一个session。
-
为什么浏览器关闭时会session失效:
- 因为浏览器关闭时缓存会消失,重新打开浏览器时,浏览器端没有以前的sessionid了。
- 浏览器通过cookie缓存状态
-
有什么session失效的方法:
- 1、超时机制:在web.xml文件中可以用
<session-config>
<session-timeout>30</session-timeout>
</session-config>
- 设置session超过30分钟后失效。
- 2、手动销毁:例如网银系统退出时的安全退出按钮。
- 使用session.invalidate()方法
- 1、超时机制:在web.xml文件中可以用
-
浏览器禁用cookie:
- 服务器正常发送cookie,但是浏览器拒收,导致每一次都会新建一个sessionid。
17.2关于Cookie
- 每一个session都对应一个sessionid
- 对于session关联的cookie来说,这个cookie是被保存到浏览器的“运行内存”当中。
- cookie最终是保存在浏览器客户端当中的。
- session是将会话状态保存在服务器端。
- cookie可以保存到硬盘上。
- cookie的作用
- 保存会话状态
- cookie经典案例:
- 126邮箱30天内免登录,实现原理:
- 用户输入正确的用户名和密码,并选择30天内免登录,浏览器会将cookie保存在硬盘当中,30天内登陆时,会加载cookie自动提交信息给服务器端,服务器端接收到cookie,获取用户名和密码,校验通过后登陆成功。
- 126邮箱30天内免登录,实现原理:
- cookie储存的是name和value。
- response.addCookie(Cookie cookie) 服务器端往浏览器端发送cookie
- cookie.setMaxAge(int seconds) 设置cookie失效时间。
- 若没有设置cookie有效时间,则保存在浏览器运行内存当中,浏览器关闭,cookie消失。
- 只要设置cookie有效时间大于0,就会保存到硬盘文件当中。
- 设置cookie有效期等于0,删除该cookie文件,主要用于删除同名cookie
- 设置cookie有效期小于0,表示该cookie不会被储存到硬盘文件中,会放在浏览器运行内存当中。
- 使用cookie实现十天内免登录功能:
- 新增一个复选框,十天内免登录,设置name和value。这会随着提交按钮提交到服务器端。
- 服务器端判断登录成功后,判断是否有收到来自复选框的数据,如果有,那么新建两个cookie,用来储存用户名和密码。
- 欢迎页改成自定义的welcome servlet,这里判断cookie是否为空,若为空跳转到登录页面,如果不为空,遍历每一个cookie,获取name为username和userpwd的两个cookie作为用户名和密码保存起来。
- 获取到cookie的用户名和密码,进行数据库的校验,正确就直接跳转到dept/list,不正确跳转到index.jsp登录页面。
- **重要:退出系统时要删除cookie,使用同名覆盖法,设置一个相同name的cookie,吧value设置为null,最后response.addCookie()
- 注意:覆盖的时候路径也要一样!!!之前用了setPath,那么删除的时候路径要一致,否则会当成新的cookie新建!!!!
18.JSP
- 我的第一个JSP程序
- WEB-INF目录之外 有一个index.jsp文件,这个文件没有任何内容。
- 打开浏览器访问,空白页面。
- JSP实际上就是一个Servlet类
- JSP第一次访问效率比较低,因为java文件和编译后的class文件都没有生成。
- JSP中直接写内容会被放到out.print中,相当于是在写前端代码。
- JSP响应乱码问题:
- 在开头加上<%@page contentType=“UTF-8”%>
- 表示采用的字符集是UTF-8
- 怎么在JSP中编写java程序?
- <%java代码,在这面写的被视为java程序,被翻译到service内部;%>
- JSP的注释:
- <%–注释–%>
- 如何翻译到service方法的外部
- <%!%>
- 这个语法很少用,而且不建议用,因为:
- 处于方法体外部,修改静态变量和实例变量时高并发下存在线程安全问题。
- JSP的输出格式:
- 向浏览器输出一个java变量:
- out.write()只能输出字符相关的值。
- out.print()可以输出数字,字符等等的值。
- 便捷的输出java变量的值
- 以前:<% out.write(n)%>
- 改进:<%=n%>
- n会被翻译到out.print()括号里
- n是变量,注意:变量后面没有分号
- 如果想要一次输出多个变量,用+号进行连接,与java语法相同。
- 向浏览器输出一个java变量:
- JSP语法总结:
-
直接写,是在html中输出,,翻译成out.print(“”);直接翻译到双引号里,被java程序当成字符串打印到网页上。
-
<%%>中的是java代码(记得加;分号)
-
<%!%>中的代码翻译到service方法之外,用来定义成员变量,方法,静态代码块等等
-
<%=%>中的代码翻译到out.print()当中,相当于输出java变量,翻译成out.print(),没有""
-
<%@page %>
- page指令,通过contentType来设置响应的内容
- <%@page import=“java.util.List”%>在jsp中导包
- <%@page errorPage=“/error.jsp”%>出错后导向error.jsp
- <%@page isErrorPage=“true”%>允许使用JSP九大内置对象之一Exception,配合<%exception.printStackTrace()%>使用。
-
JSP九大内置对象
- HttpServletRequest request 请求域
- HttpServletResponse response
- PageContext pageContext 页面请求域
- HttpSession session 会话域
- exception = JspRuntimeLibrary.getThrowable(request)
- ServletContext application 应用域
- ServletConfig config
- JspWriter out
- Object page
- 4个作用域大小:
- pageContext < request < session < application
- 以上4个作用域都有方法:setAttribute(),getAttribute(),removeAttribute()
- 作用域使用原则:尽可能使用小的域。
-
19.oa项目的进一步优化
- 实现登录功能:
- 步骤1、设计数据库表,有用户名和密码
- 步骤2、设计网页,要求输入用户名和密码,有登录按钮,点击后提交数据到后台
- 步骤3、后台写一个servlet验证用户名和密码。
- 登录成功:跳转到list,展示信息
- 登陆失败:登录失败页面或者返回登录页面
EL表达式
Expression Language 是jsp中的一部分,它有三大功效:
- 1、可以获取作用域中的数据
- 2、将数据转换成字符串
- 3、将数据输出到浏览器中
EL表达式的格式:${表达式}
表达式写的是setAttribute时的name!!不要加""双引号
EL表达式要输出某个类中具体的值,用.点号衔接,这里衔接的是get方法,与成员变量无关。
EL表达式从域中取数据时按照域从小到大的顺序取数据。
pageContext < request < session < application
EL表达式有四个隐含的域:
- pageScope
- requestScope
- sessionScope
- applicationScope
EL表达式取数据的时候有两种方法:
-
.
-
[“”]
-
[] 在中括号间输入数组元素下标还可以取出该下标的元素。例如
${usernames[0]}
- 如果在数组中又存储了数组:
- 那么可以
${depts[0].dname}
来获取0下标的数组中的dname值。
-
如何局部忽略EL表达式?:
- \${} 前面加上一个反斜杠
- 如果想要全局禁用EL表达式,使用 ${@ page isELIgnore = “true”}
-
通过EL表达式获取应用的根:
- EL表达式中没有隐含request对象,但是有pageContext,所以:
- ${pageContext.request.contextPath}
- EL表达式中没有隐含request对象,但是有pageContext,所以:
-
EL表达式隐含对象:
- pageContext
- param
- paramvalues
- initParam
# JSTL标签库
* 什么是JSTL标签库?
* 帮助我们减少java代码
* 用法:使用 `<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>导入核心库`
* ```
<c:forEach items="${list}" var="d">
部门编号:${d.deptno}<br>
部门名称:${d.dname}<br>
</c:forEach>
items是要遍历的集合(可用EL表达式),var是储存集合中元素的变量
<c:if test="${empty param.username}">
<h1>用户名不能为空!</h1>
</c:if>
-
test里的值必须是boolean类型,可以使用EL表达式。
-
<c:forEach var="i" begin="0" end="10" step="1"> i:${i}<br> </c:forEach>
相当于for(int i = 0;i <= 10;i++){System.out.println(i)}
- varStatus对象有count属性,从1开始递增,主要用作编号/序号。
-
<c:forEach var="i" begin="0" end="10" step="1" varStatus="v"> 编号${v.count} i:${i}<br> </c:forEach>
-
<c:choose> <c:when test="${param.age < 12}"> 儿童 </c:when> <c:when test="${param.age < 18}"> 青少年 </c:when> <c:when test="${param.age < 30}"> 青年 </c:when> <c:when test="${param.age < 55}"> 中年 </c:when> <c:otherwise> 老年 </c:otherwise> </c:choose>
类似于if else if else
-
改造oa
使用EL表达式和JSTL标签库改造oa项目。
-
了解:
-
HTML中有个
<base href="">
标签,可以设置项目的根路径 -
凡是路径中没有以"/"开始的,都会在前面加上base标签。
-
动态获取base中所有的路径(使用EL表达式)
-
协议:${pageContext.request.scheme} http 服务器名:${pageContext.request.serverName} localhost 端口号:${pageContext.request.serverPort} 8080 项目名: ${pageContext.request.contextPath} oa
过滤器
- 过滤器继承Filter类
- 在xml中配置Filter类:
-
<filter> <filter-name>filter1</filter-name> <filter-class>com.listener.Filter1</filter-class> </filter> <filter-mapping> <filter-name>filter1</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
- filter-mapping的顺序决定了Filter执行的优先级
-
- 使用注解配置Filter:@WebFilter({“*.do”})
- 使用注解时执行顺序是按照类名排序。
- 在xml中配置Filter类:
- chain.doFilter方法可以执行下一个Filter,如果没有Filter,执行相同路径的servlet。
- Filter生命周期?
- Filter生命周期与servlet对象一致。
- 与servlet的区别在于:服务器启动时就会实例化Filter对象,而servlet不会。
- Filter优先级比servlet高。
- 改写oa项目
设计模式:责任链设计模式
- 循环调用,工作原理类似于栈。
- 要保持修改关闭,拓展开放原则。
监听器Listener
- 监听器是Servlet规范中的一员
- Servlet规范中提供了哪些监听器?
- jakarta.servlet包下
- ServletContextListener
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
- jakarta.servlet.http包下:
- HttpSessionListener
- HttpSessionBindingListener
- HttpSessionAttributeListener
- HttpSessionIdListener
- HttpSessionActivationListener
- 实现一个监听器的步骤:
-
<listener> <listener-class>com.listener.ListenerTest</listener-class> </listener>
- 或者注解:
@WebListener
-
- jakarta.servlet包下
- 监听器在特殊的时候调用,由服务器自动调用,不需要web程序员干涉。
- 继承ServletContextAttributeListener的类不需要@WebListener