Servlet、过滤器与监听器

本文介绍了Servlet过滤器的原理、Springboot中如何创建并注册Filter,以及如何通过Filter拦截请求和响应,并探讨了声明式异常处理在Web.xml配置中的应用。重点讲解了Filter生命周期、执行顺序和如何使用注解进行配置。
摘要由CSDN通过智能技术生成

Servlet

1.简介
Servlet又叫服务器端小程序,它是使用Java语言编写的服务器端程序。它可以像JSP一样,生成动态的Web网页。Servlet主要运行在服务器端,由服务器调用执行,是一个按照Servlet标准开发的类。JSP是在Servlet的基础上学习ASP技术衍生出来的技术。Servlet采用多线程的处理方式,效率高,且跨平台。
在这里插入图片描述
注:Servlet程序需要写在服务器上,下面的所有程序都是在服务器软件Tomcat上写的。

2.子类体系
①Servlet
Servlet是一个javax.servlet包(javax代表了java包的扩展包)下的接口,public interface Servlet,当一个类实现这个接口时要实现它里面的所有抽象方法:

public class HelloServlet implements Servlet {

	@Override
	public void init(ServletConfig config) throws ServletException {
		// TODO Auto-generated method stub

	}

	@Override
	public ServletConfig getServletConfig() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		// TODO Auto-generated method stub

	}

	@Override
	public String getServletInfo() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub

	}

}

②GenericServlet
GenericServlet是Servlet接口的抽象实现类,同样继承它必须实现它里面的抽象方法:

public class HelloServlet  extends GenericServlet{

	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
	}

}

③HttpServlet
HttpServlet是GenericServlet的抽象子类,它是一个专门处理HTTP请求的类。这个类中最重要的方法是doGet和doPost方法,它们分别用来处理客户端发来的Get和Post请求。

public class HelloServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		super.doGet(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		super.doPost(req, resp);
	}
}

④自定义类
如果想要将一个Java程序变成Servlet程序,只需要让程序实现或继承以上接口或类即可。

3.请求与响应
①HttpServletResponse
HttpServletResponse是doGet方法的第二个参数,它是用来对客户端做出回应的一个接口,继承自ServletResponse接口,ServletResponse里面做出回应的方法有:
public PrintWriter getWriter() throws IOException;
public ServletOutputStream getOutputStream() throws IOException;

从上面的方法可以看出对客户端做出回应是通过流来实现的。例,

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		PrintWriter pw = resp.getWriter();
		pw.write("Hello Servlet");
		pw.close();
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		super.doPost(req, resp);
	}
}

当一个Servlet程序完成后,要想正常运行还需要进行映射的配置,即在web.xml文件中进行映射的路径指定。例,

<servlet>
	<servlet-name>Hello</servlet-name>
	<servlet-class>com.web.maven.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
	<servlet-name>Hello</servlet-name>
	<url-pattern>/Hello</url-pattern>
</servlet-mapping>

url-pattern里的内容是在浏览器中输入的路径,通过这找到servlet-name,再通过servlet-name找到Servlet程序的.class文件的路径servlet-class。
此时就可以通过浏览器写入http://http://127.0.0.1:8888/MavenServlet/Hello来访问Servlet程序了。
在这里插入图片描述
其中127.0.0.1是本机地址,因为Tomcat是配置在本机上的,8888是修改后的Tomcat端口号,MavenServlet是Web项目的项目名,/Hello是用来找Servlet程序的。
注:可以为一个servlet-class配置多个url-pattern。

response是Servlet程序对客户端的响应,在它里面有一个属性叫content-type,客户端将会根据此属性的值采用对应的编译器对响应内容的二进制文件进行编译处理。默认情况下此属性的值是text。ServletResponse接口中定义了 void setContentType(String type) 方法来设置此属性的值。例,如果要输出的内容既有文本又有HTML标签时,type的值要为text/html。也可以设置字符的编码格式。例,type的值为"text/html;charset=utf-8"
注:setContentType方法要在getWriter之前使用。

②HttpServletRequest
HttpServletRequest是doGet方法的第一个参数。继承自ServletRequest的一个接口,可以读取客户端发来的请求包里的参数信息。常用方法:
StringBuffer getRequestURL()
String getMethod()
Enumeration< String> getParameterNames()
String getParameter(String name)

