1案例1-自动登录
1.1 需求及流程介绍
当用户直接访问登录后的页面(例如:购物车页面,订单页面等)直接显示出上一次登录的用户对象,这个功能就是自动登录的功能。如果用户在上一次登录的时候,没有勾选自动登录,那么用户直接访问登录后的页面时,不显示用户是谁,加购物车,提交订单等都需要让用户先登录。
(简单说就是跟jd一样。今天只做前一句话,后一句话是权限控制,是后面的内容。)
1.2 技术分析
1:可以使用会话技术让服务器知道浏览器发送的多次请求。
2:需要使用过滤器技术,对用户发送的每一次请求都先进行过滤,过滤后,才让用户执行真正的资源。
1.3 过滤器filter概述
过滤器就是运行在服务器最前端的java小程序。
servlet,filter,listener合称为服务器的三大组件,都是被tomcat服务器创建,被服务调用。
servlet与filter的区别:
0:servlet能做的filter一定能做,filter能做的servlet不一定能做。
1:servlet通常用于专一的请求处理,而filter通常用于通用的请求处理;
2:filter一定优先于servlet执行;
进servlet之前要经过filter,从servlet出去的时候也要经过filter。
1.4 过滤器filter的应用场景
1:网站所有请求的中文参数乱码和响应乱码处理。
2:网站访问权限控制。
3:自动登录。
4:敏感字过滤。
1.5 过滤器filter的编写步骤
过滤器的英文名:filter,是javaEE定义的一个接口。
程序员只需实现filter接口,编写相应的方法即可。
关于filter接口中的doFilter方法的3个参数:
ServletRequest类型的参数:代表的是请求对象。
ServletResponse类型的参数:代表的是响应对象。
FilterChain类型的参数:代表的是用于放行的对象。(如果不放行,那么浏览器请求的资源将得不到任何响应)
1.6 自动登录案例的实现
1:案例环境搭建。(数据库创建,静态页面复制,jar包选择,工具类复制)
2:编写3层包结构。
3:将login.html文件修改成login.jsp文件。
4:编写LoginServlet,完成用户完整的登录流程,当用户登陆成功的时候需要将用户对象保存到session中。
5:编写service和dao。
6:修改loginServlet,当用户登录成功的时候,判断用户是否希望帮用户完成自动登录的动作。
如果希望,则创建一个cookie对象,并保存用户名和密码为7天
如果不希望,立刻删除原来的cookie。
7:编写一个过滤器,在过滤器中获取cookie,并使用cookie中的用户名和密码查询数据库,如果能查到则帮用户把用户对象保存到session中。
注意:
所谓的登录就是session中有用户对象。
部分参考代码:
说明:
页面里
用户名标签处:name=”username”
密码标签处:name=”password”
是否自动登录勾选选项处标签:name=”autoLogin” value=”ok”
过滤器AutoLoginFilter.java:
package com.itheima.anli00_filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.itheima.anli02_service.UserService;
import com.itheima.anli04_domain.User;
/**
* 完成自动登录的filter
*/
public class AutoLoginFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/*
* 思路:
*
* 1:判断session中是不是已经拥有了user对象,如果有,则证明用户已经登录了,可以直接放行,否则继续判断下一步;
* 2:获取浏览器携带的所有cookie,并查找出名字为auto的cookie;如果没有cookie,直接放行,否则继续下一步;
* 3:获取cookie中的用户名和密码查询数据库;如果查询成功,则保存到seesion中,如果没查到,直接放行;
*
*/
HttpServletRequest r = (HttpServletRequest)request;
HttpSession se = r.getSession();
Object user = se.getAttribute("user");
if(user==null){
//需要继续下一步
Cookie[] cs = r.getCookies();
if(cs!=null){
//说明有一部分cookie
Cookie c=null;//准备使用c保存我们想要的cookie对象
for (Cookie cookie : cs) {
if(cookie.getName().equals("auto")){
c=cookie;
}
}
//判断有没有找到cookie
if(c!=null){
String value = c.getValue();
String[] split = value.split("<itheima>");
UserService us = new UserService();
User u2 = us.findUserByUsernameAndPassword(split[0],split[1]);
if(u2!=null){
//帮这个用户保存session
se.setAttribute("user",u2);
}
}
}
}
//放行
chain.doFilter(request, response);
}
public void init(FilterConfig fConfig) throws ServletException {
}
}
LoginServlet.java
package com.itheima.anli01_web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.itheima.anli02_service.UserService;
import com.itheima.anli04_domain.User;
/**
* 用户登录的servlet
*/
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//System.out.println(request);
//1:参 调 存 转
String un = request.getParameter("username");
String ps = request.getParameter("password");
String au = request.getParameter("autoLogin");
//2:调用业务层
UserService us = new UserService();
User u = us.findUserByUsernameAndPassword(un,ps);
//3:存,一定要将用户保存到session对象中,
if(u==null){
//说明用户名或密码错误,回到login.jsp,让用户重新登录
request.setAttribute("msg","用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;//结束方法
}
//4:说明查到了用户,保存到session中
request.getSession().setAttribute("user", u);
//判断用户是否希望我们完成自动登录功能
Cookie c = new Cookie("auto",un+"<itheima>"+ps);
c.setPath("/");
if("ok".equals(au)){
//说明用户希望自动登录,将cookie设置为7天
c.setMaxAge(60*60*24*7);
}else{
//说明用户不希望自动登录,将cookie设置为0秒
c.setMaxAge(0);
}
response.addCookie(c);
//转发的购物网站,可以让用户去购物了
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
2 filter总结
2.1 Filter的生命周期
从filter创建一直到filter死亡,这个过程就是filter的生命周期。
Filter的生命周期方法有3个:
init() 初始化
doFilter() 执行过滤
destroy () 死亡
doFilter()方法:只要能匹配路径成功一定会执行,即使有多个filter都匹配了路径也会逐个执行。执行顺序仅仅与web.xml中filter-mapping的配置顺序有关,从上至下,逐个执行。
init()方法:随着tomcat启动就会创建filter对象。
destroy()方法:随着tomcat服务器正常停止而死亡。
2.2 多个filter顺序问题:
2.4 FilterConfig对象(了解)
在web.xml中的filter标签中配置init-param属性和属性值时,可以使用FilterConfig对象,获取这些初始化配置参数。
例如:
<filter>
<display-name>AutoLoginFilter</display-name>
<filter-name>AutoLoginFilter</filter-name>
<filter-class>com.itheima.anli00_filter.AutoLoginFilter</filter-class>
<init-param>
<param-name>aaa</param-name>
<param-value>111</param-value>
</init-param>
</filter>
javaee的api中有。
常用API:
getInitParameter(属性名);
2.5 关于filter中的url-pattern的配置
在filter中有3种配置:
1:完全匹配 /xxx
2:目录匹配(通配符匹配) / (用的最多)
3:后缀名匹配 .xxx (偶尔使用)
注意:
三种匹配方式不分优先级,只要与路径匹配成功,都会执行,执行顺序仅与web.xml中的配置顺序有关。
2.6 关于filter的拦截方式问题
在filter-mapping中,也可以不使用url-pattern。
使用servlet-name标签替代(了解,不常用)
例如:
关于过滤的方式:
3 案例3-中文编码过滤器
3.1 需求
当用户访问工程中的任意一个servlet的时候,在servlet中无需再处理中文参数,也无需处理中文响应,都能保证参数和响应数据的正常。(不能乱码)
3.2 技术分析
在servlet中获取参数的方法有3个,分别是:getParameter getParameterValues getParameterMap
只要对这3个方法进行增强,即可实现中文参数乱码问题的结果。
响应的时候,只需在调用getWriter或getOutputStream之前,设置响应的Content-Type头信息即可。
使用过滤器就能完成这个事情。
3.3 增强一个类的方法的技术
方式1:继承,重写方法。
缺点:
子类与父类完全绑在了一起,耦合度太高,代码不灵活。(此案例中和tomcat耦合度太高,换个服务器不用tomcat了直接就不好使了)
好处:
简单,好写,快捷。
方式2:装饰者模式
(ps:关于各种各样的设计模式,不管是什么设计模式,都是在抽取工具类的时候才会用)
装饰者模式是指A类与B类是兄弟类,将来面向接口开发的时候,使用A类中的方法,替代B类中的方法,那么此时就可以称为A装饰了B。A就是装饰者,B就是被装饰者。
好处:
代码灵活度相对高,只要不更换接口,子类可以替换。
使用前提:
在装饰者中,必须能使用被装饰者的对象,且需要与被装饰者实现相同的接口。
代码步骤:
1:编写一个类,实现与被装饰者相同的接口。(成为兄弟)
2:在类中定义一个接口类型的成员变量用于保存被装饰者对象。
3:编写增强后的方法即可。(增强方法)
本案例中由于直接使用HttpServletRequest接口,需要重写的方法太多,因此选择继承HttpServletRequestWrapper类来与HttpServletRequest接口产生关系。
3.4 过滤器代码实现:
实际装饰的是request对象。
package com.itheima.anli00_filter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* 我们自己采用装饰者模式,对reqeust对象进行增强的工具类
*/
public class MyEncodingFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//1:将request和response转成子接口类型
HttpServletRequest r = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
//2:获取请求方式,如果是get请求,则使用装饰者模式解决乱码,如果是post请求,则直接面向原始的request对象,调用setCharacterEncoding方法解决乱码
String method = r.getMethod();
//3:先解决响应乱码问题
res.setContentType("text/html;charset=utf-8");
if("GET".equalsIgnoreCase(method)){
//说明需要使用装饰者
MyRequest z = new MyRequest(r);
//放行,传递装饰后的对象
chain.doFilter(z, res);
}else{
//可以直接解决
r.setCharacterEncoding("utf-8");
chain.doFilter(r, res);
}
}
public void init(FilterConfig fConfig) throws ServletException {
}
}
class MyRequest extends HttpServletRequestWrapper{
//定义一个接口类型的成员变量,用于保存被装饰的对象
private HttpServletRequest request ;
//为了解决多次调用方法的时候,第一次不乱,之后反而乱码的问题,我们需要定义一个flag,让转换的过程仅执行一次
private boolean flag = true;//默认可以进行转换
public MyRequest(HttpServletRequest request) {
super(request);
this.request = request;
}
//增强方法;
//1:增强getParameterMap
public Map<String, String[]> getParameterMap() {
//1:先使用request获取原始的浏览器传递过来的参数
Map<String, String[]> map = request.getParameterMap();
//2:此时如果map中有中文参数,一定是乱码的!!! 仅考虑get请求,post请求直接面向原始对象解决即可
if(flag){
Set<String> set = map.keySet();
for (String key : set) {
String[] vs = map.get(key);
//对数组中的参数进行处理,处理后需要将参数重新保存回数组
for(int i=0;i<vs.length;i++){
//只要对数组的每一个元素进行了修改,那么map中的数组中的元素自然就跟着变化了(已经解决中文乱码了)
try {
vs[i]=new String(vs[i].getBytes("iso8859-1"),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
}
//修改flag为false
flag = false;
}
//返回已经解决了乱码的map
return map;
}
//重写getParameterValues方法,这个方法中的数据,都可以直接从map中获取,因为map已经解决了乱码
@Override
public String[] getParameterValues(String name) {
return getParameterMap().get(name);
}
@Override
public String getParameter(String name) {
String[] vs = getParameterValues(name);
if(vs==null){
return "";
}
return vs[0];
}
}