过滤器,拦截器(包括aop),监听器

  • 过滤器:对web服务器管理所有的web资源,主要是对用户的一些请求进行一些预处理,并在服务器响应后再进行预处理,返回给用户,例如jsp,静态图片等,用途:1、实现URL级别的访问控制,2、过滤敏感词汇,3、某些信息用*隐藏。

  • 拦截器:基本上都是AOP,当某个方法或字段被访问时进行拦截,在之前 和 在之后 加入某些操作,用途:权限验证,判断用户是否登录,或者再做某一操作时要确定满足某一定的条件

  • 监听器:用于监听servletContext、HttpSession和servletRequest等域对象的创建和销毁事件,可以在这些事件发生前和发生后进行处理。用途:1、用于统计在线人数和在线用户,2、系统启动时加载初始化信息,3、统计网站访问量,4、记录用户访问路径

执行顺序
过滤器 -> 拦截器 -> 监听器
.程序执行的顺序是先进过滤器,再进拦截器,最后进切面。注意:如果拦截器中preHandle方法返回的为false时,则无法进入切面

一、过滤器

定义一个类,实现Filter接口,重写方法;再在这个类上面添加注解@WebFilter(filtername = 过滤器名字,urlpatterns=拦截路径)

Filter,必须要有名字,所有的过滤器执行顺序是根据Filter的名字字母顺序来执行的
注意:需要在springboot的运行类增加@ServletComponentScan注解,添加上Filter的路径
如果不加@ServletComponentScan就算这个类和运行类平级或者子级也不会被扫描上

package com.ssm.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter(filterName = "myFilter", urlPatterns = "/*") //一个是filter的名字,一个是url为什么时用此过滤器,其他的看源码
public class MyFilter implements Filter{
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("这是我的Filter");
		//放行
		chain.doFilter(request, response);
		
	}
	
} 
	//下面两个如果没有可以自己添加上,看源码,有这两个,只是不是必须的
    @Override
    public void destroy() {

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }


@SpringBootApplication
@ServletComponentScan("com.ssm.filter")//这个是扫描包的路径
public class SpringBoot1Application {

	public static void main(String[] args) {
		SpringApplication.run(SpringBoot1Application.class, args);
	}

}

二、拦截器(基于AOP)

实现拦截器有两种方法 AOP和implements HandlerInterceptor
后面那种和上面的过滤器用法一样

  • 拦截器针对的是 Servlet 容器,而 AOP 针对的是 IOC 容器。
  • 拦截器的粒度太粗,拦截器是根据路由,也就是 RequestMapping 来决定,只能拦截 controller 层,不能处理主要的业务逻辑处理层 service 和 dao 层.
  • 使用拦截器做处理时,如果需要做多个处理就必须定义多个拦截器,且处理较为笨重,不仅要考虑在方法执行前或还是在方法执行后处理,还需要考虑如果出现异常的情况的处理。对于配置和维护来说就是灾难。

2.1aop

AOP 底层基于 JDK 动态代理和 CGLIB 动态代理结合实现,但仅仅使用动态代理就会面临如下问题:

  1. 精细的控制必须自己编写。比如条件判断,要进哪一些方法,不进哪一些方法,这些都需要自己控制。
  2. 如果有多个代理对象,代理类的创建也需要自己定义。比如切面,连接点,切点,织入,通知,增强都需要自己封装。多个代理对象的执行顺序也需要自己控制。
  3. 无论是使用 JDK 动态代理还是 CGLIB动态代理,它们的目标都是将目标对象转换为代理对象。只有代理对象执行方法,才会进到代理类,也就是切面去做逻辑增强和处理。

AOP 必须构建在 IOC 的基础之上,没有 IOC 也就没有 AOP 的出现。
AOP 的核心和归宿就是将目标对象(IOC 容器对象)转换成代理对象,然后进入到代理类做逻辑增强和处理后再回归去执行业务逻辑方法。

2.1.1 Aop 的核心概念