当客户端向服务器发送请求时,请求包分为两部分:请求头和请求体。请求头是以Get方式发送的请求,请求体是以Post发送的请求。请求包里的内容都是二进制文件,请求头由Tomcat进行解码(即将二进制文件还原为正确格式的内容),Tomcat默认使用的解码集是UTF-8;而请求体是由HttpServletRequest或ServletRequest的实例对象负责解码,该对象的默认解码集是ISO-8859-1,如果想要设置解码集可以使用void setCharacterEncoding(String env) 方法。

③生命周期
当服务器收到客户端发送的请求协议包后,它会为请求协议包生成一个HttpServletRequest请求对象和一个HttpServletResponse响应对象。这两个对象将会用在doGet或doPost方法的参数中,当服务器将响应结果包装成响应协议包发送给客户端之前,请求对象和响应对象要被服务器进行销毁。简言之,请求对象和响应对象的生命周期为一次请求的处理过程。

④默认欢迎页面
通过设置默认欢迎页面,可以使用http://服务器名:端口名/项目名访问一个默认的页面。设置位置:在当前项目下找WebContent目录下的WEB-INF目录下的web.xml文件。
设置命令:

<welcome-file-list>
  <welcome-file>A</welcome-file>
</welcome-file-list>

A为默认欢迎页面可以是HTML文件或JSP文件。

⑤Servlet间数据共享方式

  • ServletContext接口
  • Cookie类
  • HttpSession接口
  • HttpServletRequest接口

⑥ServletContext(全局作用域、上下文)
Web容器在启动的时候会为每个web程序创建一个对应的ServletContext对象,它代表了当前的web应用。
当两个Servlet程序在同一个项目(同一web程序)下时,彼此之间可以通过ServletContext实例对象进行数据共享。此时需要将一个Servlet程序中要共享的数据存入此实例对象中,ServletContext实例对象相当于一个Map。
当服务器启动时,服务器自动为项目创建一个ServletContext实例对象。当服务器关闭时,服务器会销毁ServletContext实例对象。
通过ServletRequest的ServletContext getServletContext() 方法获取当前项目的ServletContext实例对象,习惯上将此对象叫Application。将数据添加到Application中使用它的void setAttribute(String name, Object object) 方法,第一个参数为key,第二个参数为value。通过Object getAttribute(String name) 方法获取对应的值。

4.Servlet生命周期
Servlet程序的实例对象只能由服务器创建,而不能开发人员手动创建。当服务器收到客户端传来的第一次请求时它就会创建Servlet程序的实例对象,但是在手动配置下可以让服务器在启动时就创建Servlet程序的实例对象。

Servlet的生命周期包括Servlet程序的加载、初始化、运行、销毁和卸载。其中初始化对应了public void init() 方法,运行对应了 public abstract void service(ServletRequest req, ServletResponse res) 方法,销毁对应了 public void destroy() 方法。一个Servlet程序在第一次运行时会初始化即调用init方法,以后不会进行初始化;每次程序运行都会调用service方法;当Servlet程序长时间不用或服务器关闭时调用destroy方法。

在HttpServlet的继承子类中service方法对应着doGet和doPost方法,如果在子类中重写了service方法则doGet和doPost方法就不起作用了,因为HttpServlet的service方法实现是判断客户端发来的请求是Get还是Post来调用doGet和doPost方法,如果子类重写那么就会调用子类的service方法,所以在使用HttpServlet类时尽量不要重写service方法。

init方法是在Servlet程序第一次启动时才会调用的,如果想要服务器一启动就调用此方法可以在web.xml中增加配置信息。在< servlet > < /servlet>标签中加入< load-on-startup>1< /load-on-startup>,中间的数字代表优先级,默认为0。

HttpServlet类中定义了浏览器发送请求的7种方式:
private static final String METHOD_DELETE = “DELETE”;
private static final String METHOD_HEAD = “HEAD”;
private static final String METHOD_GET = “GET”;
private static final String METHOD_OPTIONS = “OPTIONS”;
private static final String METHOD_POST = “POST”;
private static final String METHOD_PUT = “PUT”;
private static final String METHOD_TRACE = “TRACE”;

