天天看點

Spring Boot學習筆記(七)Web開發之配置過濾器、攔截器、監聽器

一、監聽器

1.1簡介

監聽器是一個用于監聽其他對象狀态改變以及方法調用然後進行相應處理的對象。監聽器其實就是一個實作特定接口的普通java程式,當被監聽對象方法調用的時,監聽器的相應方法也會調用,執行操作。

1.2 原理

監聽器實作的原理如下圖所示:

Spring Boot學習筆記(七)Web開發之配置過濾器、攔截器、監聽器

代碼示例:

/**
 * 事件源類
 * @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.csdn.net/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容器中,進而實作相應的功能。

過濾器和攔截器執行步驟:

Spring Boot學習筆記(七)Web開發之配置過濾器、攔截器、監聽器

圖檔來源:https://blog.csdn.net/yudiandemingzi/article/details/80399971

繼續閱讀