1:Aspect:切面是切点和通知的聚合,定义了在哪一个切点做什么通知。

2:通知(advice):定义了在收作业前后需要做的事。常见的通知类型有:before、after、after-returning、around、after-throwing 等。

3:连接点(joinpoint):连接点指程序运行时允许插入切面的一个点,可以是一个函数、一个包路径、一个类、或者抛出的异常。有点类似于可以收作业的时间点。(执行的具体的目标对象的函数)

4:切点(pointcut):用于定义切面的位置,也就是捕获哪些连接点的调用然后执行”通知”的操作(什么地点)。

Spring 框架做了的事:

5:引入(introduction):引入允许我们向现有的类添加新方法或属性。

6:织入(weave):是把切面应用到切点对应的连接点的过程。切面在指定连接点被织入到目标对象中。

7:目标对象:指被切面织入的对象。就是 SpringIOC 管理的对象。

2.1.2Aop 的通知类型

1:@Before:在业务模块代码执行之前执行,其不能阻止业务:模块的执行,除非抛出异常。

2:@AfterReturning:在业务模块代码执行之后执行。

3:@AfterThrowing:在业务模块抛出指定异常后执行。

4:@After:在所有的 Advice 执行完成后执行,无论业务模块是否抛出异常,类似 finally 的作用。

5:@Around:用于编写包裹业务模块执行的代码,其可以传入一个 ProceedingJoinPoint 用于调用业务模块的代码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至可以根据一定的条件阻断业务模块的调用。

2.1.3 两种方式来实现aop

先添加依赖
二选一


<!--  原始的aop依赖-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>


<!-- springboot的AOP依赖 -->
      	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
   </dependency>

2.1.3.1第一种:基于切点表达式进行拦截

切入点的定义:https://blog.csdn.net/guo_guo_cai/article/details/78332099
在这里插入图片描述

这儿,我们就以拦截控制器的所有方法,来做日志管理

package com.ssm.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * @author 作者
 * @data 2019年8月2日 
 */
@Aspect   //这两个注解必须有
@Component 
public class MyAspect{
	private final static Logger log = LoggerFactory.getLogger(MyAspect.class);
	
	//定义切点位置:下面如果你在SSM中用AOP,在xml中配的就是下面
	@Pointcut("execution(* com.ssm.controller..*.*(..))")
	public void performance() {
	}
	
	/**
	 * 环绕通知记录时间
	 * 
	 * @param joinPoint
	 * @return
	 * @throws Throwable
	 */
	@Around("performance()")
	public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
	
	    // 记录起始时间
	    long begin = System.currentTimeMillis();
	    Object result = "";
	    /** 执行目标方法 */
	    try {
	        result = joinPoint.proceed();
	    } catch (Exception e) {
	        log.error("日志记录发生错误, errorMessage: {}", e.getMessage());
	    } finally {
	        /** 记录操作时间 */
	        long took = (System.currentTimeMillis() - begin) / 1000;
	        log.info("controller执行时间为: {}秒", took);
	    }
	    System.out.println("66666666");
	    return result;
	}
	
	/**
	 * 前置通知
	 * 
	 * @param joinPoint
	 * @throws Throwable
	 */
	@Before("performance()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
	    // 接收到请求,记录请求内容
	    log.info("doBefore");
	    System.out.println("1111111");
	}
	
	}

2.1.3.2 第二种:基于注解的

自定义一个注解

package com.ssm.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 作者
 * @data 2019年8月2日 
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
	String operateDescription();// 记录日志的操作类型,不写默认值就是一个必须填的注解
}

可以定义多个注解,但是切入面只能有一个,可以有很多个不同切入点的环绕通知

package com.ssm.aop;

import java.lang.reflect.Method;
import java.sql.SQLException;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.ssm.annotation.MyAnnotation;

/**
 * @author 作者
 * @data 2019年8月2日 
 */
@Aspect  
@Component 
public class MyAspect2 {
	 private final static Logger log = LoggerFactory.getLogger(MyAspect2.class);