5.Session和Application
①Cookie
Cookie也可以实现Servlet间的数据共享,但前提是这两个Servlet要为同一个客户端提供服务。它是javax.servlet.http包下的一个类,它保存了客户端用户的一些个人数据。Cookie数据是服务器写好返回给客户端保存的。当同一个客户端再次向服务器的同一个项目发起请求时,就会将Cookie信息一起发过去。

在Servlet程序中可以直接创建Cookie对象来保存数据,一个Cookie对象只能保存一个键值对数据,且key和value应为String类型。通过HttpServletResponse的void addCookie(Cookie cookie) 方法将Cookie信息传送给客户端。通过HttpServletRequest的Cookie[] getCookies() 方法可以获得客户端发来的Cookie对象。获得Cookie对象后可以通过以下方法获取key和value:
public String getName()public String getValue()

默认情况下,Cookie对象放在客户端的缓存中,正常情况下,当客户端关闭后,Cookie对象就会被销毁。用户可以指定将Cookie对象存放在客户端的硬盘上同时还可以指定它的存活时间,通过Cookie类的public void setMaxAge(int expiry) 方法可以设置它的存活时间。

②Session(会话)
客户端每次发送请求时,服务器都会为客户端设置一个Cookie,Cookie可以通过HttpServletRequest取得,而Session使用了Cookie的机制,所以Session也需要依靠HttpServletRequest取得。需要使用的方法:
Cookie[] getCookies()
HttpSession getSession()
HttpSession getSession(boolean create)

第二个方法会判断如果当前客户端已经创建过Session对象就将此对象返回,否则就创建Session对象。第三个方法当create为false时会判断如果当前客户端已经创建过Session对象就将此对象返回,否则就返回null。
当客户端第一次发送请求时服务器不会自动创建Session对象,只有调用第二个方法才会创建Session对象。

通过HttpSession的void setAttribute(String name, Object value) 方法可以将数据放到Session中。
Session与Cookie相同,它也可以实现Servlet间的数据共享,但前提是这两个Servlet要为同一个客户端提供服务。
与Cookie的区别:
Cookie保存在客户端的缓存或硬盘上,HttpSession 保存在服务器内存里;Cookie只能存放String类型的数据,HttpSession 可以存储任意类型的数据;Cookie只能存储一个共享数据,HttpSession 可以存储任意数量的共享数据。

判断是否是同一个客户端使用的是Session:
当客户端第一次发送请求时,服务器会生成一个Cookie,它里面有一个用来标识客户端的JSESSIONID,服务器会将它返回给客户端;当客户端再次发送请求时就会根据此JSESSIONID来判断是否是同一个客户端。
当客户端关闭时,它与服务器Session之间的关系就消失了。服务器会为Session对象设置一个空闲时间,默认是30分钟,当Session对象在此时间内未使用时就会被服务器销毁。可以在web.xml文件中对此时间进行设置:

<session-config>
	<session-timeout>A</session-timeout>
</session-config>

A为空闲时间,单位是分钟。

③URL重写
当客户端Cookie禁用时,由于不能传递JSESSIONID,所以此时无法判断是否是同一个客户端,Session也就没用了。用URL可以解决这种问题。通过HttpServletResponse的String encodeURL(String url) 方法,此方法会将当前的JSESSIONID返回给客户端,当客户端再次发起请求时这些信息会自动加在请求URL上这样就可以照常使用Session了。

④Application
Application又叫ServletContext实例对象,使用看上3.6。

6.跳转
①服务器端跳转(请求转发)
地址栏不改变,可以传递request范围的属性,属于无条件跳转,只要执行到了语句,则立刻跳转。需要依赖javax.servlet包下的RequestDispatcher接口。此接口中有两个方法:
void forward(ServletRequest request, ServletResponse response)
void include(ServletRequest request, ServletResponse response)

要想使用以上两个方法需要RequestDispatcher的实例化对象,此时就需要ServletRequest的**RequestDispatcher getRequestDispatcher(String path)**方法,path应以/开头。
从开始到结束客户端只发送一次请求。只能访问当前项目下的文件。此时的请求方式根据客户端而定。
当采用服务器端跳转时,会将ServletRequest和ServletResponse对象传递给要跳转的Servlet程序,即两个Servlet程序间共享一个请求协议包,此时可以利用ServletRequest在两个程序间进行数据传输。

