过滤器、拦截器、监听器和AOP

  created  by  鱼鱼 {{tag}}
创建于 2018年12月13日 09:47:23 最后修改于 2020年03月01日 23:32:24

    用这篇文章来梳理一下这些杂七杂八的Spring MVC中的基础概念,顺便讲一下在项目中的一些基本使用和常见应用(其实主要是针对AOP的),至于使用他们实现具体的功能,后续可能会独立写出来(谁知道呢)

    执行顺序

    执行的顺序:

项目初始化:filter:init()->filter:doFilter()->preHandle->Controller->postHandle->afterComplition ->destory()

    过滤器

    过滤器(Filter),由servlet提供,拦截URL(其实是servlet),经过代理,执行想要的方法,最基本的使用是集成Filter类并重写方法,因为是从url层面上直接拦截,可以有很多用途,比如用于用户身份校验,比如某些页面需要有用户权限才能访问,就可以利用过滤器进行拦截,一些安全框架的鉴权本身也是过滤器的实现。

    过滤器代码实现

@WebFilter(filterName="loginFilter",urlPatterns="/*.html")
public class TestFilter implements Filter {
     private String param1;
 
	@Override
	public void destroy() {
		System.out.println("销毁容器");
	}
         
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
	 FilterChain filterChain)
			throws IOException, ServletException {
		if(!UserUtil.checkLogin()){
		    response.sendRedirect("login.html"); 
		}else{
		    filterChain.doFilter(request, response);
		}
		System.out.println("过滤权限");        
	
	} 
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.param1 = filterConfig.getInitParameter("param1");
	}
}

    我们经常重写doFilter()方法实现拦截,这里模拟了一个全网的用户身份校验,没有登录的用户会重定向到登录页面,其中filterChain.doFilter(arg0, arg1)代表执行请求内容,destroy()和init方法是在容器创建前后执行的,多用于配置。

    注册过滤器

    上面利用@WebFilter注解不需要配置,如要配置过滤器可以在web.xml中这样配置注册(这也是最初版本的filter配置内容):

<filter>  
    <filter-name>testFilter</filter-name> 
    <filter-class>com.fish.filter.loginFilter</filter-class>
    <init—param>  
        <param—name>param1</param-name>       
        <param-value>0</param-value>  
    </init—pamm>  
</filter> 
<filter-mapping>
    <filter-name>testFilter</filter-name>  
    <url—pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>    //这可以根据来源筛选 如REQUEST、FROWARD等
</filter-mapping>

    后面也可以使用注解配置:

@Configuration
public class TestFilterConfig extends WebMvcConfigurerAdapter{

    @Bean
    public TestFilter testFilter() {
        return new TestFilter();
    }
 
    @Bean
    public FilterRegistrationBean filterProxy() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(testFilter());
        registration.addUrlPatterns("/*");
        registration.setName("testFilter");
        return registration;
        
    }
}


    拦截器

    拦截器(interceptor),是具体视图层的实现,此处的拦截器由Spring提供,有人喜欢将过滤器与拦截器混为一谈,但这其实是两个不同的东西,只是达成的目的基本一致,过滤器是针对url匹配具体的servlet,拦截器则是直接匹配url,实际上拦截器是在过滤器doFilter()中执行的。

    与过滤器的对比

    以下内容引自HandlerInterceptor的源码文档注释:


HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it just allows custom pre-processing with  the option of prohibiting the execution of the handler itself, and custom post-processing. Filters are more powerful, for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context. 


    大意就是:拦截器类似于过滤器,但是不同于过滤器的是,拦截器只允许在请求基础上自定义其它处理内容,并不能直接操作请求本身,同时过滤器配置在web.xml,而拦截器配置于ApplicationContext中。

    过滤器中的filterChain.doFilter()会直接涉及到具体的请求,显然,拦截器的权限更低,但是也更加安全易用。

    拦截器代码实现

@Component
public class TestInterceptor implements HandlerInterceptor{
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
     Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
     Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
     Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

    这三个方法与filter不同,都是在请求中发生的,preHandle()是请求url之前执行的方法,postHandle()是在请求之后,返回视图前的方法,而afterCompletion是在整个请求执行完毕后执行的方法。preHandle()含有返回值,当返回false后,请求将不过Controller层,直接执行afterCompletion()并结束请求。

    注册拦截器

