@author:杜勇帅
@email:[email protected]
Servlet是运行在服务器端的java程序(类),作用是接收并处理客户端的请求,生成响应给客户端。
学习了Servlet后,jsp页面只负责展示数据,Servlet负责处理请求(调用Dao实现具体功能),并将数据转发到jsp页面上去展示(生成响应给客户端).
Servlet版本:有2.3-2.5版本,还有3.0及以上版本,只有3.0及以上版本才支持Servelt注解。@WebServlet,该注解在Servelt3.0及以上版本可以直接使用,用来标注一个类是一个Servlet. 使用Ecipse创建web项目时,Dynamic web module version就是Servlet版本,可以选择3.0以下版本和3.0版本
入门程序,我们选择Servlet2.5版本,就不能使用注解,需要在web.xml中配置Servlet,才能让Servlet为我们处理请求,生成响应。
1).创建web项目,Sevlet版本选择2.5
2)创建一个Servlet类,命名:XxxServlet,例如HelloServlet。Servlet类要继承HttpServlet
public class HelloServlet extends HttpServlet {
}
3)重写doGet和doPost()方法
public class HelloServlet extends HttpServlet {
// 处理GET请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理Get请求");
}
// 处理Post请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理Post请求");
}
}
4)在web.xml中配置Servlet
helloservlet
com.sxt.servlet.HelloServlet
helloservlet
/hello.action
4)发布项目,访问Servlet
在浏览器中的地址栏输入http://localhost:8088/0510_02_serlvetHello/hello.action,即可访问Servlet
注意:1.在web.xml中servlet-class是servlet的全限定类名(包名.类名)
2.url-pattern是servlet暴露给外界的访问路径,必须以"/"开头
1).用户在地址栏上输入servlet的完整访问路径:
http://localhost:8088/0510_02_serlvetHello/hello.action
2)web服务器接收到请求servlet的完整url后,从项目根路径后面截取出/hello.action
3)Web服务器在web.xml中查找值为/hello.action的url-pattern节点,进而找到它的兄弟节点servlet-name
4)取出servlet-name节点中的值,即servlet的别名helloservlet
5)Web服务器在web.xml中查找别名和4)中的helloservlet相同的servlet-name节点
6)查找servlet-name的兄弟节点servlet-class,取出节点中的值,即servlet的全限定类名
com.sxt.servlet.HelloServlet。接下来服务器通过反射机制创建Servlet对象
7)servlet实例创建完成后,web服务器再根据客户端请求的方法来决定调用doGet()或doPost()方法处理客户端的请求并生成响应给客户端
刚才我们创建Servlet时,servlet版本号是2.5,不支持注解。
1)创建web项目时,选择Servlet3.0版本
2)创建Servlet类时,直接New->Web--->Servlet
填写Servlet的类名
修改Servlet的访问路径,默认是/servlet类名,要求改成/xxx.action
3)生成的Servlet类如下,自带了一个注解@@WebServlet,并且重写了doGet()和doPost()
@WebServlet("/hello.action")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
注意:使用第2种方式创建Servlet,就不需要在web.xml文件中配置Servlet了,因为
@WebServlet("/hello.action")注解就已经明确的告诉web服务器servlet的url-pattern是/hello.action。用了Servlet的注解后就不能再在web.xml写配置Servlet了。即注解和web.xml配置只能用其中一种.
Servlet生命周期共经历了4个阶段:
1.实例化:第1次请求Servlet时,调用无参构造方法,web服务器创建Servlet的实例
2.初始化:第1次请求Servlet时,调用init()方法,只调用一次,在init()方法初始化Servlet中的资源
3.服务:每次请求都调用service()方法,该方法会根据请求方法类型来决定调用doGet()或doPost()
4.销毁:正常关闭服务器时销毁Servlet实例,调用destory()
1.获取请求行信息的方法
//获取请求的方法类型
String method = request.getMethod();
System.out.println("请求方法:"+method);
//获取请求的资源路径
String uri = request.getRequestURI();
//获取协议以及版本号
String protocol = request.getProtocol();
2.获取请求参数的方法
getParameter(String name):获取一个请求参数的值
getParameterValue(String name):获取一组请求参数的值,针对checkbox元素
3.设置请求编码
setCharacterEncoding(“UTF-8”)
4.获取请求头的值
getHeader(String name):根据请求头的名字获取值
5.请求转发 .xxx代表要跳转的资源路径
request.getRequestDispatcher("xxx").forward(request, response);
它就相当于jsp页面上的/jsp:forward
6.setAttribute(String name,Object value):向request域对象好存储数据
1.addHeader(String name,String value):添加响应头
2.getHeader(String name):获取响应头的值
3.setHeader(String name,String value):设置响应头
4.setContentType(String contentType):设置响应的内容类型(Mime类型)
5.setCharacterEncoding(String encoding):设置响应的编码
6.getWriter():获取打印流
7.sendRedirect(String location):重定向,跳转到一个新的资源上,地址栏的url会变化
请求转发的过程只有1次请求,地址栏的url没有变化,可以继续使用request域中的数据;
重定向的过程有2次请求(产生了新的请求),url会改变,request域中的数据就丢失了.
请求转发只能访问服务器内部资源(当前web项目下的资源),重定向可以访问外部资源
如果我们在Servlet中使用request域对象存了一个数据,再跳转到jsp页面上时希望能用EL获取出request域中的数据,此时我们只能使用请求转发来跳转.如果是用session存的数据,使用转发或重定向到jsp页面,都能取到session中的数据。
在超链接或重定向中请求Servlet时,Servlet路径前的”/”参照(代表)web服务器的根路径,会丢失了web应用的根路径。
相对路径:以当前资源所在的路径为参考位置,建立出来的路径
绝对路径:在访问的资源路径前带上web项目的根路径,此时的路径叫做绝对路径
比如index.jsp在项目的根路径下,servlet的路径/user.action也是参照项目根路径
此时在index.jsp用超链接跳转到servlte中,href=”user.action”
重定向或超链接都会改变地址栏的url,都属于客户端的跳转,发生在客户端的跳转,”/”带来的问题就是丢失web应用的根路径,因为”/”参照web服务器的根路径.
解决客户端跳转时丢失项目根路径的问题:
在”/”前加上request.getContextPath()来得到项目根路径,在jsp页面上用EL表达式${pageContext.request.contextPath}来代替request.getContextPath().
请求转发是在服务器内部的跳转,是请求转发中去访问Servlet,servlet路径前的”/”参照web项目的根路径,例如下面的:
request.getRequestDispatcher("/response.action").forward(request, response);
不管是服务器内部跳转还是客户端的跳转,如果在访问Servlet时没有带”/”,都表示相对路径,相对路径是以当前web资源所在的位置建立出来的路径,即要跳转的资源必须和当前web资源在同一个位置(同一级目录下)
总之:在做客户端的跳转(请求转发以外的跳转)时,路径前”/”代表web服务器的根路径,要防止报404,可以使用绝对路径
改造项目的目的:让jsp和Servlet各司其职,jsp负责展示数据, Servlet负责处理请求,生成响应给客户端,最终消除jsp页面上所有的脚本代码
在MVC分层开发模式中,jsp充当View(视图),Servlet充当Controler(控制器)
1.把index.jsp页面上的请求路径改成Servlet的路径
1.在QueryAllServlet中处理全查询请求,将数据存起来,再转发到列表页面
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建StudentDao的对象
StudentDao stuDao = new StudentDaoImpl();
List list = stuDao.queryAll();
//把集合保存到request域中
request.setAttribute("stulist", list);
//跳转到list.jsp页面
request.getRequestDispatcher("list.jsp").forward(request, response);
}
1.list.jsp页面只负责显示数据即可
1)add.jsp上的表单要请求Servlet
2)Servlet中处理添加的请求
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1.获取表单中的所有请求参数的值
// 解决post请求的中文乱码
request.setCharacterEncoding("UTF-8");
String sname = request.getParameter("sname");
String sex = request.getParameter("sex");
int age = Integer.parseInt(request.getParameter("age"));
// 2.把参数的值封装成学生对象
Student stu = new Student(sname, sex, age);
// 3.调用dao里面的insert方法,完成添加
StudentDao stuDao = new StudentDaoImpl();
int i = stuDao.insert(stu);
if (i > 0) {
// 添加成功,重新请求全查询的Servlet
response.sendRedirect("queryAll.action");
}
}
以上的做法是用一个Servlet只处理一个请求,当项目的模块很多时,会造成Servlet的数量很多很多。
解决办法:
在实际开发中,应该以模块为单位来建立servlet,即1个模块只创建一个Servlet,该Servlet负责处理此模块的所有请求.
一个Servlet处理某个模块的所有请求的技巧:
在访问Servlet时,多加一个参数method=xxx,xxx就是Servlet中的一个方法,xxx方法的签名要和doPost()的签名完全一致
例如:index.jsp上访问Servlet中的queryAll方法
在Servlet中添加一个queryAll*方法来处理全查询请求*
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 解决post请求的中文乱码
request.setCharacterEncoding("UTF-8");
// 获取method参数值
String method = request.getParameter("method");
if ("queryAll".equals(method)) {
this.queryAll(request, response);
} else if ("add".equals(method)) {
this.add(request, response);
}
}
// 处理全查询请求
protected void queryAll(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// System.out.println("m1方法被调用");
// 创建StudentDao的对象
StudentDao stuDao = new StudentDaoImpl();
List list = stuDao.queryAll();
// 把集合保存到request域中
request.setAttribute("stulist", list);
// 跳转到list.jsp页面
request.getRequestDispatcher("list.jsp").forward(request, response);
}
同理要处理添加功能,要把add.jsp上的表单的请求路径改成servlet路径
1)把列表页面list.jsp上”修改”超链接改成servlet路径,带method参数
2)在Servlet中添加方法toUpdate处理准备修改请求
// 处理修改前的查询,为修改做准备
protected void toUpdate(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取要修改的学生的id
int id = Integer.parseInt(request.getParameter("sid"));
// 根据id查询一个学生对象
StudentDao stuDao = new StudentDaoImpl();
Student stu = stuDao.queryById(id);
// 存到request域中
request.setAttribute("stu", stu);
// 请求转发到update.jsp上去回显数据
request.getRequestDispatcher("update.jsp").forward(request, response);
}
3)在update.jsp上只回显数据,消除了jsp脚本,把表单的请求路径改成servlet的路径,发送修改的请求
4)在Servlet中增加一个update方法,处理修改请求
// 处理修改请求
protected void update(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取表单中的参数值
String sname = request.getParameter("sname");
String sex = request.getParameter("sex");
int age = Integer.parseInt(request.getParameter("age"));
// 获取sid
int sid = Integer.parseInt(request.getParameter("sid"));
// 把参数的值封装成学生对象
Student stu = new Student(sname, sex, age);
stu.setSid(sid);
// 调用dao里的修改方法,完成修改
StudentDao stuDao = new StudentDaoImpl();
int i = stuDao.update(stu);
if (i > 0) {
// 修改成功,重新请求list.jsp
response.sendRedirect("student.action?method=queryAll");
}
}
代码优化:在doPost()中使用反射思想根据获取到的方法名来创建Method对象,并调用servlet的同名方法
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 解决post请求的中文乱码
request.setCharacterEncoding("UTF-8");
// 获取method参数值
String method = request.getParameter("method");
try {
// 用反射来动态调用Servlet中的方法,方法名就是method,获取一个方法对象
Method methodObj = this.getClass().getDeclaredMethod(method, HttpServletRequest.class,
HttpServletResponse.class);
// 用当前类的实例来调用方法
methodObj.invoke(this, request, response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
1.把列表页面list.jsp上”删除”超链接所调用的js函数中的路径改成Servlet路径
function deleteById(id){
//使用confirm弹出一个确认取消对话框
var flag= confirm("是否确认删除此信息?");
if(flag){
//发请求给jsp页面去处理删除,相当于get请求, location.href:改变(设置)地址栏上的url
location.href="student.action?method=delete&sid="+id;
}
}
2.在Servlet中添加一个delete方法来处理删除请求
// 处理删除请求
protected void delete(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
int sid = Integer.parseInt(request.getParameter("sid"));
// 调用Dao里的删除方法,完成删除
StudentDao stuDao = new StudentDaoImpl();
int i = stuDao.delete(sid);
if (i > 0) {
// 删除成功,重新请求全查询的Servlet
response.sendRedirect("student.action?method=queryAll");
}
}
因为http协议默认是无状态的,它记不住客户端的状态,无法区分请求是否是来自于同一个用户,会认为每个请求都是新的客户端发过来的请求。会话技术的作用就是实现在多个请求中共享数据,区分不同的客户端.
会话:会话是浏览器与服务器之间的一次通信,它包含浏览器与服务器之间的多次请求、响应过程,浏览器关闭后本次会话结束。
常用会话技术:Cookie和Session
Cookie:将数据存储在客户端本地,安全性不好
Session:将数据存储在服务器端,安全性很好,服务器压力大
Cookie的基本使用:
第1:服务器端创建Cookie
//服务器端创建Cookie
Cookie cookie=new Cookie("username", "zhangsan");
//服务器把Cookie发送给浏览器
response.addCookie(cookie);
//重定向到ReadServlet
response.sendRedirect("read.action");
第2:服务器接收浏览器所携带的Cookie
//服务器端接收浏览器携带的Cookie
Cookie[] cookies = request.getCookies();
for(Cookie ck:cookies){
//获取我们想要的那个Cookie,通过比较名称
if(ck.getName().equals("username")){
System.out.println("名为username的Cookie的值:"+ck.getValue());
break;
}
//System.out.println("cookie名:"+ck.getName()+",cookie值:"+ck.getValue());
}
Cookie的深入使用:
1.Cookie分为两类:会话级别的Cookie和持久级别的Cookie
会话级别的Cookie:把Cookie存储在浏览器的内存中,关闭浏览器后Cookie就销毁了
持久级别的Cookie:把Cookie存储在硬盘上,关闭后再次打开浏览器,这些cookie依然有效,直到超过设定的过期时间。
2.设置Cookie的有效时间:setMaxAge(int time):时间单位是秒
补充:1)在jsp页面上读取Cookie的值,语法:${cookie.cookie名.value} 名为username的Cookie的值:${cookie.username.value }
2)多个不同浏览器是无法共享cookie的,因为每个浏览器存储cookie的路径不一样的(在硬盘上保存Cookie的txt文件的路径不同),所以无法做到不同浏览器共享cookie。
Cookie在项目中的使用场景:
由于Cookie只保存字符串数据,在实际项目中以下几个场合中会用到Cookie:
1)记住用户名密码,实现免登录
2).记住用户的偏好设置,例如网站的背景色(皮肤)
3).其它需要保存字符串数据的场景
Sesssion的原理:第1次访问服务器时,服务器会为当前会话创建一个session对象,使用服务器端内存空间为客户端保存数据,同时给浏览器发送一个名为JSESSIONID的Cookie(相当于给你分配一个房间,再给你一把钥匙),浏览器再次访问服务器时,需要携带着名为jSessionID的Cookie去服务器上寻找属于本会话的Session. 名为JSESSIONID的Cookie的值是本次会话的唯一标识ID。
1).以key/value方式存入数据:session.setAttribute(String name,Object obj);
2).根据key取出数据:session.getAttribute(String name);
3).删除数据:session.removeAttribute(String name);
例如:
//向session中存数据
session.setAttribute("book1", "西游记");
session.setAttribute("book2", "红楼梦");
//删除数据
session.removeAttribute("book1");
//获取数据
String book1 = (String)session.getAttribute("book1");
String book2 = (String)session.getAttribute("book2");
System.out.println("book1:"+book1); //没有,因为已经删除
System.out.println("book2:"+book2);
1.创建:第一次请求Servlet且调用request.getSession()方法时创建session;如果是请求jsp页面,会自动创建session(除非你把session禁用,在page指令上加一个属性 session="false" )
2.服务:每次请求服务器端的资源,都共享同一个session,为客户端服务(给客户端存储重要的数据)
3.销毁:三种”死法”
A)服务器正常关闭
B)session过期
有效期的设置有2种方式
1).在web.xml中配置session的有效期,单位是分钟
20
2).在程序中手动设置session有效时间,单位是秒
session.setMaxInactiveInterval(10*60);
在程序中设置有效期优先级要高于web.xml中配置的有效期;如果两边都没有设置session有效期,则默认有效期是30分钟
C)手动销毁
调用session.invalidate()销毁session
处理登录请求的LoginServlet主要代码如下:
// 获取请求参数
String username = request.getParameter("username");
String userpwd = request.getParameter("userpwd");
//假登录判断,假设正确用户名是admin,密码是12345
if("admin".equals(username)&&"12345".equals(userpwd)){
//把用户信息封装到用户对象中,
User user=new User(username, userpwd, "女");
//把用户对象存到session中
HttpSession session = request.getSession();
session.setAttribute("user", user);
//跳转到后台主页面
response.sendRedirect(request.getContextPath()+ "/view/main.jsp");
}else{
//登录失败,跳转到登录页面
//错误信息存到request域对象中
request.setAttribute("errorMsg", "用户名或密码错误!");
//转发到登录页面
request.getRequestDispatcher("login.jsp").forward(request, response);
}
处理安全退出请求的LogoutServlet的主要代码如下:
HttpSession session = request.getSession();
// 安全退出,即注销,将session销毁
session.invalidate();
//跳转到登录页面
response.sendRedirect( request.getContextPath()+"/login.jsp");
总结:
Session在项目中用来保存与用户会话相关的重要数据,比如登录成功后需要在多个页面中共享用户信息,此时就用session来保存用户对象;比如购物车(购物车中的数据就相当于购物小票中的一条数据)
| 商品id | 商品名 | 价格 | 数量 | 金额 | | ------ | ------ | ---- | ---- | ---- | | 1 | 篮球 | 50 | 2 | 100 | | 2 | 足球 | 40 | 1 | 40 |
总金额:140
关闭浏览器后,再次重启浏览器,取不出session中的数据了,可能原因:
1)服务器端的session已经销毁
2)客户端名为JSESSIONID的Cookie销毁了
实际原因是2,因为服务器端在创建了session后给客户端发送过去的名为JSESSIONID的Cookie是会话级别的,在重启浏览器再次访问时就无法拿着”钥匙”去找”房间”
解决办法:把名为JSESSIONID的Cookie变成持久级别的Cookie,再发给浏览器
//把名为JSESSIONID的Cookie变成持久级别的
Cookie cookie=new Cookie("JSESSIONID",session.getId());
cookie.setMaxAge(24*60*60);
cookie.setPath(request.getContextPath());
//发送给浏览器
response.addCookie(cookie);
在Servlet创建时有时需要初始化一些配置信息,但又不能写死,怎么办呢?此时可以借助ServletConfig对象,用它来读取配置信息。使用步骤:
比如在RegisterServlet中需要初始化一个name和一个age,就写到web.xml中
在Servlet中重写init()方法,在init()方法中调用ServletConfig对象的getInitParameter()方法获取配置的初始化参数
场景1: 编写servlet,接收参数:
request.getParameter() /getParameterValues()
如果不做任何处理,就出现中文乱码问题。
解决乱码问题: request.setCharacterEncoding("utf-8");
问题:如果在项目中的每一个servlet都加上request.setCharacterEncoding("utf-8");
显示代码重复啰嗦。能不能把这部分公共代码抽取出去,放在一个地方执行????
场景2: 登录 -> 输入信息 -> 登录成功 -> 看到用户主页(欢迎xxx回来。。。)
如果用户不登录,直接访问主页,将跳转到登录页面
在其他需要登录才能访问的页面中,同样也需要加上验证用户是否登录成功的代码。
用于验证用户是否登录成功代码:
用于验证用户是否登录成功代码:
user= session.getAttribute("user");
if(user==null){ // 跳转到登录页面
}
问题: 能不能把这部分公共验证用户是否登录成功代码抽取出去,在一个地方执行??
结论: 以上两种场景出现的问题,都可以使用过滤器(Filter)解决!!!!
过滤器就是Filter,它是Servlet技术中最激动人心的技术之一,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp,Servlet, 静态图片文件或静态html文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前和之后,对访问的请求和响应进行拦截
Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪些web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
1) 调用目标资源之前,让一段代码执行
2) 决定是否调用目标资源(即是否让用户访问web资源)
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,如果调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
3)调用目标资源之后,让一段代码执行
Filter开发分为二个步骤:
第1步:编写java类实现Filter接口,并实现其doFilter方法。
public class FilterTest implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
System.out.println("我是调用目标资源前执行的代码");
chain.doFilter(request, response);//放行,调用web资源,
System.out.println("调用目标资源后执行的代码");
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
第2步:在 web.xml 文件中使用和元素对编写的filter类进行注册,并设置它所能拦截的资源。
filtertest
com.sxt.filter.FilterTest
filtertest
/*
测试:访问index.jsp时被过滤器拦截了
在doFilter()方法中有个参数是FilterChain对象,FilterChain就是过滤器链,我们在一个web应用中可以开发多个Filter,这些Filter组合起来称之为一个FilterChain。
web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有其它filter,如果有,则调用下一个filter,直到filter调用完毕,最后调用目标资源。
Filter生命周期有3个方法,除了doFilter是每次拦截web资源都调用外,还有下面两个方法:
A. init(FilterConfig filterConfig)throws ServletException:
和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(注:filter对象只会创建一次,init方法也只会执行一次 )
开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
B.destroy():
在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
1.字符编码过滤器
表单以POST方式提交请求到Servlet时,可以在过滤器中统一处理中文乱码问题
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 统一设置请求和响应的字符编码
// 1.设置请求的编码
HttpServletRequest req = (HttpServletRequest) request;
req.setCharacterEncoding("UTF-8");
// 2.设置响应的编码
HttpServletResponse resp = (HttpServletResponse) response;
resp.setContentType("text/html;charset=UTF-8");
// 3.放行,让目标资源能够访问
chain.doFilter(request, response);
}
过滤器配置:
encoding
cn.sxt.filter.EncodingFilter
encoding
/*
假设WEB-INF里的页面都需要登录才能访问,则过滤器可以这么设计:
1).首先登录Servlet处理登录逻辑,控制页面跳转
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取表单参数
String username = request.getParameter("username");
// 密码
String userpwd = request.getParameter("userpwd");
// 假登录
if ("admin".equals(username) && "12345".equals(userpwd)) {
// 登录成功
User user = new User();
user.setUsername(username);
user.setUserpwd(userpwd);
// 保存到session中 request.getSession().setAttribute("user", user);
// 跳转到后台的页面
request.getRequestDispatcher("WEB-INF/jsp/main/index.jsp").forward(request, response);
} else {
// 登录失败
response.sendRedirect(request.getContextPath()+"/login.jsp"); }
}
2).过滤器CheckLoginFilter:
public class CheckLoginFilter implements Filter {
//保存初始化参数的值
private String passResource;
// 在初始化方法中获取过滤器的初始化参数的值
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
passResource = filterConfig.getInitParameter("passResource");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 把父接口对象强转成子接口的
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
HttpSession session = req.getSession();
// 1)判断请求的url是否是特殊资源(无需进行登录验证),如果是,直接放行
// 获取当前请求的资源的路径
String uri = req.getRequestURI();
// login.action,authCode.action
String[] arr = passResource.split(",");
for (String str : arr) {
if (uri.endsWith(str)) {
// 放行
chain.doFilter(request, response);
return;
}
}
// 2) url不是特殊资源,需要做登录验证
Object obj = session.getAttribute("user");
if (obj == null) {
// 未登录,跳转到登录页面
resp.sendRedirect(req.getContextPath() + "/login.jsp");
} else {
// 登录过了,放行
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
3).配置过滤器拦截Servlet请求,并对登录和验证码的请求放行
loginFilter
cn.sxt.filter.CheckLoginFilter
passResource
login.action,authCode.action
loginFilter
*.action
监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听的对象发生上述事件后,监听器某个方法将立即被执行。被监听的对象,俗称 “事件源”
监听器是Servlet 2.3新增的功能,Servlet监听器是Servlet规范中定义的一种特殊类,用于监听ServletContext、HttpSession和ServletRequest域对象的创建与销毁事件,以及监听这些域对象中属性发生变化的事件。
监听器所监听的对象:
1、ServletContext:在jsp中叫application,整个应用只存在一个
2、HttpSession:session,针对每一个会话
3、ServletRequest:request,针对每一个客户请求
监听内容:创建、销毁、属性改变事件
监听作用:可以在事件发生前、发生后进行一些处理,一般可以用来统计在线总人数和在线用户、统计网站访问量等。
创建步骤:
1、创建一个实现监听器接口的类
如: class XXX implements XXXListener
2、配置web.xml文件,注册监听器
完整类名
监听器的启动顺序:按照web.xml的配置顺序来启动
1)、用于监听应用程序环境对象ServletContext的事件监听器,实现ServletContextListener(监听ServletContext对象创建及销毁事件)、ServletContextAttributeListener(监听ServletContext对象的属性改变)接口
2)、用于监听用户会话对象HttpSeesion的事件监听器,实现HttpSessionListener、HttpSessionAttributeListener接口
3)、用于监听请求消息对象ServletRequest的事件监听器,实现ServletRequestListener、ServletRequestAttributeListener接口
A、监听域对象自身的创建和销毁事件的监听器
根据监听对象不同分别实现ServletContextListener、HttpSessionListener、ServletRequestListener接口。
1.监听ServletContext的创建和销毁:
实现ServletContextListener 接口,ServletContext创建时调用
contextInitialized方法,销毁时调用contextDestroyed方法
public void contextInitialized(ServletContextEvent sce)//ServletContext创建时调用
public void contextDestroyed(ServletContextEvent sce)//ServletContext销毁时调用
主要用途:
1)初始化的工作:初始化对象、初始化数据(如:加载驱动、读取属性文件的数据, 初始化web应用的菜单数据等);
2)加载初始化的配置文件: 如spring框架的配置文件;
3)任务调度(定时器):监听某项行为定时执行相应的功能;
在web.xml中可以配置web项目初始化信息,在contextInitialized方法中进行初始化参数的读取
参数名
参数值
自定义ServletContext监听器
public class MyServletContextListener implements ServletContextListener {
// 当ServletContext对象创建时,执行contextInitialized()
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("servletContext域对象创建好了");
ServletContext servletContext = sce.getServletContext();
String name = servletContext.getInitParameter("name");
// 读取属性文件conn.properties
Properties p = new Properties();
try {
p.load(MyServletContextListener.class.getResourceAsStream(name));
String url = p.getProperty("url");
String user= p.getProperty("user");
// 保存url值
servletContext.setAttribute("url", url);
servletContext.setAttribute("user", user);
} catch (IOException e) {
e.printStackTrace();
}
}
// 当ServletContext对象销毁时,执行contextDestroyed()
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("servletContext域对象销毁了");
}
}
监听器配置:
cn.sxt.listener.MyServletContextListener
2.监听HttpSession的创建和销毁:
实现HttpSessionListener 接口,两个方法如下:
public void sessionCreated(HttpSessionEvent se)//session创建时调用
public void sessionDestroyed(HttpSessionEvent se)//session销毁时调用
主要用途:统计在线人数、记录访问日志等
自定义Session监听器,统计网站在线人数
public class MySessionlistener implements HttpSessionListener {
// 计数器,存储在线人数
private int count = 0;
// 监听到session创建时,调用sessionCreated()方法
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("session创建了");
synchronized (MySessionlistener.class) {
count++;
// 把人数存到ServletContext对象中
ServletContext context = se.getSession().getServletContext();
context.setAttribute("count", count);
}
}
// 监听到session销毁时,调用sessionDestroyed()方法,表示用户离线
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("session销毁了");
synchronized (MySessionlistener.class) {
count--;
// 把人数存到ServletContext对象中
ServletContext context = se.getSession().getServletContext();
context.setAttribute("count", count);
}
}
}
3.监听ServletRequest的创建和销毁:requestInitialized和requestDestroyed方法
public void requestInitialized(ServletRequestEvent sre)//request创建时调用
public void requestDestroyed(ServletRequestEvent sre)//request销毁时调用
主要用途:读取request参数,记录访问历史
实例:
public class MyRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
// 读取请求参数
String value = sre.getServletRequest().getParameter("city");
System.out.println(value);
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("request销毁");
}
}
B、监听域对象中的属性发生改变的事件监听器
根据监听对象不同分别实现ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener接口。
实现方法:attributeAdded、attributeRemoved、attributeReplaced
注解是有特殊含义的,它可以代替web.xml文件里的配置信息
@WebServlet注解用在Servlet上,在servlet3.0以后不需要在web.xml中配置一个Servlet了,在类名上写注解就行,如@WebServlet(“/test.action”).
注解支持属性,语法: @WebServlet(属性名=值 ),例如
@WebServlet( urlPatterns={"/user.action","/test.action"} )
public class UserServlet extends HttpServlet {
}
这个是对监听器的注解,用了它后就不需要在web.xml注册监听器了
@WebListener
public class MySessionListener implements HttpSessionListener{
}
这个是对过滤器的注解,写在Filter类上就好,就不需要在web.xml注册过滤器了。如果过滤器拦截的资源只有一种,urlPatterns属性的值可以是字符串,如果拦截的资源有多种,则属性值就必须写成数组
//如果过滤器拦截的资源只有一种,urlPatterns属性的值可以是字符串,如果拦截的资源有多种,则属性值就必须写成数组
@WebFilter(urlPatterns = { "/view/*","*.action" })
public class MyFilter implements Filter {
用来配置初始化参数的注解,可以使用在@WebFilter和@WebServlet注解上,作为它们的initParams属性的值
@WebFilter(urlPatterns = { "/view/*","*.action" },
initParams={@WebInitParam(name="city",value="武汉"),@WebInitParam(name="username",value="张三")})
public class MyFilter implements Filter {
}
该注解主要是为了辅助 Servlet 3.0 中 HttpServletRequest 提供的对上传文件的支持。该注解标注在 Servlet 上面,以表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。另外,它还提供了若干属性用于简化对上传文件的处理
@WebServlet("/sf")
@MultipartConfig
public class SingleFileUpload extends HttpServlet {
}
在web.xml文件中,除了可以通过welcome-file设定欢迎页面外,还可以通过error-page标签来设定错误页面
404
/404.jsp
当服务器内部出错时,会跳到默认的错误页面,我们可以用自己配置的错误页面来代替默认的页面
500
/500.jsp
如果多个过滤器都是用注解版本,则调用的顺序取决于过滤器类名的头字母,与字母排序有关,如果头字母相同,再去比较后面的字母和数字,谁排在前面,就先调用谁。
例如:Filter01和Filter02都是用的注解,先调用Filter01