②客户端跳转(重定向)
地址栏跳转之后改变,无法传递request范围的属性,在所有的操作都执行完毕后才发生跳转的操作。需要依赖HttpServletResponse的 void sendRedirect(String location) 方法。当跳转的是本项目的资源文件时,location为"/项目名/资源类名";当跳转的是其他项目的资源文件时,location为完整的路径名。
通过客户端跳转后地址栏会发生改变,所以是通过地址栏发出了请求,而通过地址栏发送的请求一定是GET请求。此时,用户只通过客户端发送了一次请求,其余都是客户端自己发送的请求。

缺点是浪费时间。

过滤器

简介

Filter是Servlet规范的接口,该接口的实现类需要由开发人员创建。在一个请求去访问某个资源的时候,Filter实现类可以在这个请求访问到这个资源之前把请求拦下,然后做出一系列的处理或者判断(比如编码的转换,信息的过滤、权限的判断、是否已经登录的验证等等),最后Filter实现类再决定是否要让这个请求去访问那个资源。一般一个过滤器只干一件事,所以要处理多种情况就需要多个过滤器,此时客户端发来的请求就要经过一个一个的过滤器,这些过滤器就组成了过滤器链。

当然,过滤器既可以拦截request,也可以拦截返回的response。

在这里插入图片描述

需要实现的方法

default void init(FilterConfig filterConfig)
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
default void destroy()

Springboot 使用Spring专属Filter

创建Filter

@Component
public class TestFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        System.out.println("请求头类型: " + request.getContentType());
        System.out.println("响应:" + response.getStatus());
        filterChain.doFilter(request, response);
        System.out.println("响应:" + response.getStatus());
        stopWatch.stop();
        System.out.println("耗时:" + stopWatch.getTotalTimeSeconds());
    }
}

注册过滤器

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<TestFilter> customFilterRegistrationBean() {
        FilterRegistrationBean<TestFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new TestFilter());
        registrationBean.addUrlPatterns("/*"); // 设置过滤器的URL匹配规则
        registrationBean.setOrder(1); // 设置过滤器的执行顺序
        return registrationBean;
    }
}

修改Controller

@GetMapping("/getSalary")
public List<Salary> getSalary(String columnName, String year, HttpServletResponse response) throws InterruptedException {
    Thread.sleep(5000);
    response.setStatus(500);
    return salaryMapper.getSalary(columnName, year);
}

结果及说明

请求头类型: null
响应:200
响应:500
耗时:5.3048115

从上述看,响应状态在filterChain.doFilter(request, response);前后发生了变化。
方法filterChain.doFilter(request, response);代表当前过滤器执行结束,传递给下一个过滤器,如果没有下一个过滤器就开始执行Controller方法,Controller方法获取HttpServletResponse并将它的状态改为500,在方法filterChain.doFilter(request, response);后打印的response的状态为500说明了:过滤器拦截了返回的response。

注册过滤器

注册过滤器除了以上spring的方式还有:

web.xml配置

web.xml中注册:

<filter>
	<filter-name>过滤器名</filter-name>
	<filter-class>过滤器类全路径名</filter-class>
</filter>
<filter-mapping>
	<!-- 这里的标签是为了与上面filter中的名字对应 -->
	<filter-name>过滤器名</filter-name> 
	<!-- 拦截路径 -->
	<url-pattern>A</url-pattern>
</filter-mapping>