	    /**
	     * 环绕通知处理
	     *
	     * @param pjp
	     * @return
	     * @throws Throwable
	     */
	    @Around("@annotation(com.ssm.annotation.MyAnnotation)")
	    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
	        // 1.方法执行前的处理,相当于前置通知
	        // 获取方法签名
	        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
	        // 获取方法
	        Method method = methodSignature.getMethod();
	        // 获取方法上面的注解
	        MyAnnotation myAnno = method.getAnnotation(MyAnnotation.class);
	        // 获取到类名
	        String targetName = pjp.getTarget().getClass().getName();
	        // 获取到方法名字
	        String methodName = method.getName();
	        // 获取操作描述的属性值
	        String operateDescription = myAnno.operateDescription();
	        Object result = null;
	        try {
	            // 让代理方法执行
	            result = pjp.proceed();
	            // 2.相当于后置通知(方法成功执行之后走这里)
	        } catch (SQLException e) {
	            // 3.相当于异常通知部分
	        } finally {
	            // 4.相当于最终通知
	            log.info("class->{},methodName->{},operateDescription->{}", targetName, methodName, operateDescription);
	        }
	        System.out.println("基于手写注解的AOP拦截功能实现");
	        return result;
	    }
}

在这里插入图片描述

2.2 拦截器

(springboot-实现HandlerInterceptor接口的)

第一步:定义一个类实现拦截器接口,重写里面的方法

public class TicketsInterceptor implements HandlerInterceptor {
   @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("===执行了拦截器");
        crossDomain(request,response); // 设置请求头,解决跨域问题
        String word = "测试";
        String method = request.getMethod();
        if ("GET".equals(method)) {
            word = request.getParameter("word");
        }
        if ("POST".equals(method)) {
            String s = StreamUtils.copyToString(request.getInputStream(), Charset.defaultCharset());
            JSONObject jsonObject = JSONObject.fromObject(s);
            if (jsonObject.containsKey("word")) {
                word = jsonObject.getString("word");
            }
        }
        if (word == null || word.length() < 2) {
            response.setStatus(500, "word最少为2位");
            return false;
        }
        return true;
    }

    public void crossDomain(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }
}

第二步:定义一个类实现WebMvcConfigurer接口,重写方法,配置拦截器的配置(拦截的url,跨域等)

@Configuration//标识这是一个配置类
public class InterceptorConfiguration implements WebMvcConfigurer {

    // 拦截路径
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //注册Interceptor拦截器(Interceptor这个类是我们自己写的拦截器类)
        InterceptorRegistration registration = registry.addInterceptor(new TicketsInterceptor());
        //addPathPatterns()方法添加需要拦截的路径
        registration.addPathPatterns("/openapi/**");                      //所有路径都被拦截
        //excludePathPatterns()方法添加不拦截的路径
        registration.excludePathPatterns(                         //添加不拦截路径
                "/demo/loginPage",            //登录页面的地址【不拦截】
                "/**/*.html",            //html静态资源
                "/**/*.js",              //js静态资源
                "/**/*.css"              //css静态资源
        );
    }

    // 跨域
    /*@Override
    public void addCorsMappings(CorsRegistry registry) {

    }*/
}

这个时候假如发现拦截器没有起作用,那可能是因为:有配置类继承了WebMvcConfigurationSupport(比如使用swagger的时候配置了),查询WebMvcConfigurationSupport源码发现其中有拦截器注册方法addInterceptors(InterceptorRegistry registry),所以在版本控制配置类中重写此方法添加拦截器,拦截器生效,问题解决。代码如下:

@Configuration
public class ApiConfig extends WebMvcConfigurationSupport {
	
	// 你自定义的拦截器类
	@Autowired
	private RequestParamInfoIntorceptor requestParamInfoIntorceptor;
	
	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(this.requestParamInfoIntorceptor).addPathPatterns("/**").excludePathPatterns("/luser/login", "/luser/register", "/send/message");
		super.addInterceptors(registry);
	}
	
}

