过滤器、监听器

一、用户注销

过滤器、监听器_第1张图片

用户注销则为退出登录状态,将当前登录的用户信息销毁。而我们之前做的用户登录功能,是把当前登录用户信息是存在 session 中的,所以只需要清除 session 中的用户信息即可实现用户注销。

1、修改 login.jsp

为了登录页面好看,加入新的登录页面,按照给的资料\模板页面目录里面去拷贝即可(login.jspcss目录,js 目录)。

2、修改 list.jsp

修改产品的 list.jsp,提供给用户点击可以安全退出的链接,点击发送请求交由 Servlet 去处理。

欢迎:${USER_IN_SESSION.username}  href="/logout"> 安全退出

3、编写 LogoutServlet.java

处理安全退出的请求,清除 session 中用户的登录信息,并重定到 login.jsp

@WebServlet("/logout")

public class LogoutServlet extends HttpServlet { 

@Override

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

  • 方式一:删除用户共享数据
  • req.getSession().removeAttribute("USER_IN_SESSION");
  • 方式二:销毁整个 Session 对象

req.getSession().invalidate();

resp.sendRedirect("/login.jsp");

}

}

二、记住账号

为了提升用户体验,在用户登录的时候可以选择记住账号信息,在指定时间范围内可以不用再重新填写账户信息。

因为需要将用户信息保存一段时间,放 session 的话浏览器关闭则会失效,所以选择使用 Cookie 来实现。

1、修改 login.jsp

提供勾选记住我复选框,点击登录时会传递参数 remeberMe  LoginServlet

type="checkbox" name="remeberMe" value="true"> 记住我

2、修改 LoginServlet.java

获取请求参数 remeberMe,若是选中(true),创建 Cookie,指定时间并响应给浏览器;若没有选中,清除 Cookie 对象

@Override

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.setCharacterEncoding("UTF-8");

  • 接受请求参数

String username = req.getParameter("username"); String password = req.getParameter("password"); try {

  • 调用业务方法来处理登录请求

User user = userService.login(username, password);

  • 登录成功时往 Session 中加入一个登录成功的标识,给其它需要登录才可以操作的资源做判

断使用

req.getSession().setAttribute("USER_IN_SESSION", user);

//============== 是否记住我 ===============

boolean remeberMe = Boolean.valueOf(req.getParameter("remeberMe")); Cookie cookie = new Cookie("username", username); cookie.setPath("/");

if (remeberMe){

  • 设置 Cookie 有效时间为一周

cookie.setMaxAge(60 * 60 * 24 * 7);

}else{

  • 删除 Cookie cookie.setMaxAge(0);

}

resp.addCookie(cookie);

  • 控制跳转

resp.sendRedirect("/product");

}catch (Exception e){ // 有异常时表示登录失败

e.printStackTrace();

req.setAttribute("errorMsg", e.getMessage());

req.getRequestDispatcher("/login.jsp").forward(req, resp);

}

}

3、在登录页面回显账号和记住我

修改 login.jsp 使用 EL 表达式回显账号和记住我。

type="text" class="form-control" placeholder="请输入账号" name="username" value="${cookie.username.value}">

type="checkbox" name="remeberMe" value="true"

${cookie.username.value != null ? 'checked' : ''}> 记住我

三、验证码

1、作用

验证码(CAPTCHA)是 “Completely Automated Public Turing test to tell Computers and Humans Apart”全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止:恶意破解密码、刷票、论坛灌水,有效防止黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试,实际上用验证码是现在很多网站通行的方式,我们利用比较简易的方式实现了这个功能。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答 CAPTCHA 的问题,所以回答出问题的用户就可以被认为是人类。

2、分类

 图片验证码(最常用)。

 手机短信验证码。

 手机语音验证码。

3、图片验证码原理

图片验证码功能分三个部分:

  A 程序生成随机内容的验证码图片展现给用户。

 用户根据图片输入验证码文本。

  B 程序判断用户输入的验证码和 A 程序生成的验证是否相同。

问题:生成验证码的程序 A 和判断用户填写验证码的程序 B 不是同一个,如何在程序 B 中获取到程序

  • 生成的验证码文本做判断?

过滤器、监听器_第2张图片

解决:因为两个程序我们都是使用 Servlet 来实现的,所以可以把 A 程序生成 验证码之后存入 Session B 程序从 Session 中来获取。

4、图片验证码实现

需求:防止恶意登录,给登录加上验证码。

