05).30分钟学会Servlet+过滤器+监听器+实际案例
一、Servlet简介:
Servlet程序处理流程
二、Servlet程序实现
实现要求
Servlet属于java ee范畴,而java和javac属于java se范畴,要想编译servlet必须配置java ee的开发包:将tomcat的lib包下的servlet-api.jar复制到jdk的文件夹下。
程序实例
WEB-INF/classes文件夹下编译此程序。现在一个servlet程序就算开发完了,但是这个servlet并不能被外部直接访问,因为还缺少一个映射的配置(WEB-INF/web.xml),下图紫色部分即为映射配置
表示映射路径为:项目根目录/HelloServlet,注意,配置后服务器需要重新启动。
使用response的getWriter()方法得到指向浏览器的字符流时,如果输出内容为中文,为避免乱码,需要设置输出数据的类型及编码
但是,servlet一般不作为输出页面来使用,因为太麻烦了,而是作为一些中间的控制逻辑层使用。同时servlet还有一个特点,即可为servlet配置多个任意映射路径,只需要配置多个<servlet-mapping>即可
“*”代之任意名称
xixi/后面可跟任意名称,均指向servlet
三、servlet与表单的交互
1, 表单
InputServlet
注意,表单提交方式为“get”对应servlet的doGet()方法,如果表单提交方式为“post”,则servlet还必须覆写doPost()方法。doPost()与doGet()方法的方法体一样,只是对应不同的提交方法
InputServlet的web.xml映射配置,注意,InputServlet的映射路径必须与表单的提交路径一致,表单默认提交根路径为表单所在文件夹
四、servlet生命周期
1, 要点
2, 生命周期
生命周期对应方法
编写servlet代码并配置映射路径
当浏览器第一次打开Life这个servlet的时候,会执行init()函数,输出1、初始化--->init
以后,每当浏览器刷新一次或者调用Life时候,都会执行一次doGet或者doPost输出2、服务--->doGet/doPost
当关闭服务器即容器的时候,会执行destroy()函数,输出3、销毁--->destroy 并延迟三秒
其中容器关闭,或者执行了reload操作,或者长时间不使用servlet都可能调用销毁函数
对于httpServlet而言,里面的service()方法的主要功能是区分get请求和post请求,并调用相应doGet()或doPost
如果覆写了service()则不具备区分能力,doGet()和doPost()都失效。所以一般不要覆写service()方法
Init()方法一般是在第一次使用servlet时调用,其实也可以通过配置web.xml文件使其在容器启动时调用
则启动容器时
小结:
五、取得初始化配置信息
1, 要点
2, servlet编写
取得名称为“chen”的初始化参数,需要在web.xml中配置GetInitParam的映射路径和参数“chen”
当打开InitParam时
注意,如果同时覆写了有参和无参的init()函数,则只有取得初始化参数的init()方法有作用
六、取得其他内置对象
1, 要点
2, servlet本身只有request和response两个内置对象,可以通过request对象取得session对象
编写GetSession代码,并配置映射路径
当浏览器访问此servlet的时候
如何取得application对象:HttpServlet的父类提供getServletContext()方法,可以取得application对象
其实还可以通过req.getSession().getServletContext()取得application对象
七、servlet跳转
1, 要点
2, 客户端跳转:利用response.sendRedirect(url)重定向完成,配置映射并且尝试接收request和session类属性
只能传递session范围的属性
3,服务器端跳转:请求转发
可以取得request范围的属性
服务器端跳转在开发中的应用率高达95%:
取得RequestDispatcher对象的方法
3, sendRedirect()与requestDispatcher区别
sendRedirect()跳转是通过客户端发送命令完成的,而requestDispatcher()跳转是在服务器端完成的。所以前者对浏览器是可见的,后者对于浏览器不可见
sendRedirect()跳转是地址重定向,因此可跳转的范围是任意的,可以是服务器本地资源也可以是网络资源如百度等。RequestDispatcher()跳转是将一个请求转发,因此可跳转的范围服务器本地资源
sendRedirect()跳转通过地址重写的方式传递参数,二requestDispatcher()通过域属性传递参数
两者共同点:在写URL时,“***”代表从当前页面所在目录寻找资源***,“/***”代表从项目根目录下寻找***
以上异同点其实也是客户端跳转个服务器跳转的异同点
八、web开发模式:mode Ⅰ与mode Ⅱ.整个web开发的核心结构
1, modeⅠ
在javabean包含了专门处理数据的操作,数据层主要以DAO为主。modeⅠ的开发时,必须javabean和jsp一起开发完成后才能使用,这样容易出现推诿问题。所以,modeⅠ适合快速开发,但不方便后期维护。
2,modeⅡ
servlet可以接受用户请求参数,并调用相应的javabean,javabean中包含业务逻辑处理和DAO数据层处理,
最后将处理的结果交给jsp显示,这样不仅性能更高,安全性也高,同时也避免了servlet的缺点。
这样,由javabean完成具体的单个功能,jsp完成显示的功能,而servlet负责连接javabean和jsp。即jsp—>servlet->javabean.这种设计分工明确,适合大型项目开发,且维护方便。
2,MVC模型
MVC处理流程:其中最重要的就是输出和跳转问题
在MVC设计模式中,由于所有内容都要交给jsp显示且显示一次即可,从性能和可用性出发,最方便的属性范围便是request,这也是request属性的主要功能。而session范围主要是用于登陆验证。
3,小结
4,MVC开发实例:用户登录程序的开发
操作流程
设计清单
首先创建用户数据库
然后编写javabean和servlet
User
package com.chen.ying.dao; public class User { private String userid; private String name; private String password; public String getUserid() { return userid; } public void setUserid(String userid) { this.userid = userid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
databaseConnection
package com.chen.ying.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DatabaseConnection { private static final String DBDRIVRER="com.mysql.jdbc.Driver"; private static final String DBURL="jdbc:mysql://localhost:3306/mldn"; private static final String DBUSER="root"; private static final String DBPASSWORD="1999922"; private Connection conn=null; public DatabaseConnection() throws Exception { Class.forName(DBDRIVRER); this.conn=DriverManager.getConnection(DBURL,DBUSER,DBPASSWORD); } public Connection getConnection(){ return this.conn; } public void closeConnection() throws Exception{ this.conn.close(); } }
IUserDAO
package com.chen.ying.dao; public interface IUserDAO { public boolean findUser(User user)throws Exception;//定义查找用户接口 }
UserDAOImpl
package com.chen.ying.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class UserDAOImpl implements IUserDAO{ Connection conn=null; PreparedStatement pstmt=null; public UserDAOImpl(Connection conn){ this.conn=conn;//不具体连接数据库 } public boolean findUser(User user)throws Exception{ boolean flag=false; String sql="select userid,name,password from user where userid=? and password=?"; pstmt=this.conn.prepareStatement(sql); pstmt.setString(1, user.getUserid()); pstmt.setString(2, user.getPassword()); ResultSet rs=pstmt.executeQuery(); if(rs.next()){ user.setName(rs.getString(2)); flag=true; } return flag; } }
Servlet
package com.chen.ying.servlet; import com.chen.ying.dao.*; import java.io.*; import java.util.ArrayList; import java.util.List; import javax.servlet.*; import javax.servlet.http.*; public class LoginServlet extends HttpServlet{ public void doGet(HttpServletRequest req,HttpServletResponse res) throws ServletException, IOException{ String path="Login.jsp"; String userid=req.getParameter("userid");//接收请求参数 String password=req.getParameter("password"); List<String> list=new ArrayList<String>();//用于收集信息 if(userid==null||"".equals(userid)){//判断请求参数是否合法 list.add("用户id不能为空"); } if(password==null||"".equals(password)){//判断请求参数是否合法 list.add("密码不能为空"); } if(list.size()==0){//没有错误记录 User user=new User(); user.setUserid(userid); user.setPassword(password); try{ IUserDAO dao=DAODFactory.getIUserDAOInstance(); if(dao.findUser(user)){//判断请求参数是否合法,并调用javabean list.add("登陆成功,欢迎:"+user.getName()); }else{ list.add("登陆失败,用户名或密码错误"); } }catch(Exception e){ list.add("测试有误####"); System.out.println(e); } } req.setAttribute("result", list); req.getRequestDispatcher(path).forward(req, res);//将处理结果交给jsp显示 } public void doPost(HttpServletRequest req,HttpServletResponse res) throws ServletException, IOException{ this.doGet(req, res); } }
Login.jsp
九、 过滤器
1, servlet一共分为三种:普通servlet,过滤servlet,监听servlet。要点:
2, 要完成过滤器开发,必须让一个类实现Filter接口,此接口是javax.servlet包中,而并非在javax.servlet.http协议下,故此接口主要功能是完成公共协议操作。
Filter接口的操作方法:
主要功能就是用于限制用户访问某些资源或在响应请求时提前处理某些资源(如设置request编码,response编码),达到一个过滤的作用。过滤器可以以链的形式多次过滤。
3, 简单过滤器实例
Filter代码
package com.chen.ying; public class simpleFilter implements Filter{ @Override public void init(FilterConfig arg0) throws ServletException { String param=arg0.getInitParameter("filter");//取得初始化参数 System.out.println("初始化Filter--->"+param); } @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { System.out.println("完成一次过滤操作"); } public void destroy() { System.out.println("销毁过滤器"); } }
Filter配置
配置方法与servlet类似,但要放在servlet之前,其中<url-pattern>表示的是过滤路径,/*即表示对项目全部目录过滤。过滤器可以对多个路径下的文件进行过滤,通过设置多个<filter-mapping>
过滤机制:当编写好Filter并配置好对哪些资源进行过滤后,web服务器会在每次调用资源的service()方法之前,
先调用filter的doFilter()方法,在doFilter()方法中可实现调用目标资源之前先执行一段代码(过滤),执行目标资源后再让一段代码执行
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对 象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法(且无其他dofilter方法),则web服务器就会调用web资源的service方 法,即web资源就会被访问,否则web资源不会被访问
过滤器会在服务器启动时自动调用init()方法,完成初始化
此后,每一次访问过滤范围内的路径时,都会调用doFilter()方法进行过滤
当服务器关闭时,调用destroy()方法
4, 过滤链FilterChain
web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用 时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了 FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个 filter,如果没有,则调用目标资源。
5,编码过滤与登陆验证
编码过滤:通过编码过滤,可以对一个项目的中所有页面或servlet设置编码,从而减少重复代码的编写。
每次在访问过滤路径的资源之前,都会先调用紫色框内的代码进行编码过滤或者说编码设置,如果没有其他的filter,
则在执行chain.doFilter()时,便会调用目标资源。
登陆验证:验证一次访问是否处于登陆状态,一般是通过编写session属性判断语句,但如果每个页面都编写session属性判断语句,就变得复杂冗余了。可以在需要登陆验证的页面加一个过滤器,统一验证,则每次访问这些页面之前,首先得进行登陆验证。
十、 监听器
1,监听器概念:javaWeb中监听器是一个特殊的类,用于监听ServletContext,httpSession,ServletRequest等域对象的创建、销毁以及域对象属性的修改,并在监听对象发生情况时,监听器的某个对应方法立即被执行。
2,web监听器监听的事件源分别为ServletContext,HttpSession,ServletRequest,在这三种对象的监听类型可分为:
监听域对象创建与销毁的监听器
监听域对象属性的增加、删除,替换(增加同名属性)的监听器
监听绑定到HttpSession域中某个对象的状态的监听器
3,对ServletContext对象的监听:需要实现ServletContextListener接口
监听器代码
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class MyServletContextListener implements ServletContextListener { public void contextInitialized(ServletContextEvent event) {//event对象中封装了监听源的创建信息System.out.println("ServletContext对象被创建了:"+event.getServletContext().getContextPath()); } public void contextDestroyed(ServletContextEvent event) { System.out.println("ServletContext对象被销毁了"+event.getServletContext().getContextPath()); try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
监听器配置
此后,每当服务器启动时,便会自动创建一个ServletContext对象,服务器关闭时都会将这个对象销毁。ServletContext对象的创建、销毁都会被MyServletContextListener监听并执行相应代码
5,对ServletContext属性的监听:实现ServletContextAttributeListener接口
代码实现
import javax.servlet.ServletContextAttributeEvent; public class ServletContextAttributeListener implements ServletContextAttributeListener { public void attributeAdded(ServletContextAttributeEvent event) { System.out.println("监听到有新的Context属性增加--->"+event.getName()+"=="+event.getValue()); } public void attributeRemoved(ServletContextAttributeEvent event) { System.out.println("监听到有Context属性被删除--->"+event.getName()+"=="+event.getValue()); } public void attributeReplaced(ServletContextAttributeEvent event) { System.out.println("监听到有Context属性被替换--->"+event.getName()+"=="+event.getValue()); } }
监听器配置
属性修改
在tomcat控制台可以每隔三秒,监听器监听到ServletContext属性变化,并做出反应
6,对与session对象及其属性的监听,servlet提供了三个实现接口
对session对象的监听:需要实现httpSessionListener接口,并实现sessionCreated()和sessionDestroyed()
import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class MySessionListener implements HttpSessionListener { public void sessionCreated(HttpSessionEvent event) { System.out.println("session属性被创建了:id="+event.getSession().getId()); } public void sessionDestroyed(HttpSessionEvent event) { System.out.println("session属性被销毁了:id="+event.getSession().getId()); } }
当浏览器第一次连接到服务器时,服务器会为用户自动创建一个session并分配一个session id。现在问题是创建的session什么时候销毁呢,不是在关闭浏览器的时候,而是在调用session.invalidate()或者session超时的时候销毁
超时配置
对session属性的监听:需要实现HttpSessionAttributelistener接口,方法与监听servletContext属性类似
对request对象的监听:需要实现servletRequestListener接口
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; public class MyServletRequestListener implements ServletRequestListener { public void requestInitialized(ServletRequestEvent event) { System.out.println("request对象被创建了:"+event.toString()); } public void requestDestroyed(ServletRequestEvent event) { System.out.println("request对象被销毁了:"+event.toString()); } }
用户每一次访问jsp都会创建一个request对象,当前访问结束,就会销毁掉request对象
Request属性的监听:需要实现servletRequestAttributeListener接口,方法与servletContext属性监听类似
7,感知session绑定的事件监听器
保存在session域中的对象有多种状态:被绑定(session.setAttribute(“bean”,Object));被解除(session.removeAttribute(“bean”));随session对象持久化到一个存储设备中;随session对象从一个存储设备中恢复。
Servlet 规范中定义了两个特殊的监听器接口"HttpSessionBindingListener和HttpSessionActivationListener"来帮助JavaBean 对象了解自己在Session域中的这些状态:,实现这两个接口的类不需要 web.xml 文件中进行注册。
检测对象绑定状态:当一个javanbean实现了HttpSessionBindingListener接口后,每当这个javabean对象被绑定或者解除绑定,都可以被这个javabean自身监听到,所以这个javabean是一个特殊的监听器,且不需要在web.xml
中配置
package com.chen.ying; import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionBindingListener; public class BindingListener implements HttpSessionBindingListener { private String name; public BindingListener(String name) { this.name=name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void valueBound(HttpSessionBindingEvent event) { System.out.println(this.name+"被绑定到-->"+event.getName()); } public void valueUnbound(HttpSessionBindingEvent event) { System.out.println(this.name+"取消绑定-->"+event.getName()); } }
当访问如下jsp时
8,监听器实例--在线人员统计
所有登陆人员列表应该使用集合保存,而且所有用户可以通过这个集合访问到在线人数,所以这个集合要用application对象保存。用户登录成功后应该要设置属性。所以监听器设计思路为:服务器启动时,通过监听器获得application对象,并用application对象保存一个集合属性all,以后每当有一个新的登陆用户时,便设置一个session登陆属性,此时触发监听器的attributeAdded()方法,在此方法,为集合all添加一个登陆用户名。
当会话关闭时,触发监听器的sessionDestroyed方法,在此方法中,将all中对应的登陆名删除。
故此监听器一共要实现ServletContextListener、HttpSessionListener、HttpSessionAttributeListener
监听器实现
public class OnlineUser implements ServletContextListener, HttpSessionAttributeListener,HttpSessionListener { private ServletContext app=null; public void contextInitialized(ServletContextEvent event) { System.out.println("成功创建application属性"); this.app=event.getServletContext();//得到服务器启动时创建的application对象 this.app.setAttribute("online", new TreeSet());//用appplication对象保存一个集合 } public void contextDestroyed(ServletContextEvent event) {} public void attributeAdded(HttpSessionBindingEvent event) { System.out.println("成功创建session属性"); Set all=(Set)this.app.getAttribute("online"); all.add(event.getValue());//当有session属性(登陆属性)创建时,将其添加到集合中 this.app.setAttribute("online", all); } public void attributeRemoved(HttpSessionBindingEvent event) {} public void attributeReplaced(HttpSessionBindingEvent event) {} public void sessionCreated(HttpSessionEvent arg0) {} public void sessionDestroyed(HttpSessionEvent event) { Set all=(Set)this.app.getAttribute("online"); all.remove(event.getSession().getAttribute("userid"));//注销会话时,将登陆属性删除 this.app.setAttribute("online", all); } }
Login.jsp
test.jsp
以后每打开一个新的浏览器进行登陆,会显示所有处于登陆状态的用户名,且是共享的
9,小结