一、 Listener监听器
Javaweb中的监听器是用于监听web常见对象HttpServletRequest,HttpSession,ServletContext
作用:
监听web对象的创建与销毁
监听web对象的属性变化
监听session绑定javaBean操作
监听机制的相关概念
事件----一件事情
事件源---产生这件事情的源头
注册监听---将监听器与事件绑定,当事件产生时,监听器可以知道,并进行处理。
监听器---对某件事情进行处理监听的一个对象
Servlet的监听器:
- 监听ServletContext,HttpSession,ServletRequest
- 事件源和监听器绑定的过程:通过配置完成.
Servlet中的监听器:提供了8个监听器.
一类:监听三个域对象的创建和销毁的监听器.3个
二类:监听三个域对象的属性变更的监听器.(属性添加,属性移除,属性替换)3个.
三类:监听HttpSession对象中的JavaBean的状态的改变.(绑定,解除绑定,钝化和活化)2个
1. 一类监听器:监听三个域对象的创建和销毁的监听器
1.1 ServletContextListener:监听ServletContext对象的创建和销毁.
【方法】
【问题】
ServletContext对象何时创建和销毁:
- 创建:服务器启动时候,服务器可以为每个WEB应用创建一个单独的ServletContext.
- 销毁:服务器关闭的时候,或者项目从服务器中移除.
【入门案例】
1.编写一个类实现监听器的接口.
public class MyServletContextListener implements ServletContextListener{
@Override
/**
* 监听ServletContext对象的创建的方法:
* @param sce
*/
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象被创建了...");
}
@Override
/**
* 监听ServletContext对象的销毁的方法:
* @param sce
*/
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext对象被销毁了...");
}
}
- 通过配置完成监听器和事件源的绑定.
<!-- 配置监听器 -->
<listener>
<listener-class>com.itheima.weblistener.MyServletContextListener</listener-class>
</listener>
【企业中应用】
- 初始化工作.
- 加载配置文件:Spring框架.
ContextLoaderListener: - 定时任务调度:
Timer,TimerTask.
1.2 HttpSessionListener:监听HttpSession对象的创建和销毁的监听器.
【方法】
【问题】
- HttpSession对象何时创建和销毁的?
- 创建:服务器第一次调用getSession()方法的时候.
- 销毁:
- 非正常关闭服务器(正常关闭序列化到硬盘)
- session过期了(默认30分钟)
- session.invalidate()
【入门】
- 编写监听器:
public class MyHttpSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("HttpSession对象被创建了...");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("HttpSession对象被销毁了...");
}
}
- 配置监听器:
<listener>
<listener-class>com.itheima.weblistener.MyHttpSessionListener</listener-class>
</listener>
【问题】
1.访问html是否创建session对象? 不会
2.访问一个Servlet是否创建session对象? 不会
3.访问一个jsp是否创建session对象? 会
1.3 ServletRequestListener:监听ServletRequest对象的创建和销毁的监听器
【方法】
【问题】
ServletRequest对象何时创建和销毁?
- 创建:客户端向服务器发送请求的时候.
- 销毁:服务器为这次请求作出了响应时候.
【入门】
1.编写一个监听器
public class MyServletRequestListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("ServletRequest被创建了...");
}
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("ServletRequest被销毁了...");
}
}
- 配置监听器
<listener>
<listener-class>com.itheima.weblistener.MyServletRequestListener</listener-class>
</listener>
【问题】
1.访问html是否创建request对象? 会
2.访问一个Servlet是否创建request对象? 会
3.访问一个jsp是否创建request对象? 会
2. 二类:监听三个域对象属性变更的监听器
2.1 ServletContextAttributeListener:监听ServletContext对象中的属性变更的监听器
2.2 HttpSessionAttributeListener:监听HttpSession对象中的属性变更的监听器
2.3 ServletRequestAttributeListener:监听ServletRequest对象中的属性变更的监听器
3. 三类:监听HttpSession中的JavaBean的状态改变的监听器.(绑定,解决绑定,钝化,活化)
三类监听器非常特殊:
监听器作用在JavaBean上.JavaBean可以自己感知在session中状态.
这类监听器不用配置.
3.1 HttpSessionBindingListener:监听HttpSession中的JavaBean的绑定和解除绑定的状态.
3.2 HttpSessionActivationListener:监听HttpSession中的JavaBean的钝化和活化的状态.
sessionDidActivate(HttpSessionEvent se); -- 活化
SessionWillPassivate(HttpSessionEvent se); -- 钝化
通过配置序列化session:
context.xml
- tomcat/conf/context.xml: 对tomcat中的所有虚拟主机和虚拟路径生效.
- tomcat/conf/Catalina/localhost/context.xml: 对tomcat下的localhost虚拟主机中的所有路径生效.
- 工程的META-INF/context.xml: 对当前的工程生效.
<?xml version="1.0" encoding="UTF-8"?>
<!--
maxIdleSwap :1分钟 如果session不使用就会序列化到硬盘.
directory :itheima 序列化到硬盘的文件存放的位置.
-->
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="itheima"/>
</Manager>
</Context>
4. 监听器的总结:
Servlet的监听器分成三类8个:
- 一类:监听三个域对象的创建和销毁的监听器.
- 二类:监听三个域对象的属性的变更.
- 三类:监听HttpSession中JavaBean的状态的改变
5. 案例-定时删除过时订单分析
功能描述:
若一个订单从下单开始超过30分钟未支付,则删除该订单。
分析:
为了这个操作我们需要拿到订单的下单时间和支付状态.然后判断订单是否超过30分钟未支付,若未支付则取消该订单,想实现此功能,还需要使用任务调度功能.要求在项目一启动的时候就可以扫描订单.比如每分钟查找一下,将满足条件的删除掉.
步骤分析:
创建一个ServletContext创建与销毁监听器,在ServletContext对象创建时,启动定时扫描器.
在定时器内部实现查询订单及删除订单操作
public void contextInitialized(ServletContextEvent sce) {
//一旦服务器启动 该定时器就开始扫描
Timer t=new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
//完成订单查询和删除操作即可
System.out.println(".........");
}
}, 1000, 2000);//延迟1秒开始执行,每2秒执行一次
}
二、Filter过滤器
1. filter介绍及其作用介绍
Filter是sun公司中servlet2.3后增加的一个新功能.
Servlet规范中三个技术 Servlet Listener Filter
在javaEE中定义了一个接口 javax.servlet.Filter来描述过滤器
作用:
通过Filter可以拦截访问web资源的请求与响应操作.
WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
常用api:
filter入门案例
创建步骤:
- 编写filter
a. 创建一个类实现javax.servlet.Filter接口
b. 重写接口方法 - 编写配置文件
a. 注册filter
b. 绑定路径
Filter在web.xml文件中配置的目的:配置拦截什么样的资源。
Filter初始化
<filter>
<filter-name>demo1Filter</filter-name>
<filter-class>cn.itcast.web.filter.Demo1Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>demo1Filter</filter-name>
<url-pattern>/demo1</url-pattern>
</filter-mapping>
拦截分析:
注意:
在Filter的doFilter方法内如果没有执行,那么资源是不会被访问到的。
FilterChain功能介绍
FilterChain 是 servlet 容器为开发人员提供的对象,它提供了对某一资源的已过滤请求调用链的视图。过滤器使用 FilterChain 调用链中的下一个过滤器,如果调用的过滤器是链中的最后一个过滤器,则调用链末尾的资源。
2. filter链与生命周期
filter链介绍
多个Filter对同一个资源进行了拦截,那么当我们在开始的Filter中执行 chain.doFilter(request,response)时,是访问下一下Filter,直到最后一个Filter执行时,它后面没有了Filter,才会访问web资源。
如果有多个Filter形成了Filter链,那么它们的执行顺序是怎样确定的?
它们的执行顺序取决于<filter-mapping>在web.xml文件中配置的先后顺序。
filter生命周期
当服务器启动,会创建Filter对象,并调用init方法,只调用一次.
当访问资源时,路径与Filter的拦截路径匹配,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法.
当服务器关闭时,会调用Filter的destroy方法来进行销毁操作.
4. FilterConfig介绍
Filter功能介绍
在Filter中的init方法上有一个参数叫FilterConfig,是Filter的配置对象
作用:
获取初始化参数
获取filter的名称
获取全局管理者(SerlvetContext对象)
常用api
5. filter配置详解
Filter基本配置介绍
<filter>
<filter-name>filter名称</filter-name>
<filter-class>filter类全名</filter-class>
</filter>
<filter-mapping>
<filter-name>filter名称</filter-name>
<url-pattern>映射路径</url-pattern>
</filter-mapping>
url-pattern配置
完全匹配 :
要求必须以"/"开始.
目录匹配:
要求必须以"/"开始,以*结束.
扩展名匹配:
不能以"/"开始,以.xxx结束.例如:.jsp *.do
关于servlet-name配置
针对于servlet拦截的配置 <servlet-name>配置
在Filter中它的url-pattern配置项上有一个标签
<servlet-name>它用于设置当前Filter拦截哪一个servlet。
是通过servlet的name来确定的。
关于dispatcher配置
可以取的值有 REQUEST FORWARD ERROR INCLUDE
作用:
当以什么方式去访问web资源时,进行拦截操作.
REQUEST
当是从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置的 它也是默认值
FORWARD
它描述的是请求转发的拦截方式配置
ERROR
如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
INCLUDE
如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
7.Filter案例
7.1自动登录功能
之前的登录流程:
login.jsp-->loginServlet-->UserService-->UserDao
现在分析下自动登录的原理
a.当用户登录成功之后,判断一下用户是否勾选了自动登录,若勾选了,将用户名和密码通过cookie持久化到浏览器上.
b.做一个filter,filter的作用为:判断cookie中是否有用户名和密码,若有拿过来调用service完成登录操作
c.filter放行
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//1.强转
HttpServletRequest req=(HttpServletRequest) request;
HttpServletResponse resp=(HttpServletResponse) response;
//2.逻辑
//首先判断session中是否有user 若没有再继续操作
User user = (User) req.getSession().getAttribute("user");
if(user==null){//session中没有用户 需要继续操作
//获取请求路径 若是login和regist的不需要自动登录
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
String path=uri.substring(contextPath.length());
//System.out.println(path);
if(!("/login.jsp".equals(path)||"/login".equals(path)||"/regist.jsp".equals(path)||"/regist".equals(path))){
//查找是否有自动登录的cookie
Cookie c = CookieUtils.getCookieByName("autoLogin", req.getCookies());
if(c!=null){
//若有 调用service 自动登录
//拿到用户名和密码
String username=c.getValue().split("-")[0];
String password=c.getValue().split("-")[1];
//调用service登录
user = new UserService().login(username, password);
//若登录成功 将user放入session中
if(user!=null){
req.getSession().setAttribute("user", user);
System.out.println("自动登录..........");
}
}
}
}
//3.放行
chain.doFilter(req, resp);
}
注意:
cookie中不能存放中文,若出现中文还需编码解决
7.2编码过滤器
思路:在Filter中对request进行功能增强,让它处理了乱码问题,再将request传递到servlet中,这样在servlet中获取请求参数就不会乱码。
如何进行功能加强?
-
三种方式:
继承
装饰者模式
动态代理
装饰模式实现步骤:
a.装饰类与被装饰类要继承同一个父类或实现同一个接口。
b.在装饰类中重写方法,进行功能增强
c.在装饰类中持有一个被装饰类的对象
在EncodingFilter中已经创建了一个MyReqeust,它是一个HttpServletRequest的装饰类,而我们在chain.doFilter(MyRequest,response);
也就是在servlet中使用的request其实是装饰类。
对于我们通过reqeust对象获取请求参数有三种方式:
getParameter
getParameterValues
getParameterMap
我们不需要将这三个都进行编码处理,只需要对getParameterMap进行乱码处理,而getParameter及getParameteValues可以依赖于getParametreMap的实现
class MyRequest extends HttpServletRequestWrapper{
private HttpServletRequest request;
private boolean flag=true;
public MyRequest(HttpServletRequest request) {
super(request);
this.request=request;
}
@Override
public String getParameter(String name) {
if(name==null || name.trim().length()==0){
return null;
}
String[] values = getParameterValues(name);
if(values==null || values.length==0){
return null;
}
return values[0];
}
@Override
/**
* hobby=[eat,drink]
*/
public String[] getParameterValues(String name) {
if(name==null || name.trim().length()==0){
return null;
}
Map<String, String[]> map = getParameterMap();
if(map==null || map.size()==0){
return null;
}
return map.get(name);
}
@Override
/**
* map{ username=[tom],password=[123],hobby=[eat,drink]}
*/
public Map<String,String[]> getParameterMap() {
/**
* 首先判断请求方式
* 若为post request.setchar...(utf-8)
* 若为get 将map中的值遍历编码就可以了
*/
String method = request.getMethod();
if("post".equalsIgnoreCase(method)){
try {
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if("get".equalsIgnoreCase(method)){
Map<String,String[]> map = request.getParameterMap();
if(flag){
for (String key:map.keySet()) {
String[] arr = map.get(key);
//继续遍历数组
for(int i=0;i<arr.length;i++){
//编码
try {
arr[i]=new String(arr[i].getBytes("iso8859-1"),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
flag=false;
}
//需要遍历map 修改value的每一个数据的编码
return map;
}
return super.getParameterMap();
}
}