4.2、修改在 login.jsp

提供 img 标签,显示验证码。

class="row">

class="col-xs-8">

type="text" class="form-control" placeholder="请输入验证码" name="randomCode">

class="col-xs-4">

style="width: 85px; height: 30px;" alt="验证码" src="/randomCode">

4.3、修改 LoginServlet.java

加入验证码判断。

@Override

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.setCharacterEncoding("UTF-8");

  • 接受请求参数

String username = req.getParameter("username");

String password = req.getParameter("password");

  • 判断验证码是否正确

String randomCode = req.getParameter("randomCode"); String sessionRandomCode = (String)

req.getSession().getAttribute("RANDOMCODE_IN_SESSION"); if(!(StringUtil.hasLength(randomCode)

    1. .hasLength(sessionRandomCode)
    1. .equalsIgnoreCase(sessionRandomCode))) {
  • 跳回登录页面重新填写验证码

req.setAttribute("errorMsg", "验证码错误");

req.getRequestDispatcher("/login.jsp").forward(req, resp); return;

}

  • 说明验证码正确,可以做登录校验,可以把验证码清除,验证码仅限于一次登录使用 req.getSession().removeAttribute("RANDOMCODE_IN_SESSION");
  • 后面登录操作省略 ......

}

4.4、修改验证码

生成的图片验证码可能存在用户识别不了的情况,可提供点击更换图片验证码功能。

思路:  img 标签的 src 属性值改变了,浏览器会自动发送新的请求。那么我们就给图片添加一个点击事件处理,点击了图片,修改该图片 src 的值即可,重新请求验证码图片。

修改 login.jsp

id="randomCode" style="width: 85px; height: 30px; cursor: pointer" alt="验证码" src="/randomCode" onclick="changeImg()">

  • 点击图片执行下面的函数 function changeImg() {
  • 修改验证码 img 标签的 src 属性值,为什么要带参数,禁用浏览器缓存 document.getElementById("randomCode").src="/randomCode?" + new

Date().getTime();

}

浏览器有缓存,将 get 请求的结果缓存到本地磁盘,若请求相同的资源,不再发送新的请求,而是直接使用缓存中的结果。我们可以通过携带参数不一样来欺骗浏览器我们请求的资源不是同一个资源(实际上是同一个)

四、过滤器 Filter

1、定义及应用

Filter  Servlet 2.3 新增加的功能,Java Web 组件之一。使用户可以改变一个 request 和修改一个responseFilter 不是一个 Servlet,它不能产生一个 response,它能够在一个 request 到达 Servlet 之前预处理 request,也可以在 response 离开 Servlet 后处理 response。换种说法,Filter 其实是一个 "Servlet chaining"Servlet 链)。

过滤器使用场景:

 字符编码处理

 登录校验

 论坛敏感字过滤

 做前端框架的分发器。

2Filter  Hello World

2.1、回顾 Servlet 开发步骤

回顾 Servlet 开发步骤,类比有助于学习 Filter,步骤如下:

定义类,并实现 javax.servlet.Servlet 接口。

 覆写其中的 5 个方法,在 service 方法中编写处理请求做响应的代码。

将写好的 Servlet 交给 Tomcat 来管理(XML/注解)。

2.2HelloServlet 代码演示

为了给大家更清楚地演示访问的执行流程,也写一个 HelloServlet

2.2.1、编写 HelloServlet.java

public class HelloServlet extends HttpServlet { @Override

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("helloServlet");

req.getRequestDispatcher("/hello.jsp");

}

}

2.2.2、配置 HelloServlet

使用 XML 配置,在 web.xml 配置如下:

helloServlet

HelloServlet

helloServlet

/hello

2.2.3、编写 hello.jsp

 web 目录下新建 hello.jsp,内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

</span><span style="color:#333333;">hello</span><span style="color:#117700;">

欢迎

2.3Filter 开发步骤

Filter 也是一个 Java Web 组件之一,所以开发步骤和 Servlet 一致,步骤如下:

定义类,实现 javax.servlet.Filter 接口

 覆写其中的 3 个方法。

将写好的 Filter 交给 Tomcat 来管理(XML/注解)。

2.4HelloFilter 代码演示

2.4.1、编写 HelloFilter.java

public class HelloFilter implements Filter {

  • 初始化方法

@Override

public void init(FilterConfig filterConfig) throws ServletException {

}