    使用注解:

@Configuration
public class TestInterceptorConfig implements WebMvcConfigurer {
    @Autowired
    TestInterceptor testInterceptor;    

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(testInterceptor).addPathPatterns("/*");
    }
}

    在项目xml中的配置:

application.xml

<mvc:interceptors>
  <mvc:interceptor>
     <mvc:mapping path="/**" />
     <mvc:exclude-mapping path="/api/login"/>
     <bean id="testInterceptor" class="com.fish.interceptor.TestInterceptor"></bean> 
  </mvc:interceptor>
</mvc:interceptors>

  监听器

    此处特指Servlet的监听器,其余(如Listener)不包含在其中。

    类似于javascript中被广泛使用的事件监听,java中的监听器监听对象是web应用中某些对象的初始化、销毁、改变等事件,并对事件作出相应响应,可以用于埋点统计网站数据。

    监听器的分类

    监听器主要是如下三类:

  • ServletContext对象监听器

  • HttpSession对象监听器

  • ServletRequest对象监听器

    可以监听的范围包含对象和属性的创建和销毁。

    监听实例

    比如,可以使用如下代码监听用户会话的创建和销毁从而在线人数:

@WebListener
public class onLineCount implements HttpSessionListener {

    public int count=0;//记录session的数量
    @Override
    public void sessionCreated(HttpSessionEvent arg0) {//监听session的创建
        count++;
        arg0.getSession().getServletContext().setAttribute("Count", count);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent arg0) {//监听session的撤销
        count--;
        arg0.getSession().getServletContext().setAttribute("Count", count);
    }

}

    除了使用注解配置一个监听器,还可以在web.xml中:

<listener>
     <listener-class>com.ygj.control.onLineCount</listener-class>
</listener>

    监听器优先于过滤器。

    监听器还有很多内容,先不写在这里了。

    AOP

    其实这篇文章主要就是为了说明AOP的使用和原理。

    AOP(面向切面)是Spring两大核心之一,其实也是一种代理(可以参阅设计模式中的代理模式),过滤器与拦截器都是针对一个servlet或是url的,但是AOP是针对具体类的代理,在各个关键点执行对应的方法。

    原理简述与动态代理

    AOP采用JDKProxy和Cglib进行代理,默认使用JDKProxy来进行动态代理,接下来解释一下动态代理。

    AOP的动态代理要区别与静态态代理,静态代理指直接将代理代码织入字节码中。

    代理类实现InvocationHandler,实现invoke(Object proxy, Method method, Object[] args)方法,方法的调用都会被转发到invoke方法

    调用方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)生成代理对象,其中handler指定了代理类,interfaces指定了实现的接口类(即可转化的类),对该对象的所有方法调用都会被转发到invoke方法上(不包括hashCode(),equals()和toString()),

    //TODO

    相关概念

    在AOP中,我们要先了解以下几点概念:

  • Aspect:切面,一个类,用于拦截待拦截的类,在里面定义切入点和通知;

  • JoinPoint:连接点,程序中需要切入的点,通常是一个方法;

  • Advice:通知,在切入点上执行的具体处理,包括before、after等;

  • PointCut:切入点,带有通知的连接点,其实是一个符号或标志;

    常用通知分类

  • Before:连接点执行前执行,不干扰连接点内容执行,没有拦截功能;

  • Around:连接点前后执行,有过滤的功能,可选择性的执行方法,类似于前文中的doFilter();

  • After:连接点后执行,类比于try语句的finally,无论如何都会执行;

  • AfterReturning:连接点后执行,且仅在不抛出异常的情况下执行;

  • AfterThrowing:连接点抛出异常后执行;

    使用AOP

1.使用xml启动AOP功能:

<aop:aspectj-autoproxy />

2.使用注解定义一个切面类

    使用@Aspect来标示一个切面。

@Aspect        //声明切面类
@Service
public class AspectAop {    
    @Pointcut("execution(* com.lili.maven.service.annotationaop.UserService.addUser(..))")  
    //切入点,在其中定义将要拦截的类
    public void pointcut() {
    }

    @Before("pointcut()")    
    public void doBefore() {
        System.out.println("doBefore advice");
    }

    @AfterReturning("pointcut()")    
    public void doAfterReturning() {
        System.out.println("doAfterReturning advice");
    }

    @After("pointcut()")    
    public void doAfter() {
        System.out.println("doAfter advice");
    }

    @AfterThrowing("pointcut()")    
    public void doAfterThrowing() {
        System.out.println("doAfterThrowing advice");
    }