此处与Servlet程序的配置大致相同,但A处的信息为资源的路径信息这表示哪些资源文件需要被过滤,一般是/*,表示所有资源,也可以是文件.后缀名,当使用后缀名时前面不能有任何路径或斜杠。默认情况下,当服务器启动时过滤器就会启动,此时就会调用第一个初始化方法。如果想要客户端发来的请求经过过滤器时继续向下发而不是拦截阻止,此时就需要使用第二个方法,在方法中通过chain.doFilter(request, response);语句将请求继续向下发;对资源文件的处理或者判断也需要写在此方法中。当服务器停止时就会调用第三个方法将过滤器销毁。
注:资源文件包括Servlet程序、HTML程序等。

当要过滤的资源文件中出现跳转时过滤器不会对跳转后的文件进行过滤,如果想要过滤需要在filter-mapping中配置以下信息:

<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>

其中,第一条语句是默认就有的。

  • REQUEST:默认值,浏览器直接请求资源
  • FORWARD:转发访问资源 : RequestDispatcher.forward();
  • INCLUDE:包含访问资源 : RequestDispatcher.include();
  • ERROR:错误跳转资源 : 被声明式异常处理机制调用的时候
执行顺序

web.xml配置中谁在上面,谁优先执行

拦截路径

在这里插入图片描述

声明式异常处理

在web.xml中通过配置来确定不同的异常类型将如何被处理,最后跳转到哪个页面,也就是我们常常看到的一些404错误页面:

<error-page>
      <!--异常的类型如IOException-->
         <exception-type>xxx</exception-type>
      <!--异常发生时跳转的页面-->
        <location>xxx</location>
</error-page>

使用注解配置

直接在Filter类实现类上配置:javax.servlet.annotation.WebFilter

  • 指定它的名字和拦截路径@WebFilter("filterName="FilterDemo1",urlPatters="/*")
  • 不需要指定其名字@WebFilter("/*")
  • 若想在filter注解中配置dispatcher,我们需要设置dispatcherTypes属性@WebFilter(value = "/*",dispatcherTypes ={DispatcherType.FORWARD,DispatcherType.FORWARD} )

非Spring示例

@WebFilter("/*")
public class LoginFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //获取资源请求路径
        String requestURI = request.getRequestURI();

        //排除包含登录确实所需要的资源,给予放行
        if (requestURI.contains("/login.jsp") || requestURI.contains("/loginServlet")) {
            chain.doFilter(request, response);
        } else {
            //不包含,即验证用户是否已经登录
            Object user = request.getSession().getAttribute("user");
            if (user != null) {
                //登陆了,放行
                chain.doFilter(request, response);
            } else {
                //没有登录,跳转回登录页面
                request.getRequestDispatcher("/login.jsp").forward(request, response);
            }
        }
    }

    public void init(FilterConfig config) throws ServletException {
    }
}

FilterRegistrationBean

FilterRegistrationBean是Spring框架提供的一个实用类,用于注册和配置Filter过滤器。它提供了一些方法来设置过滤器的属性、URL匹配规则、执行顺序等。
FilterRegistrationBean的主要作用是将Filter过滤器注册到Servlet容器中。

  • setFilter(Filter filter):设置要注册的Filter对象。
  • setName(String name):设置Filter的名称,可以用于在其他地方引用该Filter。
  • setUrlPatterns(List urlPatterns):设置过滤器的URL匹配规则,指定哪些URL需要经过该过滤器。
  • addUrlPatterns(String… urlPatterns):为过滤器添加URL匹配规则。
  • setOrder(int order):设置过滤器的执行顺序,数字越小优先级越高。
  • setInitParameters(Map<String, String> initParameters):设置过滤器的初始化参数,可以在过滤器中使用这些参数。
  • addInitParameter(String name, String value):为过滤器添加初始化参数。
  • setAsyncSupported(boolean asyncSupported):设置过滤器是否支持异步请求。

8.监听器
①简介
监听器是一组来自Servlet规范的接口,共有8个。该接口的实现类需要由开发人员创建,它用于监控作用域对象的生命周期变化和作用域对象里共享数据的变化。作用域对象就是在服务器中可以为多个Servlet程序提供数据共享服务的对象。有:ServletContext、ServletRequest、HttpSession。

②ServletContextListener
常用方法:
default void contextInitialized(ServletContextEvent sce)
default void contextDestroyed(ServletContextEvent sce)

第一个方法在ServletContextListener实现类对象初始化时运行,第二个方法在ServletContextListener实现类对象销毁时运行。要想ServletContextListener实现类起作用还需要在web.xml中注册:

<listener>
	<listener-class>A</listener-class>
</listener>

A是ServletContextListener实现类的完整路径即包名+类名。

③ServletContextAttributeListener
对ServletContext对象也叫Application中共享数据的变化进行监控。常用方法:
default void attributeAdded(ServletContextAttributeEvent event)
default void attributeRemoved(ServletContextAttributeEvent event)
default void attributeReplaced(ServletContextAttributeEvent event)

Application中共享数据增加时调用第一个方法,更新数据时调用第三个方法,删除数据时调用第二个方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值