  • 过滤方法

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse

servletResponse, FilterChain filterChain) throws IOException, ServletException {

可以在这里做过滤操作

System.out.println("进来过滤了...");

  • 目前直接放行

filterChain.doFilter(servletRequest, servletResponse);

}

  • 销毁方法

@Override

public void destroy() {

}

}

2.4.2、配置 HelloFilter

使用 XML 配置,在 web.xml 配置如下:

helloFilter HelloFilter

helloFilter

init 方法(一次)---> doFilter 方法(N 次)---> destroy 方法(一次/ 0 )

5FilterChain(过滤器链)

在开发中会存在并配置了多个过滤器时,多个过滤器按照一定的顺序,排列起来,多个 Filter 组合在一起都形成一个 Filter 链,使用 FilterChain 对象来做牵引关联。

程序中,存在多个过滤器的时候,过滤器的先后执行顺序由谁来决定?

 由在 web.xml 中配置的 -mapping> 的先后顺序来决定

 注解配置时则是由 Filter 的名称的字母先后顺序来决定。

6、过滤方式

默认过滤器只对请求操作做过滤,针对转发是没有做过滤的,若要对转发方式等做过滤,需要设置过滤方式。过滤方式如下:

  REQUEST: 一次全新的请求,只有全新的请求才会经过过滤器(默认)。

FORWARD: 请求转发

  ERROR 错误页面跳转。

6.1、过滤转发

 web.xml 修改 HelloFilter 的过滤器方式,如下:

helloFilter

