过滤器
过滤器是服务器端的组件,用于对来到服务器的请求和服务器返回的响应进行过滤操作。例如通过过滤器判断用户是否登录从而执行不同操作,或者请求到不存在页面时返回报错信息等。过滤器在服务器服务启动时就被加载生成了,之后用户和服务器之间的请求和响应都要经过过滤器
1、创建过滤器
创建一个filter类需要实现Filter接口的三个方法,init()是过滤器的初始化方法,web容器创建过滤器后调用该方法从web.xml文件中读取过滤器参数。doFilter()完成过滤操作,当request请求到达过滤器后执行doFilter()操作,对请求进行处理:转发、重定向或者传给下一个过滤器,执行完相关操作后返回的response将经过Filter返回给用户。destroy()在Web容器销毁实例前调用,释放过滤器所占用的资源。
public class firstFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("创建第一个过滤器");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("第一个过滤器执行过滤之前");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("第一个过滤操作之后");
}
public void destroy() {
System.out.println("销毁第一个过滤器");
}
}
在web.xml文件中对过滤器进行注册,配置如下:
如下注册一个名为FirstFilter的过滤器,其类在filter.myFilter,并定义对应的mapping对所有url路径进行过滤。
<filter>
<filter-name>FirstFilter</filter-name>
<filter-class>filter.myFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FirstFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
还可以通过注解的方式对过滤器进行 注册,不需要手动在web.xml文件中进行配置,注解会在部署时被web容器自动配置注册为过滤器。如下所示为通过注解方式等价的方式
@WebFilter(filterName = "firstFilter",value = {"/*"},dispatcherTypes = {DispatcherType.REQUEST})
public class firstFilter implements Filter {
......
}
注解WebFilter常用属性如下:
2、过滤器链
当所访问的url定义了多个过滤器时,用户request请求将按照在web.xml中注册先后顺序依次经过多个过滤器,访问结束之后再嵌套返回response,最后返回给用户。这个顺序类似于栈操作,先进后出。
例如定义两个过滤器的链执行结果:
3、过滤器分类
在配置过滤器时的<dispatcher>选项可以选择对四
种类型的页面请求进行过滤,分别是request、forward、include、error,不同类型的过滤器只会对本类型的请求进行过滤。
request请求是<dispatcher>默认的过滤类型,它会对页面的request请求进行过滤,直接请求或者重定向都是request类型。
forward会对转发类型的页面请求进行过滤,通过forwar()函数或者<jsp:forward>动作的均属于转发操作,如下所示,在index.jsp页面中调用forward()函数转发到regPage.jsp页面,定义针对regPage.jsp页面转发操作的过滤器forwardFilter,当用户以转发操作访问regPage.jsp页面时就会经过该过滤器,并在控制台输出:转发过滤器
//在index.jsp页面进行转发到regPage.jsp
servletRequest.getRequestDispatcher("regPage.jsp").forward(servletRequest,servletResponse);
//定义转发过滤器
public class forwardFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("转发过滤器");
chain.doFilter(req, resp);
}
public void init(FilterConfig config) throws ServletException {
}
}
//注册转发过滤器,针对regPage.jsp页面的转发类型请求进行过滤
<filter>
<filter-name>ForwardFilter</filter-name>
<filter-class>filter.forwardFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ForwardFilter</filter-name>
<url-pattern>/regPage.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
类似地,include会对页面的包含行为进行过滤,使用getDispatcher().include()或者<jsp:include>的操作均是包含行为。
error类型是指页面发生错误之后跳转到指定页面前进行的过滤器,如下所示定义页面在发生404错误后跳转到exception.jsp页面,在这之前经过ERROR类型过滤器errorFilter
<error-page>
<error-code>404</error-code>
<location>/exception.jsp</location>
</error-page>
<filter>
<filter-name>errorFilter</filter-name>
<filter-class>filter.errorFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>errorFilter</filter-name>
<url-pattern>/exception.jsp</url-pattern>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
4、加载配置信息
在过滤器init()函数可以加载web.xml文件中<init-param>标签的配置信息。
例如当一个网站过滤器loginFilter需要对用户是否登录进行判断,在用户登录之后将保存信息到session中,在过滤器中对session进行判断,如果已经登录则可以继续访问其他页面,反之如果没有登录则需要跳转到login.jsp页面进行登录。但是过滤器不应该对login.jsp和登陆失败的页面fail.jsp页面进行再次过滤,这样就陷入了死循环。因此把不需要过滤的例外页面以初始化参数的形式保存在web.xml中,在过滤器init()函数中加载,并在doFilter()中进行判断当前路径是否属于例外,如果是则放行请求。
为了防止中文乱码,需要在页面中设置request编码格式,但是在每个页面中都设置过于麻烦,可以在过滤器中对request编码进行设置,这样只要经过过滤器的页面的编码就都会生效。可以在web.xml文件中对编码方式进行指定,之后再加载到过滤器中。
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>filter.loginFilter</filter-class>
<init-param> <!--设置不需要过滤的路径-->
<param-name>passPath</param-name>
<param-value>login.jsp;fail.jsp</param-value>
</init-param>
<init-param> <!--设置编码方式-->
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
public class loginFilter implements Filter {
private FilterConfig config; //设置配置参数变量
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
//读取配置文件中的编码并设置
String charset = config.getInitParameter("charset");
if (charset != null)
request.setCharacterEncoding(charset);
//判断路径是否属于配置文件中规定的路径,如果是则无需验证,放行请求
String passPath = config.getInitParameter("passPath");
String[] paths = passPath.split(";");
for (String path : paths) {
if (request.getRequestURI().indexOf(path) != -1) {
chain.doFilter(req, resp);
return;
}
}
//检查session是否有用户名信息,有代表已经登录可以访问,否则跳转到登录页面
if (request.getSession().getAttribute("username") != null)
chain.doFilter(req, resp);
else
response.sendRedirect("login.jsp");
}
public void init(FilterConfig config) throws ServletException {
this.config = config; //在初始化时加载配置参数
}
}
监听器
监听器是Servlet中定义的一种特殊类,用于监听ServletContext、HttpSession和HttpRequest域对象(分别对应jsp的application、session、request三个域)创建、属性修改或者销毁等事件,并且在事件发生前后执行操作。监听器常用于监听客户端的请求与服务器端的操作,例如统计在线人数、访问量、加载初始化信息等。
1、创建监听器
监听器本质上是一个Java的servlet类,只不过它实现了一些Listerner接口的方法,例如实现ServletContextListener接口的initialized和destroyed方法,这两个方法分别对应在servlet创建、销毁时进行调用
public class FirstListener implements ServletContextListener{
// Servlet要求保留无参构造器
public FirstListener() {
}
// web应用servlet部署时调用此方法
public void contextInitialized(ServletContextEvent sce) {
}
// servlet销毁时调用
public void contextDestroyed(ServletContextEvent sce) {
}
}
接下来需要在web.xml对监听器进行注册,该监听器位于listener包下的FirstListener:
<listener>
<listener-class>listener.FirstListener</listener-class>
</listener>
在servlet3.0以后不需要注册,而是采用注解的方式,在监听器类声明前添加一行:@WebListener("描述")
web应用按照在web.xml文件中的注册顺序启动监听器,并且先启动监听器,再启动过滤器,最后加载servlet
2、监听器的分类
1、监听对象的创建和销毁
按照监听的对象分为监听整个应用application、监听session、监听request的三种监听器,对应的需要实现ServletContextListener、HttpSessionListenter、ServletRequestListener三个接口,每个接口都有initialized()和destroyed()方法来对对应对象的创建和销毁进行监听。通过方法传入参数对象的getXxx()方法可以获取当前调用的对象,例如se.getSession()可以获取当前的session对象,进而通过监听器操作对象。
例如通过Servlet监听器获取初始化参数,并将其设置为servlet的全局参数,这样在之后整个application中都可以得到该参数
<context-param>
<param-name>initParam</param-name>
<param-value>初始化参数</param-value>
</context-param>
@Override //servlet创建时调用
public void contextInitialized(ServletContextEvent sce) {
//获取servlet初始化参数
String initParam =sce.getServletContext().getInitParameter("initParam");
//设置全局servlet参数
sce.getServletContext().setAttribute("init",initParam);
}
@Override //servlet销毁时调用
public void contextDestroyed(ServletContextEvent sce) {
}
如下为session和request监听器的创建和销毁时的监听
package listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class FirstListener implements HttpSessionListener, ServletRequestListener {
//servlet要求保留空构造器
public FirstListener() {
}
@Override //创建session时调用
public void sessionCreated(HttpSessionEvent se) {
se.getSession();
}
@Override //session销毁时调用
public void sessionDestroyed(HttpSessionEvent se) {
}
@Override //request对象创建时调用
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override //request销毁时调用
public void requestInitialized(ServletRequestEvent sre) {
}
}
2、监听属性的改变
用于监听Servlet、Session、Request中的对象属性发生改变时进行调用,其继承的接口为ServletContextAttributeListener,
HttpSessionAttributeListener, ServletRequestAttributeListener,这三个接口均需要实现三个方法:attributeAdded(),attributeRemoved(),attributeReplaced(),只不过传入的参数对象不同,例如Servlet对应传入的参数对象为ServletContextAttributeEvent scae。其余的用法都类似。例如下面通过request属性监听器来监听其属性创建、改变、删除操作
package listener;
import javax.servlet.*;
public class AttributeListener implements ServletRequestAttributeListener {
// Public constructor is required by servlet spec
public AttributeListener() {
}
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
System.out.println("request添加属性:"+srae.getName());
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
System.out.println("request删除属性:"+srae.getName());
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
System.out.println("request修改属性:"+srae.getName());
}
}
//--------------------listener.jsp页面创建并修改request属性-----------------
<%
request.setAttribute("request属性","request参数");
request.setAttribute("request属性","request参数2");
%>
Session的绑定接口HttpSessionBindingListener、钝化接口HttpSessionActivationListener不需要创建专门的监听器,而是可以在普通JavaBean类中继承和实现,例如一个Student类实现了这两个接口,则当一个Student对象被绑定/解绑到session中时,就会触发对应的方法,同理该对象被钝化/活化时也会触发。如下为Student类:
package modules;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
public class Student implements HttpSessionBindingListener, HttpSessionActivationListener {
private String name;
private int age;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("Session绑定User对象" + event.getName());
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("Session解绑User对象" + event.getName());
}
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println("Session钝化对象:" + se.getSource());
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println("Session活化对象:" + se.getSource());
}
}
//-----------------在jsp页面内绑定、解绑Student对象------------------------
<%
Student s1=new Student();
session.setAttribute("student",s1); //输出:Session绑定User对象student
session.removeAttribute("student"); //输出:Session解绑User对象student
%>