一、监听器
1.1简介
监听器是一个用于监听其他对象状态改变以及方法调用然后进行相应处理的对象。监听器其实就是一个实现特定接口的普通java程序,当被监听对象方法调用的时,监听器的相应方法也会调用,执行操作。
1.2 原理
监听器实现的原理如下图所示:
代码示例:
/**
* 事件源类
* @author dmf
*
*/
public class Person {
//监听器对象
private PersonListener personListener;
private String name;
public Person(){
}
//注册监听器对象
public Person(PersonListener personListener){
this.personListener = personListener;
}
//
public void sleep(){
System.out.println(this.name+"躺下睡觉!");
//如果注册了监听器,就开始监听sleep方法
if(personListener!=null){
personListener.doSleep(new Event(this));
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 事件类
* @author dmf
*
*/
class Event {
//封装事件源
private Person source;
public Event(Person source){
this.source = source;
}
public Person getPerson() {
return source;
}
}
/**
* 监听器接口
* @author dmf
*
*/
interface PersonListener{
//监听person的sleep方法,可以在实现类的doSleep方法中做一些监听的处理
public void doSleep(Event evt);
}
测试类:
public class ListenerMain {
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("张三");
p1.sleep();
Person p2 = new Person(new PersonListener(){
@Override
public void doSleep(Event evt) {
System.out.println("监听到"+evt.getPerson().getName()+"正在睡觉!");
}
});
p2.setName("李四");
p2.sleep();
}
}
运行结果:
张三躺下睡觉!
李四躺下睡觉!
监听到李四正在睡觉!
1.3 java Web监听器
java Web中的监听器listener是servlet规范中定义的一种特殊类。用于监听servletContext、HttpSession和servletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。其主要可用于以下方面:1、统计在线人数和在线用户2、系统启动时加载初始化信息3、统计网站访问量4、记录用户访问路径。原理和上面的类似,以HttpSessionListener为例,找到org.apache.catalina.session.StandardSession类,以tellNew方法为例:
//此方法就是创建HttpSession时,调用监听器的sessionCreated方法。
public void tellNew() {
// Notify interested session event listeners
fireSessionEvent(Session.SESSION_CREATED_EVENT, null);
// Notify interested application event listeners
Context context = manager.getContext();
//这里是通过方法获取监听器对象,上面的例子是在事件源中封装监听器对象
Object listeners[] = context.getApplicationLifecycleListeners();//取出所有监听器
if (listeners != null && listeners.length > 0) {
//创建事件对象,事件源通过getSession()方法获取,就是StandardSessionFacade对象它是HttpSession的子类
HttpSessionEvent event =
new HttpSessionEvent(getSession());
for (int i = 0; i < listeners.length; i++) {
if (!(listeners[i] instanceof HttpSessionListener))//判断是不是HttpSessionListener类型
continue;
HttpSessionListener listener =
(HttpSessionListener) listeners[i];
try {
context.fireContainerEvent("beforeSessionCreated",
listener);
listener.sessionCreated(event);//调用监听器的sessionCreated方法。
context.fireContainerEvent("afterSessionCreated", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
try {
context.fireContainerEvent("afterSessionCreated",
listener);
} catch (Exception e) {
// Ignore
}
manager.getContext().getLogger().error
(sm.getString("standardSession.sessionEvent"), t);
}
}
}
}
@Override
public HttpSession getSession() {
if (facade == null) {
if (SecurityUtil.isPackageProtectionEnabled()) {
//实际上返回的是一个StandardSessionFacade对象,这个类的父类就是HttpSession
//可以在监听器上打断点观察HttpSession的类型是不是StandardSessionFacade。
//this表示把此类对象其实就是(StandardSession)封装成StandardSessionFacade,
//StandardSession也是HttpSession的子类
facade = AccessController.doPrivileged(new PrivilegedNewSessionFacade(this));
} else {
facade = new StandardSessionFacade(this);
}
}
return facade;
}
监听servletContext对象的监听器:
总共有两种:ServletContextListener和ServletContextAttributeListener,前者用来监控application内置对象的创建和销毁。后者用来监控对象含有的属性(通过setAttribute方法设置的)的新增、删除和修改。源码如下:
public interface ServletContextListener extends EventListener {
//servletContext对象初始化时调用
public default void contextInitialized(ServletContextEvent sce) {
}
//servletContext对象销毁时调用
public default void contextDestroyed(ServletContextEvent sce) {
}
}
public interface ServletContextAttributeListener extends EventListener {
//添加属性时调用
public default void attributeAdded(ServletContextAttributeEvent scae) {
}
//移除属性时调用
public default void attributeRemoved(ServletContextAttributeEvent scae) {
}
//修改属性时调用
public default void attributeReplaced(ServletContextAttributeEvent scae) {
}
}
监听HttpSession对象的监听器:
总共有五种:HttpSessionListener、HttpSessionIdListener、HttpSessionBindingListener、HttpSessionAttributeListener、HttpSessionActivationListener。作用分别是监听session内置对象的创建和销毁、监听会话id是否发生变化、监听POJO类(需要实现HttpSessionBindingListener)对象的事件、监听对象含有的属性的新增、删除和修改、监听Session数据的钝化与活化(不用的session数据序列化到本地文件中的过程,就是钝化;当再次访问需要到该session的内容时,就会读取本地文件,再次放入内存中,这个过程就是活化。)。
源码:
public interface HttpSessionListener extends EventListener {
//session对象创建时调用
public default void sessionCreated(HttpSessionEvent se) {
}
//session对象销毁时调用
public default void sessionDestroyed(HttpSessionEvent se) {
}
}
public interface HttpSessionIdListener extends EventListener {
//当session对象的id发生变化时调用
public void sessionIdChanged(HttpSessionEvent se, String oldSessionId);
}
public interface HttpSessionBindingListener extends EventListener {
//对象绑定到session中时调用
public default void valueBound(HttpSessionBindingEvent event) {
}
//对象从session中解绑时调用
public default void valueUnbound(HttpSessionBindingEvent event) {
}
}
//同ServletContextAttributeListener
public interface HttpSessionAttributeListener extends EventListener {
public default void attributeAdded(HttpSessionBindingEvent se) {
}
public default void attributeRemoved(HttpSessionBindingEvent se) {
}
public default void attributeReplaced(HttpSessionBindingEvent se) {
}
}
public interface HttpSessionActivationListener extends EventListener {
//Session数据钝化
public default void sessionWillPassivate(HttpSessionEvent se) {
}
//Session数据活化
public default void sessionDidActivate(HttpSessionEvent se) {
}
}
监听servletRequest对象的监听器:
总共有两种:ServletRequestListener和ServletRequestAttributeListener。作用分别是:监控request内置对象的创建和销毁;监听属性的新增、删除和修改。源码:
public interface ServletRequestListener extends EventListener {
//request对象销毁时调用
public default void requestDestroyed (ServletRequestEvent sre) {
}
//request对象初始化时调用
public default void requestInitialized (ServletRequestEvent sre) {
}
}
//同ServletContextAttributeListener
public interface ServletRequestAttributeListener extends EventListener {
public default void attributeAdded(ServletRequestAttributeEvent srae) {
}
public default void attributeRemoved(ServletRequestAttributeEvent srae) {
}
public default void attributeReplaced(ServletRequestAttributeEvent srae) {
}
}
1.4 监听器实例
在Spring Boot中实现监听器功能,只需要根据功能写个对应监听器的实现类,然后注册到容器中即可。以HttpSessionListener实现统计网站在线人数功能为例:
方式一:使用Spring Boot配置类。
//@WebListener//servlet 3.0的注解
public class MyHttpSessionListener implements HttpSessionListener{
private static int count=0;
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("创建session");
count+=1;
se.getSession().getServletContext().setAttribute("onlineNum", count);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("销毁session");
count-=1;
se.getSession().getServletContext().setAttribute("onlineNum", count);
}
}
@Configuration
public class MyConfig{
@SuppressWarnings({ "rawtypes", "unchecked" })//去掉警告
@Bean
public ServletListenerRegistrationBean listenerRegist() {
ServletListenerRegistrationBean srb = new ServletListenerRegistrationBean();
srb.setListener(new MyHttpSessionListener());
return srb;
}
}
方式二:
使用servlet3.0注解。监听类加上@WebListener注解然后在主类上加上@ServletComponentScan方法
@SpringBootApplication
@ServletComponentScan//扫描servlet3.0的注解配置
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
前端页面使用pageContext(jsp)/application(thymeleaf)对象取出即可。
二、过滤器
2.1 简介
Filter是Servlet规范中定义的特殊类,通过Filter技术,可以对web服务器的所有web资源进行拦截:例如Jsp, Servlet, 静态图片文件或静态 html 文件等,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息,设置请求响应编码等一些高级功能。它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
2.2 实现
以Filter进行登陆验证为例,在Spring Boot中使用Filter有两种方式:
方式一:
编写过滤器类并实现Filter接口,重写doFilter方法:
public class MyFilter implements Filter{
/**
* 判断是否登陆
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse res = (HttpServletResponse)response;
//设置编码
req.setCharacterEncoding("utf-8");
res.setContentType("text/html;charset=utf-8");
String url = req.getRequestURI();
//放行首页、登陆页以及静态资源(asserts文件夹是保存静态资源的文件夹)
if(url.indexOf("/index.html") != -1 ||
url.indexOf("/user/login") != -1||url.indexOf("/asserts/") != -1||url.indexOf("/webjars/") != -1||
url.indexOf("favicon.ico") != -1|| url.endsWith("dmf/")
) {
chain.doFilter(req, res); //通过过滤器,放行,执行下一个过滤器
}else {
Object obj = req.getSession().getAttribute("loginUser");
if(obj==null){
//未登录
req.setAttribute("msg", "没有权限请先登录!");
req.getRequestDispatcher("/index.html").forward(req, res);
}else{
chain.doFilter(req, res);
}
}
}
//filte初始化调用
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
//filte销毁调用
@Override
public void destroy() {
Filter.super.destroy();
}
}
然后在配置类里把过滤器加到容器中:
@Configuration
public class MyConfig{
@SuppressWarnings({ "rawtypes", "unchecked" })//去除警告
@Bean
public FilterRegistrationBean filterRegist() {
FilterRegistrationBean frBean = new FilterRegistrationBean();
frBean.setFilter(new MyFilter());
frBean.addUrlPatterns("/*");//设置过滤路径
frBean.setName("myfilter");设置过滤器名称
frBean.setOrder(0);//设置过滤器优先级
return frBean;
}
}
方式二:注解方式。
@WebFilter(urlPatterns = "/*", filterName = "logFilter2")//过滤路径和过滤器名称
public class MyFilter implements Filter{
...
}
在入口类上加上@ServletComponentScan注解;
@ServletComponentScan//扫描servlet3.0的注解配置,可以加上包名,扫描指定包下
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@WebFilter这个注解并没有指定执行顺序的属性,其执行顺序依赖于Filter的名称,是根据Filter类名(注意不是配置的filter的名字)的字母顺序倒序排列
三、拦截器
3.1 简介
过滤器只在servlet前后起作用,想要在servlet里面进行操作,只有通过拦截器来实现。拦截器用在某个方法或字段,被访问时进行拦截,在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。
3.2 Spring Boot实现拦截器
引用:https://blog.youkuaiyun.com/hongxingxiaonan/article/details/48090075
Spring Boot可以实现两种拦截器,一个是HandlerInterceptor,一个是MethodInterceptor。前者用在springMVC中,拦截目标是请求的地址,后者是AOP中的拦截器,它拦截的目标是方法。这里主要讲的是HandlerInterceptor拦截器。
3.2.1 HandlerInterceptor拦截器
HandlerInterceptor比MethodInterceptor先执行。实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。
实例:
以登陆验证功能为例:
1、编写拦截器类
public class LoginHandlerInterceptor implements HandlerInterceptor{
//在控制器执行前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
Object obj = request.getSession().getAttribute("loginUser");
if(obj==null){
//未登录
request.setAttribute("msg", "没有权限请先登录!");
request.getRequestDispatcher("/index.html").forward(request, response);
return false;//没有通过拦截器,返回登录页面
}else{
//已登陆
return true;//通过拦截器,继续执行请求
}
}
//在后端控制器执行后调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
//整个请求执行完成后调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
2、将拦截器加到容器中。
方法一:在配置类中加入
@Configuration
public class MyConfig{
@Bean//该注解表示将方法的返回值加到容器中,名称是方法名
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer(){
//这个方法是添加视图映射的方法,
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO Auto-generated method stub
//springboot1版本已经做好静态资源映射:*.css*.js,2版本需要自己添加静态资源的放行规则
//addPathPatterns方法表示添加拦截目标,excludePathPatterns方法表示放行目标。
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/asserts/**","/webjars/**","**/favicon.ico");
}
};
}
}
方法二:编写一个配置类实现WebMvcConfigurer接口,然后重写addInterceptors方法。
@Configuration
public class MyConfig implements WebMvcConfigurer{
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO Auto-generated method stub
//springboot已经做好静态资源映射:*.css*.js
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/asserts/**","/webjars/**","**/favicon.ico");
}
}
四、Servlet
1、配置类注册方式
示例:
public class MyServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello!");
}
}
然后在配置类中注册
@Configuration
public class MyConfig{
@Bean
public ServletRegistrationBean servletRegist(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return registrationBean;
}
}
2、注解方式。
在Spring Boot的主类上开启Servlet注解扫描,然后在自己的Servlet加上注解即可。
@WebServlet("/myServlet")
public class MyServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("Hello!");
}
}
@SpringBootApplication
@ServletComponentScan("com.dmf.servlet")//扫描servlet3.0的注解配置,可以加上包名,扫描指定包下
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
五、总结
监听器和过滤器是Servlet规范的技术,依赖servlet容器,而拦截器不依赖servlet容器,一般拦截器方法都是通过动态代理的方式实现。过滤器只在servlet前后起作用,拦截器可以作用于方法,而监听器只监听servletContext、HttpSession、servletRequest这三个对象的事件。在Spring Boot中使用时,首先实现相应的接口定义类,然后通过配置类将其加入到spring容器中,从而实现相应的功能。
过滤器和拦截器执行步骤:
图片来源:https://blog.youkuaiyun.com/yudiandemingzi/article/details/80399971