/*

REQUEST

FORWARD

6.2、过滤器错误

6.2.1、编写 404.jsp  500.jsp

 web 目录下新建 404.jsp  500.jsp404.jsp 内容提示用户访问的资源不存在,500.jsp 内容提示用户程序员正在努力修复程序。

6.2.2、配置通用的错误页面

 web.xml 中配置如下:

404

/404.jsp

500

/500.jsp

6.3.2、修改过滤器过滤方式

 web.xml 修改 HelloFilter 的过滤器方式,如下:

helloFilter

/*

REQUEST

FORWARD

ERROR

五、字符编码过滤器

1、编码过滤器引入

之前编码处理的代码是直接写 Servlet 中的,那么它存在以下问题:

 代码重复,无法复用,从增加维护成本。

目标:一处设置,到处有效(设置一次,所有 Servlet 共用)。

方案:访问到 Servlet 之前对请求中的编码做处理(Filter)。

2、代码实现

2.1、修改所有 Servlet

删除其中对请求编码的的处理的代码。

2.2、编写 CharacterEncodingFilter.java

统一在过滤器的 doFilter 方法中设置好编码为 UTF-8

public class CharacterEncodingFilter implements Filter {

@Override

public void init(FilterConfig filterConfig) throws ServletException {

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  • 设置请求编码

servletRequest.setCharacterEncoding("UTF-8");

  • 发行

filterChain.doFilter(servletRequest, servletResponse);

}

public void destroy() {

}

}

2.3、配置 CharacterEncodingFilter

 web.xml 配置此过滤器,如下:

characterEncodingFilter

CharacterEncodingFilter

characterEncodingFilter

/*

3、字符编码可以配置

在过滤器中,我们将请求参数的编码设值为 UTF-8,此时编码是写死在 Java 代码中的,若后期需要修改,那么这个 UTF-8 就是硬编码,不可以灵活配置。

3.1、配置字符编码

Filter 类似 Servlet 一样,也可以配置初始化参数。在 web.xml 配置如下:

characterEncodingFilter

CharacterEncodingFilter

encoding

UTF-8

characterEncodingFilter /*

3.2、修改 CharacterEncodingFilter.java

在初始化方法里从 FilterConfig 对象中获取配置的 encoding 的初始化参数值。

public class CharacterEncodingFilter implements Filter {

private String encoding;

@Override

public void init(FilterConfig filterConfig) throws ServletException { this.encoding = filterConfig.getInitParameter("encoding")

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

if(StringUtil.hasLength(encoding) {

  • 设置请求编码

servletRequest.setCharacterEncoding(this.encoding);

}

  • 放行

filterChain.doFilter(servletRequest, servletResponse);

}

@Override

public void destroy() {

}

}

4、字符编码覆盖设置

因为可能存在多个过滤器且都设置字符编码,可以让开发者能够选择是否覆盖之前的字符编码,这种设计才是最灵活的。

4.1、配置字符编码是否覆盖

 web.xml 配置如下:

characterEncodingFilter CharacterEncodingFilter

encoding

UTF-8

force

true

characterEncodingFilter /*

4.2、修改 characterEncodingFilter.java

在初始化方法里从 FilterConfig 对象中获取配置的 encoding 的初始化参数值。

public class CharacterEncodingFilter implements Filter {

private String encoding;

private boolean force;

@Override

public void init(FilterConfig filterConfig) throws ServletException {

this.encoding = filterConfig.getInitParameter("encoding");

this.force = Boolean.valueOf(filterConfig.getInitParameter("force"));

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

if(StringUtil.hasLength(encoding)) {

  • 若配置强制或者之前设置过请求字符编码的话

if(force || servletRequest.getCharacterEncoding() == null) { servletRequest.setCharacterEncoding(this.encoding);

}

}

filterChain.doFilter(servletRequest, servletResponse);

}

@Override

public void destroy() {

}

}

六、登录校验过滤器

1、登录校验过滤器引入

在实际开发中,我们项目中会存在很多的资源都需要在登录之后才能访问,所以我们需要在请求到达这些资源之前先判断当前用户是否登录,如果没有应该跳转到登录页面。之前我们是在需要登录访问的的资源加上登录判断代码,代码如下:

Object obj = req.getSession().getAttribute("USER_IN_SESSION"); if(obj == null){

resp.sendRedirect("/login.jsp");

return;

}

以上操作能够解决登录检查的需求,但存在大量重复代码,在每个资源的代码中都会编写检查登录的代码,从增加维护成本。

目标:一处设置,到处有效(设置一次,所有 Servlet 共用)。

方案:访问到 Servlet 之前对请求进行登录校验处理(Filter)。

2、代码实现

2.1、修改所有 Servlet

删除其中对请求登录校验处理的代码。

2.2、编写 CheckLoginFilter.java

过滤器请求,做登录校验。

public class CheckLoginFilter implements Filter { @Override

public void init(FilterConfig filterConfig) throws ServletException {

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  • 把请求和响应转化为符合 HTTP 协议的对象

HttpServletRequest req = ((HttpServletRequest) servletRequest); HttpServletResponse resp = ((HttpServletResponse) servletResponse);

Object obj = req.getSession().getAttribute("USER_IN_SESSION");

  • 没有登录,重定向到登录页面 if(obj == null) {

resp.sendRedirect("/login.jsp"); return;

}

  • 登录过,放行访问

filterChain.doFilter(req, resp);

}

@Override

public void destroy() {

}

}

2.3、配置 CheckLoginFilter

 web.xml 配置此过滤器,如下:

checkLoginFilter

CheckLoginFilter

checkLoginFilter

/*

2.4、匿名访问资源

匿名访问资源指的是不需要登录也可以访问的资源,比如 /login.jsp/login/randomCode 和静态资源等等。若不排除,那么当浏览器中请求这些资源,也需要登录,这就会造成这些资源访问不到了。

针对上面的情况,只需要指定 CheckLoginFilter 对哪些资源做登录校验处理或者不对哪些资源做登录校验处理。

2.4.1、方式一

指定 CheckLoginFilter 不对哪些资源做登录校验处理。

修改 CheckLoginFilter,配置初始化参数,指定哪些资源不做登录校验,如下:

checkLoginFilter

ilter.CheckLoginFilter

unCheckUri /login.jsp;/login;/randomCode

checkLoginFilter /*

public class CheckLoginFilter implements Filter {

private List<String> unCheckUriList;

@Override

public void init(FilterConfig filterConfig) throws ServletException { this.unCheckUriList =

Arrays.asList(filterConfig.getInitParameter("unCheckUri").split(";"));

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest req = ((HttpServletRequest) servletRequest); HttpServletResponse resp = ((HttpServletResponse) servletResponse);

Object obj = req.getSession().getAttribute("USER_IN_SESSION");

  • 获取请求的资源路径

String uri = req.getRequestURI();

  • 没有登录且访问的不是匿名资源,重定向到登录页面

if(obj == null && !unCheckUriList.contains(uri)) { resp.sendRedirect("/login.jsp");

return;

}

  • 登录过,放行访问

filterChain.doFilter(req, resp);

}

@Override

public void destroy() {

}

}

2.4.2、方式二

因为会发现,上面那样处理还是会导致静态资源不可以访问到,而实际开发中,我们需要排除的资源较多的话,尤其是像静态资源,会很麻烦。所以这次指定 CheckLoginFilter 对哪些资源做登录校验处理。将需要受检查的资源通通存放到 check 路径下。

修改 CheckLoginFilter 的过滤路径,如下:

checkLoginFilter

filter.CheckLoginFilter

checkLoginFilter

/check/*

修改 CheckLoginFilter,如下:

public class CheckLoginFilter implements Filter {

@Override

public void init(FilterConfig filterConfig) throws ServletException {

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest req = ((HttpServletRequest) servletRequest); HttpServletResponse resp = ((HttpServletResponse) servletResponse);

Object obj = req.getSession().getAttribute("USER_IN_SESSION");

  • 没有登录,重定向到登录页面 if(obj == null) {

resp.sendRedirect("/login.jsp"); return;

}

  • 登录过,放行访问

filterChain.doFilter(req, resp);

}

@Override

public void destroy() {

}

}

最后只要修改需要做登录校验的资源,加上 /check 路径即可(记得把 list.jsp 查询产品的路径和登录成功重定向的的路径改了),如下:

@WebServlet("/check/product")

public class ProductServlet extends HttpServlet { // 省略......

}

七、监听器 Listene

Listener  Java Web 组件之一,主要的作用是用于监听作用域对象的创建和销毁动作以及作用域属性值的改变动作。若用户触发了这些动作,那么会立即执行相应的的监听器的操作。

1、监听什么及分类

监听的对象:作用域对象,作用域属性。

监听的动作:作用域对象的创建和销毁,作用域属值的增删改。

监听器分类

 按作用域对象:

ServletRequestListener

HttpSessionListener

ServletContextListene

 按作用域属性分:

ServletRequestAttributeListener

HttpSessionAttributeListener

ServletContextAttributeListener

2、开发监听器的步骤

 创建一个类,根据需求实现对应的接口。

 实现其中的方法。

 将监听器交给 Tomcat 管理。

3、统计游客数量

案例:统计游客数量。

3.1、编写 VisitorListener.java

public class VisitorListener implements HttpSessionListener {

private static long totalCount = 0;

  • Session 对象创建会执行下面的方法

@Override

public void sessionCreated(HttpSessionEvent httpSessionEvent) { totalCount++;

System.out.println("在线人数:" + totalCount);

}

  • Session 对象销毁会执行下面的方法

@Override

public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { totalCount--;

}

}

3.2、配置 VisitorListener

可以使用注解配置(直接在自己写监听器类上贴 @WebListener 即可),也可以是 XML 配置,XML 配置如下:

listener.VisitorListener

4、创建系统默认管理员

一般系统最开始部署到用户的服务器时,需要在用户的表中添加一个默认的超级管理员账户,再由该管理员来管理其他的账户的添删改查操作。

4.1、思考的问题

 超级管理员什么时候创建?服务器启动的时候创建。

 哪些技术可以在服务器启动做操作?

答案:可以使用 Servlet  Filter 来实现,也可以使用 ServletContextListener 这个监听器来完成,这个监听器可以用来监听 ServletContext 对象的创建,而这个对象的创建就是在 Tomcat 启动的时候。

4.2、编写并配置 CreateSuperUserListener

在其 contextInitialized 方法中创建管理员账户:

 查询用户表中是否存在管理员的账户。

 存在则不做操作,不存在则创建账号

@WebListener

public class CreateSuperUserListener implements ServletContextListener {

private IUserDAO userDAO = new UserDAOImpl();

  • 容器启动的时候会执行下面的方法

@Override

public void contextInitialized(ServletContextEvent servletContextEvent) {

User user = userDAO.checkUser("admin");

if(user == null) {

  • 保存管理员账号

User adminUser = new User();

adminUser.setUsername("admin");

adminUser.setPassword("admin");

userDAO.insert(adminUser);

}

}

@Override

public void contextDestroyed(ServletContextEvent servletContextEvent) {

}

}

4.3、修改 IUserDAO  UserDAOImp

void insert(User user);

@Override

public void insert(User user) {

SqlSession session = MyBatisUtil.getSession();

session.insert("mapper.UserMapper.insert", user);

session.commit();

session.close();

}

4.4、修改 UserMapper.xml

id="insert" useGeneratedKeys="true" keyProperty="id">

INSERT INTO user(username, password, headImg) VALUES(#{username}, # {password}, #{headImg})

你可能感兴趣的:(简单学点java,java,tomcat,mybatis)