    @Around("pointcut()")    
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("doAround advice start");
        Object result = pjp.proceed();
        System.out.println("doAround advice end");        return result;
    }

}

    在定义通知时,直接指明了切入点方法,也可以按照与切入点相同的方式定义,比如:

@Before("execution (* com.lilin.maven.service.annotationaop.UserService.addUser(..))")

    切入点标志pointcut(),可以更便捷的配置通知。

参考链接:Java监听器Listener使用详解

评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

过滤器、拦截器、监听器和AOP

过滤器、拦截器、监听器和AOP

    用这篇文章来梳理一下这些杂七杂八的Spring MVC中的基础概念,顺便讲一下在项目中的一些基本使用和常见应用(其实主要是针对AOP的),至于使用他们实现具体的功能,后续可能会独立写出来(谁知道呢)

    执行顺序

    执行的顺序:

项目初始化:filter:init()->filter:doFilter()->preHandle->Controller->postHandle->afterComplition ->destory()

    过滤器

    过滤器(Filter),由servlet提供,拦截URL(其实是servlet),经过代理,执行想要的方法,最基本的使用是集成Filter类并重写方法,因为是从url层面上直接拦截,可以有很多用途,比如用于用户身份校验,比如某些页面需要有用户权限才能访问,就可以利用过滤器进行拦截,一些安全框架的鉴权本身也是过滤器的实现。

    过滤器代码实现

@WebFilter(filterName="loginFilter",urlPatterns="/*.html")
public class TestFilter implements Filter {
     private String param1;
 
	@Override
	public void destroy() {
		System.out.println("销毁容器");
	}
         
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
	 FilterChain filterChain)
			throws IOException, ServletException {
		if(!UserUtil.checkLogin()){
		    response.sendRedirect("login.html"); 
		}else{
		    filterChain.doFilter(request, response);
		}
		System.out.println("过滤权限");        
	
	} 
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		this.param1 = filterConfig.getInitParameter("param1");
	}
}

    我们经常重写doFilter()方法实现拦截,这里模拟了一个全网的用户身份校验,没有登录的用户会重定向到登录页面,其中filterChain.doFilter(arg0, arg1)代表执行请求内容,destroy()和init方法是在容器创建前后执行的,多用于配置。

    注册过滤器

    上面利用@WebFilter注解不需要配置,如要配置过滤器可以在web.xml中这样配置注册(这也是最初版本的filter配置内容):

<filter>  
    <filter-name>testFilter</filter-name> 
    <filter-class>com.fish.filter.loginFilter</filter-class>
    <init—param>  
        <param—name>param1</param-name>       
        <param-value>0</param-value>  
    </init—pamm>  
</filter> 
<filter-mapping>
    <filter-name>testFilter</filter-name>  
    <url—pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>    //这可以根据来源筛选 如REQUEST、FROWARD等
</filter-mapping>

    后面也可以使用注解配置:

@Configuration
public class TestFilterConfig extends WebMvcConfigurerAdapter{

    @Bean
    public TestFilter testFilter() {
        return new TestFilter();
    }
 
    @Bean
    public FilterRegistrationBean filterProxy() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(testFilter());
        registration.addUrlPatterns("/*");
        registration.setName("testFilter");
        return registration;
        
    }
}


    拦截器

    拦截器(interceptor),是具体视图层的实现,此处的拦截器由Spring提供,有人喜欢将过滤器与拦截器混为一谈,但这其实是两个不同的东西,只是达成的目的基本一致,过滤器是针对url匹配具体的servlet,拦截器则是直接匹配url,实际上拦截器是在过滤器doFilter()中执行的。

    与过滤器的对比

    以下内容引自HandlerInterceptor的源码文档注释:


HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it just allows custom pre-processing with  the option of prohibiting the execution of the handler itself, and custom post-processing. Filters are more powerful, for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context. 


    大意就是:拦截器类似于过滤器,但是不同于过滤器的是,拦截器只允许在请求基础上自定义其它处理内容,并不能直接操作请求本身,同时过滤器配置在web.xml,而拦截器配置于ApplicationContext中。

    过滤器中的filterChain.doFilter()会直接涉及到具体的请求,显然,拦截器的权限更低,但是也更加安全易用。

    拦截器代码实现

@Component
public class TestInterceptor implements HandlerInterceptor{
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
     Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
     Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
     Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