post请求的问题

注意对于post请求是会有问题的,用流只能读取一次参数,因为可以把流比喻成水,request里面的inputStream就好比杯子中的水。试问杯子中的水倒掉之后还能继续倒吗?当然不能滴!InputStream里面有做指针和同步处理,一旦指针到了末尾是不会回来的。那么我们怎么拷贝request body里面的数据呢,当然我们得找一种可以复制的存储方式了,比如String,可以先把request 的inputStream转成String,然后又把String转成byte[] 存回去就是了,String对象我们可以无限使用。

解决方法就是写一个request继承HttpServletRequestWrapper,相当于重写了一个HttpServletRequest,然后再写一个过滤器。
具体查看这篇文章:
https://www.kuangstudy.com/bbs/1453201557431640066

三、 监听器

监听器是servlet规范中定义的一种特殊类,用于监听ServletContext(application)、HttpSession(session)和ServletRequest(request)等域对象的创建与销毁事件,以及监听这些与对象属性发生修改的事件。可以在事件发生之前,之后做一些处理。

根据监听器的对象划分

  • 用于监听application对象,实现ServletContextListener接口
  • 用于监听session对象,实现HttpSessionListener接口
  • 用于监听request对象,实现HttpSessionListener接口

监听器能干什么

  1. 统计在线人数和在线用户
  2. 系统启动时加载初始化信息
  3. 统计网站访问量
  4. 与spring结合

自定义一个监听器类,实现HttpSessionListener接口/ServletContextListener /看你要监听的对象是谁,重写方法。
启动类上加@ServletComponentScan(“com.ssm.listener”) //需要扫描包

package com.ssm.listener;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * @author 作者
 * @data 2019年8月2日 
 */
@WebListener()
public class MyListener implements HttpSessionListener{

	 public static int online = 0;

	    @Override
	    public void sessionCreated(HttpSessionEvent se) {
	        System.out.println("创建session,在线用户数:" + (++online));
	    }

	    @Override
	    public void sessionDestroyed(HttpSessionEvent se) {
	        System.out.println("销毁session,在线用户数:" + (--online));
	        online--;
	    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
拦截过滤器监听器是Java中常用的三种应用组件,它们在不同的场景下有不同的作用。 1. 拦截(Interceptor): 拦截是一种在方法调用前后、异常抛出前后等特定点进行拦截的组件。它可以用于实现日志记录、权限验证、性能监控等功能。在Java中,拦截常用于框架(如Spring)中,通过AOP(面向切面编程)的方式来实现。拦截可以对方法进行拦截,并在方法执行前后进行一些处理。 2. 过滤器(Filter): 过滤器是一种用于在请求到达目标资源之前或之后进行预处理或后处理的组件。它可以用于实现请求参数处理、字符编码转换、请求拦截等功能。在Java Web开发中,过滤器常用于对请求进行预处理,如验证用户身份、设置字符编码等。过滤器可以对请求进行拦截,并在请求到达目标资源之前或之后进行一些处理。 3. 监听器(Listener): 监听器是一种用于监听特定事件并采取相应行动的组件。它可以用于实现应用程序的启动和关闭监听、会话创建和销毁监听、属性变化监听等功能。在Java中,监听器常用于Web应用程序中,通过监听特定事件来执行相应的逻辑。监听器可以监听特定事件的发生,并在事件发生时执行一些处理。 以下是一个简单的示例,展示了拦截过滤器监听器的应用: 1. 拦截示例: ```java public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 在方法调用前执行的逻辑 // ... 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. 过滤器示例: ```java public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // 过滤器初始化逻辑 // ... } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 过滤器处理逻辑 // ... chain.doFilter(request, response); } @Override public void destroy() { // 过滤器销毁逻辑 // ... } } ``` 3. 监听器示例: ```java public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // 应用程序启动时执行的逻辑 // ... } @Override public void contextDestroyed(ServletContextEvent sce) { // 应用程序关闭时执行的逻辑 // ... } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LC超人在良家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值