服务器的概念
Web开发架构
- c/s:
- client / servlet 客户端/服务器
- 开发周期长
- 用户粘度大
- 前提推广费劲
- b/s
- Borswer/servlet 浏览器/服务器
- 开发周期短 客户端借助浏览器
- 但是后期需要浏览器进行操作
- 需要学习前端的相关技术
服务器
微观的看:就是一台电脑
宏观的看: 就是服务器主机的集群
服务器就是,服务器主机+服务器的软件
所有的WEB项目都需要在服务器中发布,才可以运行
常见的服务器软件:
- tomcat
- Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。
- nginx:
- Nginx是一款轻量级的Web服务器反向代理服务器及电子邮件电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。
- tomcat 的安装:
- 复制解压 路径层次不要深、路径不能有中文、空格、中划线
- 确保java_home环境变量 tomcat运行以后要找到jdk
- bin/startup.bat 启动 注意:不要关状态框!
- bin/shutdown.bat 关闭
- 访问测试: http://localhost:8080 出现一只公猫!
- tomcat的结构介绍:
- bin文件夹:存储的是,Tomcat的可执行文件,例如:stuartup.bat
- conf文件夹:存储的是tomcat的常用配置;可以在servlet之中修改端口号
- lib文件夹:存储的是tomcat的依赖jar包
- webapps文件夹:存储的是,项目的资源
- 协议://ip:port/webapps中的路径 ----> tomcat webapps文件夹
- 每次访问tomcat就是访问的webapps文件夹
Web项目的类型
静态的web项目资源
- 静态的资源就是不需要执行 的文件,可以直接打开展示的文件 例如:图片,html,css等
- 会将这些文件直接原封不动的返回给客户端
- 静态资源可以直接通过路径访问,不需要配置虚拟路径
- Web开发注重的Java文件的执行的效果,而不是文件的本身
动态的Web项目资源
- 内部可以包含静态资源 、 直接按照路径访问
- 动态项目中会包含可执行文件 .java文件,我们不能直接按照路径访问可执行文件!
我们需要根据他配置的路径进行访问,配置的路径不是访问静态资源本身,而是执行静态资源并拿到执行结果!
部署静态以及动态的Web项目
部署静态的web项目
- 直接在webapps中创建一个文件 注意:静态资源不能直接放到webapps下!
- 在文件夹中添加静态资源即可! 图片 html css js
部署动态的web项目
- 动态Web项目的部署有三种方法 注意,动态资源,需要编译后再部署
- 第一种:直接将所有的资源放在webapps下面
- 第二种:进行压缩,再将压缩包放在webapps下:通过 jar -cvf xxx.war * 得到war包,将这个war包移动到webapps下,在tomcat运行的时候,会自己进行解压。
- 第三种:结合第三方软件进行部署
- 注意,idea之中 需要在deployment之中,进行根路径 的设置
理解访问的流程和引出servlet
一级理解:理解整体的访问流程
例如:http://localhost:8080/根目录/访问路径 本机也可以是127.0.0.1
localhost : IP地址,8080:端口号port ,通过IP地址找到服务器的主机,然后通过port找到对应的软件,再通过根目录去找到对应的项目,对于静态资源,直接根据访问路径访问,如果是虚拟的访问路径,则去XML之中去找对应的全限定名,通过全限定名,反射拿到实例,进行后续的操作
二级理解:理解内部静态资源的调用和动态资源的调用
- 在通过全限定名,拿到了类的实例对象,那么该调用方法获取返回值,再将返回值返回给客户端
- 首先,调用方法是由tomcat调用的,但是调用方法的时候,他不知道该调用什么方法
- 给所有的类规定一个统一的方法,让tomcat知道该调啥子
- 在Java中,这个接口就是servlet,通过接口,统一方法
三级理解:理解servlet的调用流程
- 检测到路径为虚拟路径!对应Servlet
- 获取虚拟路径对应的类的全限定符
- 反射得到类的模板文件
- 模板文件反射得到实例对象 注意:对象使用Servlet接值
- 调用固定的方法!获取数据
- 返回数据到客户端即可!
Servlet的基本使用
Servlet介绍
servlet能干什么
- servlet是一个接口,是一种被访问的规范!
- 因为动态的java类不能直接过去资源,需要调用处理!tomcat需要调用这一类类的某一个方法
为了统一规范,这些被访问的类都需要有一个共同的方法!所以定义访问规范接口!
这个接口就是servlet!
servlet涉及到的方法:
- void init(ServletConfig config) 初始化 执行一次
- void service(ServletRequest rsq , ServletResponse resp) 处理请求响应 可以多次执行
- void destroy() 销毁 执行一次
- 剩下两个不常用,就不看了
生命周期方法:
void init(ServletConfig config) config 是局部变量
第一次访问的时候被调用(默认情况)
服务器软件会实例化Servlet对象的时候会调用一次init方法!
init方法早于所有的方法,我们可以在内部做一些准备工作,加载全局的编码格式!
注意 init方法不能做过多耗时操作!因为后面还有请求方法等着执行!(i/o ,数据库)
servlet只会有一个对象!
默认情况(懒汉式):第一次访问调用!只调用一次!初始化参数
饿汉式:当服务器启动的时候,就初始化servlet实例!第一次会快 !设置servlet 的
1 在xml文件中
- 场景:
- 1.要求数据返回及时
- 2.servlet要加载大量的配置文件! springmvc
void service(ServletRequest rsq , ServletResponse resp) 可以理解成一次业务的入口
- 最重要的方法,每次请求都会调用service方法
- service被服务器软件传请求和响应对象!
- 服务器软件把抽象的请求和响应的数据格式给我们转成对象,方便我们操作请求和响应对象!
- service指定多次!在init方法之后执行!
- service是多线程方法!每次请求都在不线程中!
void destroy() 销毁 执行一次
- 当tomcat关闭,或者tomcat移除servlet实例,会调用destroy方法,一次!
- 跟init刚好相反,给我们一个机会!回收释放资源的机会!
Servlte的基本使用
创建一个类实现servlet接口 ,实现所有的抽象方法
配置虚拟路径
<servlet> <!--这个是服务器的别名,由自己取名 --> <servlet-name>show</servlet-name> <!--这个是服务器的全限定名 --> <servlet-class>com.qf.servlet.ShowServlet</servlet-class> <!-- Load -on -startup 填入 1-n数据,咱们就提前启动 这个值 用来对比其他的servlet 谁更早一点! 数字越小 (1-n),越提前实例化! --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <!--这个是服务器的别名,由自己取名 --> <servlet-name>show</servlet-name> <!--这个是服务器的访问路径,需要自己写 但是需要注意的就是必须加 / 他前面默认的有服务器的根路径 url: 协议://ip:port/项目标识/模块标识/动作标识 http://127.0.0.1:8080/store/user/login http://127.0.0.1:8080/store/user/register 1.路径可以设置为多层 【当前项目的虚拟路径】/模块/功能 2.一个servlet可以对应多个路径 servlet-mapping 写多个url-pattern 3.支持通配符* 多层任意/ --> <url-pattern>/show</url-pattern> </servlet-mapping>
Servlet的进化史
Servlet接口
问题:
实现servlet接口需要实现所有的方法!有些方法没用显着很乱!
servlet可以接受很多协议的请求,不专门针对http协议,所以service方法的请求和响应对象
不能获取所有http的数据!每次我们需要强转在获取 HttpServletRequest
- 思考优化:
- 不想重写所有方法
- 必须重写service方法
- 适配器模式: 一个类 实现 接口 实现所有方法
- 一个类去继承适配器类,选择性的重写!
- 可以把必须要求重写方法变成抽象方法!适配器抽象类!
- 解决:
- 有了GenericServlet 抽象类
- 实现所有方法,将service设为抽象方法,即所有继承他的类,都需要实现service方法,而不需要去实现 其他的方法
GenericServlet抽象类
- 问题:
- service方法的请求和响应对象不是针对http的还需要强转!
- servlet没有针对http做优化!
- 优化;
- 请求和响应对象转成http的
- 不同的请求方式调用不同的方法
- 解决:
- 有了HttpServlet类
- 他干了啥呢?
- 首先,他实现了service方法,在这个方法之中,将ServletRequest和ServlectResponse强转成了HTTPServletRequest 和HttpServlectResponse
- 再通过重载,又写了一个service方法,重载的方法参数是http的请求和响应, 在重写的service方法之中,调用重载的service,将强转后的请求和响应作为参数传入
- 并且,他还自己写了doGet和doPost方法,在重载的service方法之中,进行判断,将不同的请求,交给不同的处理方法
HTTPServlet
- 不同的请求方法调用不同的do方法!其实业务逻辑都是一套!
- 优化:继承之后,重写doGet和doPost方法,在doGet之中调用doPost方法,将所有的请求和响应都交给doPost处理
- 其实:既然重载的service之中,只进行了request的请求方式判断,然后再调用doGet和doPost方法,那么,我们在继承之后,只需要重写重载的service方法,那么就可以对所有的request和response进行处理
- 若不是必须,重写service就是最优的选择
Servlet的扩展
Servlet的局部配置
- 有时候需要从外部进行参
- 数的传入,可以通过外部的配置文件传入数据
- 局部的参数配置在 web-xml之中的servle标签之中
- 参数的配置
- init-param > param-name / param-vapue 都是标签,但是在servlet标签的内部
- 参数的读取
- 如何在servlet中读到参数
- 方案1:重写init方法,可以通过他的Servletconfig 参数调用getInitParam(key)获取
- 方案2:service之中 直接调用 getServletConfig 方法获取config ,然后和方案一一样
Servlet的全局配置
- 首先,全局变量就是当前项目之中,所有的servlet都能访问到的变量
- 在servlet标签外边
- context-param 之中设置
- 他的访问:
- 首先获取getServletContext ,然后和局部变量一样,通过getInitParam获取
- 全局变量和局部变量的比较
- 声明的位置不同
- 局部在servlet内部 init-param
- 全局在servlet外部 context-param
- 所有声明都在web.xml
- 访问的方法不同
- 局部变量存放在servletconfig getServletConfig()
- 全局变量存在servletcontext getServletContext();
- 数据是不互通
Servlet的注解式声明
每一个servlet都需要在xml文件之中进行声明
但是,有一个注解能简化这个过程,可以通过注解的形式声明这个Servlet
@WebServlet(name="hello",value = {"/hello","/user/hello","/xxx/*"}) //name 相当于服务器的别名,可以根据自己的需求,写不写都行 //value 相当于访问路径,也是可以设置多个,若只有一个值,则会默认的赋值为value
servlet的欢迎页面的设置
在启动服务器的时候,会默认的加载一个jsp页面,不是自己想要的
当访问项目根路径 没有指定具体资源,显示的页面,就是欢迎页面!
默认的欢迎页面 web/index.html / web/index.jsp
那么如何更改这个页面呢:
需要设置,因此,先打开xml文件,在给他添加一个 welcome-file-list
他是一个list,可以设置多个,按照顺序寻找,若第一个存在,那么就不会执行下面设置的
<welcome-file-list> <welcome-file>/html/ergouzi.html</welcome-file> 这个是访问路径 <welcome-file>index.html</welcome-file> </welcome-file-list>
Servlet的请求响应的处理
Servlet处理网络请求的流程
- 客户端发起请求
- 请求到tomcat服务器中
- 服务器根据虚拟路径找到对应的servlet
- 实例化对象
- 调用对象init
- 调用对象的service传入请求和响应对象
- 获取请求参数
- 编写业务逻辑
- 响应数据
- 得到响应结果返回给客户端
Servlet 的request处理
介绍
- tomcat在调用service方法的时候都会传入请求对象
- 请求对象是tomcat为了简化开发者操作请求数据创建的
- 请求对象中包含所有http请求数据中包含的数据(请求行、请求头、请求体)
- 请求对象我们更多的是获取数据! “获取” get
获取数据
- 请求行
- 请求方式 getMethod();
- 请求路径 getRequestURL()
- 获取项目根路径 getContextPath();
- 请求头
- getHeader(头的key);
- 注意:请求指的是浏览器传递给服务的一些信息,包含浏览器信息、数据格式等
头都是固定的,我们现在只需要记住User-Agent代表浏览器!
- 请求体
- 请求体内部携带了,每次用户传递的参数,是最重要的一部分函数!
- getParameter(参数名) 根据参数名(form input name值)获取参数值
- getParameterValues(参数名) 返回一个名对应多个值,例如爱好
- getParameterMap() 获取所有请求参数,放入一个map中,key参数名 value参数值
设置编码格式
- tomcat8.0以前版本
- get、post 中文都乱码
- tomcat8.0 以后版本
- get不乱吗
- post乱码
- 解决方案:req.setCharacterEncoding(“utf-8”); 注意:此方法一定早于获取参数方法,否则无效!
- 为什么呢?
- tomcat 8以前版本 路径编码格式 iso-8859-1 8以前 get / post乱码
- tomcat 8以后。url编码默认改为 utf-8 ! 8以后 get不乱码 post 请求体还是iso-8859-1
- 响应的编码格式也需要进行设置,不然在页面显示的时候会出现乱码
- resp.setContentType(“text/html;charset=utf-8”);
Servlet的response处理
设置响应头
- setHeader(key,value);
设置编码格式
- resp.setContentType(“text/html;charset=utf-8”);
- text/html代表着 响应的东西是 text类型,而浏览器的解析是html类型
- 设置了tomcat的输出编码格式
- 设置了浏览器的显示编码格式
响应数据
字符输出流
PrintWriter writer = resp.getWriter();
writer可以多次写出!
writer.print**(****"<h1>****注册成功****</h1>"****)**; writer.print**(****"<a href='/store/html/login.html'>"****)**; writer.print**(****"****跳转到登录页面****"****)**; writer.print**(****"</a>"****)**;
字节输出流
- getOutputStream();
细节掌控
- 配置文件
- web-inf 文件夹对外隐藏!
- 导包 web-inf 下创建 lib 注意这个lib 不能写成别的名字,更不能加s这个lib包的名字和位置是固定的
- 更新一定重启
- html页面和web项目没在一起。页面中的路径写全!
- 一个servlet成功和失败都应该返回json字符串,不应该一个json 一个普通的字符串
- 建议成功和失败返回结果对称!我们可以定义结果封装类! 将这个封装类转成JSON字符串返回给客户端
转发和重定向
转发的介绍
- 为什么转发 和 重定向
- 因为一个servlet或者一个页面不是一个完整的动作 (删除 + 查询)
- 一个servlet的操作,需要另外一个辅助!
- 为了考虑职责单一!我们会将代码分开写!业务和显示
- 他们的作用
- 从一个资源(servlet)跳转到另外一个资源(servlet/html)!
- 串联多个资源,完成一个动作!
- 发起方(客户端)拿到的结果是最后一个资源返回(html/servlet json字符串)
- 转发的理解
- 转发属于一次请求动作,效率较高!
- 转发是由request发起!
- 转发客户端的路径不变!
- 转发只能到当前项目中的资源!
- 转发的路径是项目中的相对路径! 写法: /资源路径 不用写项目路径
- 转发属于一次请求
- 转发是属于服务器的内部操作,浏览器的路径不会被改变
- 转发最后拿到的结果是最后一个资源的结果
- 转发的语法操作:
- 一共两步 :
- 先获取一个转发器 request.getRequestDispacther(相对路径)
- 调用转发器 forward(请求和响应);
- 转发的动作一定在响应的部分写
重定向
- 重定向的理解
- 重定向数多次请求
- 重定向是响应对象发起
- 重定向浏览器路径会发生改变
- 重定向可以访问当前项目和其他项目
- 重定向效率偏低
- 重定向的状态码不是200,而是302;响应头location = url 注意:重定向的路径为全路径
- 在同一项目之中,则/项目/访问 外部项目则全路径
- 重定向语法:
- response.sendRedirect(绝对路径 /项目根路径/ 其他项目全路径)
- 获取项目的根路径:req.getContextPath();
- 重定向和转发的比较:
- 发起对象不一样 转发 请求 重定向 响应
- 访问区域不一样 转发 本项目 重定向 本项目和其他项目
- 浏览器路径 转发 不改变 重定向 改变
- 请求次数 转发 一次 重定向 多次
- 返回状态码 转发 200 重定向 302
- 效率 转发 高 重定向 低
- 使用场景:
- 项目外部的资源 重定向 (支付、第三方登录)
- 其余全部转发~
会话管理
会话的介绍
需求
- http是一次性 http的请求是无状态、使用一次的数据不会被保留!
- 有一些数据需要保存!但是http做不到! 账号登录!
会化的概念:
- 什么是会话:
- 客户端 — 服务端的持续交流的过程!
- 多次请求 形成一个会话!
- 对于服务器来说 可能同时存在很多会话!
- 为什么抽象出会话的这个概念
- 会话就是一个请求的容器单位!
- 为了几个多个请求装到一个会话中
- 可以以会话为单位!进行内部的数据共享(登录一次!其他的请求共享!)
- 会话代表着什么
- 一次完整的业务逻辑(多次请求组成)
- 一个用户正在操作服务器的数据!
- 会话的开始以及结束
- 开始:打卡浏览器,第一次访问 代表着开始!
- 持续:当前浏览器对这个页面的所有操作!一次会话!
- 结束:关闭浏览器
会话的应用
会话代表一个用户正在和服务器进行一次完整业务的交互过程!
抽象出会话以后!可以做会话这个单位级别的数据存储!
会话内的组成请求都可以获取数据!
一个业务流程可以共享数据了!
会话的存储方式:
- cookie 客户端存储
- session 服务器存储
Cookie
- Cookie 的理解:
- 当打开浏览器第一次服务器发起请求 第一次的请求,没有cookie
- 服务器创建了一个cookie对象,通过响应头!传递到客户端
- 客户端将cookie保存起来!存在浏览器的运行内存!
- 下次请求带着所有的cookie到服务器中!
- 服务器可以通过请求对象获取cookie对象!变相的共享数据!
- 数据的存储
- cookie的使用:
- 首先创建一个Cookie对象:
- Cookie cookie = new Cookie(string name,stirng value); name和value代表的key-value值
- 通过响应,将cookie对象加载到响应头之中,将其带给浏览器
- response.addcookie(cookie);
- cookie如何读取数据:
- 通过请求对象获取
- Cookie [] cookies = requst.getCookies();
- cookie解析
- cookie.getName(); cookie.getValue();
- Cookie的存活时间
- 通过setMaxage(设置存活时间);
- response.addCookie(cookie); 开始计算时间!
- 默认活 -: 一次会话 默认情况下cookie存在浏览器的运行内存中!默认的存活时间是一次会话
- 指定存活时间 正数 单位是秒: 将cookie写到磁盘中!指定存活时间!
- 销毁cookie: 0 立即销毁!
- Cookie的path
- path决定了cookie根绝哪个请求路径走
- 跟相同路径 或者 自己的 子路径
- cookie的path设置为项目的根路径即可! /项目根路径 setPath();
- cookie默认path = servlet的上一层!
session
session的理解:
- 是一种会话级别的数据存储技术!(纯 他的周期不调 最长是一次会话!)
- 存储在服务端的技术!
- session的实现一定依赖cookie!
- 客户端向服务端发起请求,服务端创建一个session!
- 先检查是否包含jsessionid的cookie
- 有:获取sessionid 获取服务器中的session!
- 获取的到
- session还没有过期!
- 获取到原来的session
- 获取不到
- session过期了
- 创建一个新的session和新的cookie
- cookie写到客户端即可!
- 没有
- 创建一个新的session和新的cookie
- cookie写到客户端即可
session的使用:
存数据
首先需要一个session对象:
- 获取session request.getSession();
再给session设置参数
- session.setAttribute(key,value object);
session不需要将其加载到response之中,会自己在响应头之中 返回一个cookie类型的JSESSIONID
这个JSESSIONID是在调getSession的时候,检测有没有,没有就自己生成,并将其返回
数据的读取:
- 获取session request.getSession();
- session.getAttribute(key,value object);
session的存活时间:
默认30分钟
局部修改 session.setMaxInactiveInterval**(30)**; 单位秒
全局修改 在xml文件中修改
- <session-config> - <!-- 分钟 --> - <session-timeout>1</session-timeout> - </session-config>
Cookie和session的对比
- 只要是会话级别的存储 session
- 跨会话存储使用cookie (记住账号、历史记录)
过滤器
介绍
过滤器和Servlet一个级别! Filter Servlet Listener WEB三剑客
servlet 可以被访问的资源!出现在访问链的后端 终端
filter过滤请求和响应 ,出现在访问链的 中间
在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ includefile="/index.jsp"%>的情况。
到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要forward的时候也用到Filter,这样就需要如下配置。<filter> <filter-name>TestFilter</filtername> <filter-class>anni.TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filtername> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> <dispatcher>EXCEPTION</dispatcher> </filter-mapping>
作用
- 放行 可以提前修改请求和响应 (设置编码格式,cors,追加文件!)
- 通过 filterChain.doFilter(req ,resp); 实现放行
- 注意,每一个过滤器的放行,都必须添加进去,否则所有的请求都会被拦截下来
- 判断拦截 需要提前验证请求的数据是否符合规则,决定放行或者转发重定向(权限验证)
使用
- 创建一个类 实现 Filer接口
- 实现所有方法
- web.xml中或者注解的方式声明
- 注意:拦截的路径设置,不要添加,不要添加项目的根路径 /资源路径
- 优先级的问题:
- 在xml 之中的配置,要求的是 mapping的顺序决定优先级
- 在注解之中, 通过filter的首字母
- xml和注解相比较而言,xml高于注解
过滤器的作用:
设置编码格式:encoding 放行修改
- req.setCharacterEncoding(“utf-8”)
- resp.setContextType(“text/html;charset=utf-8”)
- 在前后端的分离之中 :resp.setContextType(“application/json;charset=utf-8”)
- 注意:设置完编码格式之后,必须放行
做权限的验证 判断拦截
账号是否登录成功
是否符合访问规则
自动登录
- 在第一次登录的时候,创建一个Cookie ,用来保存账号和密码,并设置这个Cookie的路径以及设置他的存活时间
- cookie.setPath(req.getContextPath())
- cookie.setMaxAge(秒为单位)
- 然后再设置过滤器给登录的界面
- 因为Http请求会携带浏览器缓存的Cookie ,因此,在对该项目的登录发起请求的时候,会携带本地的Cookie,获取Cookie,对其进行验证,然后将账号和密码保存到session里面。
同源策略 放行修改
问题:外部的页面无法得到Web项目的Json数据
- 原因:就是因为 同源策略
同源策略:浏览器判断当前资源和目标资源ip和port是否一致!
- 一致:一个源 可以访问
- 不一致:不同一个源 不允许访问拦截
解决方案:
变成同一个源:把前端页面放在我们的web项目 (前后没有分离)
服务端告诉浏览器允许其他源进行访问!你别拦截了(CORS)
- 添加过滤器,设置参数
resp.setHeader("Pragma","No-cache"); resp.setHeader("Cache-Control","no-cache"); resp.setHeader("Access-Control-Allow-Origin", "*"); //ip port resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, HEAD, DELETE, PUT"); resp.setHeader("Access-Control-Max-Age", "3600"); resp.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Accept, Origin, User-Agent, Content-Range, Content-Disposition, Content-Description"); resp.setDateHeader("Expires", -10);
Listener 监听器
- 主要记忆两个监听:
- HttpSessionListener 监听的是session的创建和销毁
- ServletContextListener 监听的是全局,即tomcat的启动和销毁
- 学习监听器,就是利用他的特性进行初始化和销毁工作
- 那么就可以根据这个特性,做一些前置操作 通过监听session 的创建和销毁来监听在线的人数
- Listener的使用
- 创建一个类实现对应的Listener接口
- 添加未实现的方法
- 注册监听器
- 注解 @WebListener
- web.xml 中配置监听:只需要声明就行了