    这三个方法与filter不同,都是在请求中发生的,preHandle()是请求url之前执行的方法,postHandle()是在请求之后,返回视图前的方法,而afterCompletion是在整个请求执行完毕后执行的方法。preHandle()含有返回值,当返回false后,请求将不过Controller层,直接执行afterCompletion()并结束请求。

    注册拦截器

    使用注解:

@Configuration
public class TestInterceptorConfig implements WebMvcConfigurer {
    @Autowired
    TestInterceptor testInterceptor;    

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(testInterceptor).addPathPatterns("/*");
    }
}

    在项目xml中的配置:

application.xml

<mvc:interceptors>
  <mvc:interceptor>
     <mvc:mapping path="/**" />
     <mvc:exclude-mapping path="/api/login"/>
     <bean id="tmptestInterceptor" class="com.fish.interceptor.TestInterceptor"></bean> 
  </mvc:interceptor>
</mvc:interceptors>

  监听器

    此处特指Servlet的监听器,其余(如Listener)不包含在其中。

    类似于javascript中被广泛使用的事件监听,java中的监听器监听对象是web应用中某些对象的初始化、销毁、改变等事件,并对事件作出相应响应,可以用于埋点统计网站数据。

    监听器的分类

    监听器主要是如下三类:

    可以监听的范围包含对象和属性的创建和销毁。

    监听实例

    比如,可以使用如下代码监听用户会话的创建和销毁从而在线人数:

@WebListener
public class onLineCount implements HttpSessionListener {

    public int count=0;//记录session的数量
    @Override
    public void sessionCreated(HttpSessionEvent arg0) {//监听session的创建
        count++;
        arg0.getSession().getServletContext().setAttribute("Count", count);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent arg0) {//监听session的撤销
        count--;
        arg0.getSession().getServletContext().setAttribute("Count", count);
    }

}

    除了使用注解配置一个监听器,还可以在web.xml中:

<listener>
     <listener-class>com.ygj.control.onLineCount</listener-class>
</listener>

    监听器优先于过滤器。

    监听器还有很多内容,先不写在这里了。

    AOP

    其实这篇文章主要就是为了说明AOP的使用和原理。

    AOP(面向切面)是Spring两大核心之一,其实也是一种代理(可以参阅设计模式中的代理模式),过滤器与拦截器都是针对一个servlet或是url的,但是AOP是针对具体类的代理,在各个关键点执行对应的方法。

    原理简述与动态代理

    AOP采用JDKProxy和Cglib进行代理,默认使用JDKProxy来进行动态代理,接下来解释一下动态代理。

    AOP的动态代理要区别与静态态代理,静态代理指直接将代理代码织入字节码中。

    代理类实现InvocationHandler,实现invoke(Object proxy, Method method, Object[] args)方法,方法的调用都会被转发到invoke方法

    调用方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)生成代理对象,其中handler指定了代理类,interfaces指定了实现的接口类(即可转化的类),对该对象的所有方法调用都会被转发到invoke方法上(不包括hashCode(),equals()和toString()),

    //TODO

    相关概念

    在AOP中,我们要先了解以下几点概念:

    常用通知分类

    使用AOP

1.使用xml启动AOP功能:

<aop:aspectj-autoproxy />

2.使用注解定义一个切面类

    使用@Aspect来标示一个切面。

@Aspect        //声明切面类
@Service
public class AspectAop {    
    @Pointcut("execution(* com.lili.maven.service.annotationaop.UserService.addUser(..))")  
    //切入点,在其中定义将要拦截的类
    public void pointcut() {
    }

    @Before("pointcut()")    
    public void doBefore() {
        System.out.println("doBefore advice");
    }

    @AfterReturning("pointcut()")    
    public void doAfterReturning() {
        System.out.println("doAfterReturning advice");
    }

    @After("pointcut()")    
    public void doAfter() {
        System.out.println("doAfter advice");
    }

    @AfterThrowing("pointcut()")    
    public void doAfterThrowing() {
        System.out.println("doAfterThrowing advice");
    }

    @Around("pointcut()")    
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("doAround advice start");
        Object result = pjp.proceed();
        System.out.println("doAround advice end");        return result;
    }

}

    在定义通知时,直接指明了切入点方法,也可以按照与切入点相同的方式定义,比如:

@Before("execution (* com.lilin.maven.service.annotationaop.UserService.addUser(..))")

    切入点标志pointcut(),可以更便捷的配置通知。

参考链接:Java监听器Listener使用详解


过滤器、拦截器、监听器和AOP2020-03-01鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论