在JavaWeb中有着三大组件:Servlet,Filter过滤器与LIstener监听器
而Filter就是三大组件中的过滤器,他可以将资源的请求和响应拦截下来(也就是一次请求拦截俩次),从而实现一些特殊的功能
作用:
一般用于完成通用的操作(因为可以让在进行特定资源的请求直接先经过过滤器)
如:登录验证、统一编码处理、敏感字符过滤…
JavaWeb的三大组件实现步骤基本都是一样的:
定义一个类,实现接口Filter
示例:
public class FilterDemo1 implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 放行前操作....
// 放行代码:
chain.doFilter(request, response);
// 放行后回来的操作....
}
}
其中的chain.doFilter(request, response);
语句表示, 请求经过拦截器之后会被拦截器拦截,写了这一行语句表示从拦截器中放行请求
重写方法
在一个过滤器中有三个方法,这三个方法就是过滤器的生命周期方法:
init :在服务器启动后,会创建Filter对象,然后调用init方法, 只执行一次, 一般用于加载资源
doFilter:每一次请求被拦截资源时,会执行, 执行多次
destroy:在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
配置拦截路径(注意不是访问路径)
可以通过web.xml:
配置示例:
<filter>
<filter-name>demo1filter-name>
<filter-class>cn.itcast.web.filter.FilterDemo1filter-class>
filter>
<filter-mapping>
<filter-name>demo1filter-name>
<url-pattern>/*url-pattern>
filter-mapping>
也可以通过注解的方式配置:
只需要在Filter类上加上一个@WebFilter注解即可,其中的value属性与urlPatterns属性代表含义是一样的,都是拦截路径,都是数组
示例:
// "/*"表示所有资源都会被拦截
@WebFilter("/*")
因此我们可以在过滤器中对请求数据和响应数据做一些通用的操作
拦截路径不管是使用注解还是xml配置,路径写法都有以下四种:
拦截方式配置,即配置资源在什么时候会别拦截下来,同样可以通过注解配置与xml配置:
注解配置:
设置注解的dispatcherTypes属性即可,dispatcherTypes也是一个数组,有五个可选的值:
示例:
@WebFilter(value = "/*", dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.FORWARD})
xml配置:
在
1). 如果配置了俩个过滤器,则此时的执行顺序为:
过滤器1->过滤器2->执行对应资源->过滤器2->过滤器1
2). 过滤器执行的先后顺序:
验证用户是否登录,如果登录了就直接放行,如果未登录则跳转到登录页面并提升用户登录(因为用户在访问很多资源时都需要验证是否已经登录,因此这种通用的操作我们放在过滤器中实现)
一般我们登录成功了会将用户存入到session中,因此我们查询session数据即可知道用户有没有成功登录了
注意:
在默认创建的Filter类中,俩个和请求与响应的参数是ServletRequest与ServletResponse,这俩个参数不包含与HTTP相关的方法,只有他们的俩个儿子HTTPServletRequest与HTTPServletResponse才会有与HTTP协议相关的方法,;例如获取Session,获取请求路径,请求参数等等…
因此此时我们需要在方法体中做的第一件事就是对这俩个参数进行强制转换
验证登录代码:
package com.ahua.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/*")
public class LoginFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 第一步强转
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 先判断是否是登录相关的资源,如果则直接放行
String requestURI = httpServletRequest.getRequestURI();
// 注意:排除是否是登录相关资源的时候一定要排除干净
// css文件,js文件,图片,验证码判断的Servlet,与登录相关Servlet都需要排除
if (requestURI.contains("/login.jsp") || requestURI.contains("/loginServlet") || requestURI.contains("/checkServlet") ||
requestURI.contains("/js/") || requestURI.contains("/lcss/") || requestURI.contains("/img/")) {
// 如果是与登录相关的资源则直接放行
chain.doFilter(request, response);
}else {
HttpSession session = httpServletRequest.getSession();
// 如果已经登录,则放行
if (session.getAttribute("User") != null){
chain.doFilter(request, response);
} else {
// 如果还没登录, 则跳转页面,跳转前存储提示信息:
httpServletRequest.setAttribute("login-msg", "您尚未登录,请先登录");
httpServletRequest.getRequestDispatcher("/view/login.jsp").forward(httpServletRequest,response);
}
}
}
}
可以参考spring解决乱码问题的Filter:
package org.springframework.web.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public class CharacterEncodingFilter extends OncePerRequestFilter {
@Nullable
private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
public CharacterEncodingFilter() {
this.forceRequestEncoding = false;
this.forceResponseEncoding = false;
}
public CharacterEncodingFilter(String encoding) {
this(encoding, false);
}
public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
this(encoding, forceEncoding, forceEncoding);
}
public CharacterEncodingFilter(String encoding, boolean forceRequestEncoding, boolean forceResponseEncoding) {
this.forceRequestEncoding = false;
this.forceResponseEncoding = false;
Assert.hasLength(encoding, "Encoding must not be empty");
this.encoding = encoding;
this.forceRequestEncoding = forceRequestEncoding;
this.forceResponseEncoding = forceResponseEncoding;
}
public void setEncoding(@Nullable String encoding) {
this.encoding = encoding;
}
@Nullable
public String getEncoding() {
return this.encoding;
}
public void setForceEncoding(boolean forceEncoding) {
this.forceRequestEncoding = forceEncoding;
this.forceResponseEncoding = forceEncoding;
}
public void setForceRequestEncoding(boolean forceRequestEncoding) {
this.forceRequestEncoding = forceRequestEncoding;
}
public boolean isForceRequestEncoding() {
return this.forceRequestEncoding;
}
public void setForceResponseEncoding(boolean forceResponseEncoding) {
this.forceResponseEncoding = forceResponseEncoding;
}
public boolean isForceResponseEncoding() {
return this.forceResponseEncoding;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String encoding = this.getEncoding();
if (encoding != null) {
if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (this.isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
}
需求:如果出现敏感词汇表中的敏感词汇,就替换为***(因为在很多资源中都需要进行敏感词汇过滤,因此使用过滤器进行通用操作的过滤)
注意:
由于request对象只有getParameter()方法,而没有setParameter方法来设置请求参数的值,因此我们需要对request对象进行增强,再使用这个增强后的新的request对象来完成需求
对于request对象的增强,我们只需要使用一种设计模式:装饰模式或者代理模式即可,这里我们使用动态代理
示例:
package com.ahua.web.filter;
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
/**
* 敏感词汇过滤器
*/
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {
// 存放敏感词汇的集合:
List<String> list = new ArrayList<>();
/**
* 在init()方法中读取文件,避免多次读取浪费内存
* @param config
* @throws ServletException
*/
public void init(FilterConfig config) throws ServletException {
BufferedReader bufferedReader = null;
try {
// 加载存放敏感词汇的文件:
// 先获取真实路径
ServletContext context = config.getServletContext();
// 此时的txt文件放在src下
String realPath = context.getRealPath("/WEB-INF/SensitiveWords.txt");
// 加载文件进字符输入流
System.out.println(realPath);
bufferedReader = new BufferedReader(new FileReader(realPath));
// 将文件中的信息加载进集合中
String line;
while ((line = bufferedReader.readLine()) != null){
list.add(line);
}
for (String s : list) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取代理对象并增强getParameter方法
ServletRequest proxyRequest = (ServletRequest) Proxy.newProxyInstance(request.getClass().getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getParameter")){
// 增强getParameter方法的返回值
// 先获取getParameter方法的返回值
String parameter = (String) method.invoke(request, args);
// 检验返回值是否包含list中的敏感词
if (parameter != null) {
for (String s : list) {
if (parameter.contains(s)) {
// 包含则修改为***
parameter = parameter.replaceAll(s,"***");
}
}
}
return parameter;
}
// 除此之外还要增强所有可以获取值的方法
// 例如getParameterMap, getParameterValue等
// 不是我们需要增强的方法就直接调用返回即可
return method.invoke(request, args);
}
});
// 放行
chain.doFilter(proxyRequest, response);
}
public void destroy() {
}
}
web的三大组件之一
由于在项目开发中监听器应用比较少,因此只解析了ServletContextListener监听器
ServletContextListener:
可以监听ServletContext对象的创建和销毁
在JavaEE中ServletContextListener是一个接口,并且没有提供这个接口的实现类(也就是说需要我们自己写)
ServletContextListener接口提供的API:
实现步骤:
定义一个类,实现ServletContextListener接口并重写里面的方法
配置,有俩种方式:注解与web.xml中配置:
web.xml配置:(参数以及servlet标签中的初始化参数(springMVC))
<listener>
<listener-class>cn.ahua.web.listener.ContextLoaderListenerlistener-class>
listener>
上面说了contextInitialized方法主要用于加载资源文件, 但我们如果直接在代码中写死不太好, 因此我们一般使用xml配置资源文件,需要在
配置示例:
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
而在
**注解:**在监听器实现类上加@WebListener注解即可:
@WebListener
ContextLoaderListener.java示例:
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.FileInputStream;
@WebListener
public class ContextLoaderListener implements ServletContextListener {
/**
* 监听ServletContext对象创建的方法。ServletContext对象会在服务器启动后自动创建。
* 在服务器启动后自动调用
* @param servletContextEvent
*/
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//加载资源文件
//1.获取ServletContext对象
ServletContext servletContext = servletContextEvent.getServletContext();
//2.加载资源文件
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
//3.获取真实路径
String realPath = servletContext.getRealPath(contextConfigLocation);
//4.加载进内存
try{
FileInputStream fis = new FileInputStream(realPath);
System.out.println(fis);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 在服务器关闭后,ServletContext对象被销毁。当服务器正常关闭后该方法被调用
* @param servletContextEvent
*/
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext对象被销毁了。。。");
}
}
有关web.xml中的参数配置:
示例(配置Spring中解决乱码问题):
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
spring-mvc.xml中配置前端控制器也使用了
<servlet>
<servlet-name>DispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>DispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
在web.xml中各个元素的执行顺序是这样的:
context-param–>listener–>filter–>servlet
小剧透:
当在Spring MVC中配置了拦截器,则从整个项目中看,一个servlet请求的执行过程就变成了这样:
context-param–>listener–>filter–>servlet–>interceptor(指的是拦截器)
为什么拦截器是在servlet执行之后,因为拦截器本身就是在servlet内部的
拦截器:
在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
在Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
谈到拦截器,还有一个词大家应该知道——拦截器链(Interceptor Chain,在Struts 2中称为拦截器栈 Interceptor Stack)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用
拦截器的实现原理:
大部分时候,拦截器方法都是通过代理的方式来调用的。Struts 2的拦截器实现相对简单。当请求到达Struts 2的ServletDispatcher时,Struts 2会查找配置文件,并根据其配置实例化相对的拦截器对象,然后串成一个列表(list),最后一个一个地调用列表中的拦截器。
拦截器的执行时机:
拦截器(interceptor):就是对请求和返回进行拦截,它作用在servlet的内部,具体来说有三个地方:
1)请求还没有到controller层时
2)请求走出controller层次,还没有到渲染时图层
3)结束视图渲染,但是还没有到servlet的结束
拦截器与过滤器的使用选择:
当需要监听到项目中的一些信息,并且不需要对流程做更改时,用监听器
当需要过滤掉其中的部分信息,只留一部分时,就用过滤器;当需要对其流程进行更改,做相关的记录时用拦截器
至此,原始的JavaWeb开发技术告一段落