拦截器与过滤器的区别

spring的拦截器与servlet的filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等,不同的是:

  • 适用范围不同:filter是servlet规范规定的,只能用于web程序中;而拦截器既可用于web程序,也可用于application、swing程序中。
  • 规范不同:filter是servlet规范定义的,是servlet容器支持的,而拦截器是spring容器中的,是spring框架支持的。
  • 使用资源不同:同其他代码块一样,拦截器也是一个spring组件,归spring管理,配置在spring文件中,因此能使用spring里的任何资源、对象,例如service对象、数据源事务管理等,通过IOC注入到拦截器即可。而filter则不能。
  • 深度不同:Filter只能在servlet前后起作用。而拦截器能够深入到方法前后,异常抛出前后等,因此拦截器具有更大的弹性,在spring框架的程序中,优先使用拦截器。

最简单明了的区别就是

  • 过滤器可以修改request,而拦截器不能
  • 过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境
  • 拦截器可以调用IOC容器中的各种依赖,而过滤器不能
  • 过滤器只能在请求的前后使用,而拦截器可以详细到每个方法

拦截器的使用

拦截器(Interceptor)和过滤器不同(filter),但是也可以实现方法前中后的处理策略,一般情况是进入方法前进行拦截,然后进行身份验证。下面是实现进入方法前的身份,这里是使用groovy写的,与java略有差异
1、实现拦截器

@Component
@Slf4j
class SecurityInterceptor implements HandlerInterceptor {

    String account_interface_url

    @Value('${account_interface_url}')
    void setAccount_interface_url(String account_interface_url) {
        this.account_interface_url = account_interface_url
    }
    //Controller逻辑执行之前
    @Override
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        def name,ticket
        response.setCharacterEncoding("UTF-8")
        if(!request.getHeader("ticket")){
            JSONObject json = new JSONObject()
            json.put("code","1")
            json.put("msg","认证失败,无访问权限")
            response.getWriter().write(json.toString())
            return false
        }
        ticket = request.getHeader("ticket").toString()
        if(handler instanceof HandlerMethod){
            name = handler.getMethod().getName().toString()
        }
        def url = String.format(account_interface_url,ticket,"test111")
        println name
        //这里是远程调用权限接口
        def account = Http.http_request(url)
        println account
        JSONObject account_result = new JSONObject(account)
        if(account_result.has("status")&& account_result.getString("status") == "NOT_FOUND"){
            JSONObject json = new JSONObject()
            json.put("code","1")
            json.put("msg","认证失败,无访问权限")
            response.getWriter().write(json.toString())
            return false
        }
        return true
    }
    //Controller逻辑执行完毕但是视图解析器还未进行解析之前
    @Override
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {

    }

    //Controller逻辑和视图解析器执行完毕
    @Override
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {

    }
}

2、注册拦截器
实现拦截器后需要将拦截器注册到spring容器中,可以通过implements WebMvcConfigurer覆盖addInterceptor方法,把Bean注册到Spring容器中,可以选择@Component 或者 @Configuration。

@Configuration
class SecurityConfigurer implements WebMvcConfigurer{

    @Bean
    SecurityInterceptor securityInterceptor(){
        return new SecurityInterceptor()
    }
    @Override
    void addInterceptors(InterceptorRegistry registry) {
        registry
                .addInterceptor(securityInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/*.html")
    }
}

相信很多人对preHandle方法中的HttpServletReques, HttpServletResponse , handler这三个类的功能比较模糊,所以在这里进行简单的介绍。
3、HttpServletRequest
web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象,和代表响应的response对象。他们分别代表着请求和响应,如果需要客户端提交过来的数据,只需要request对象就可以,如果要向客户端发送信息,就需要response对象。
HttpServletRequest代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。
获得客户端信息:

方法

说明

getRequestURL()

返回客户端发出请求时的完整URL。

getRequestURI()

返回请求行中的参数部分。

getQueryString ()

方法返回请求行中的参数部分(参数名+值)

getRemoteHost()

返回发出请求的客户机的完整主机名。

getRemoteAddr()

返回发出请求的客户机的IP地址。

getPathInfo()

返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以"/"开头。

getRemotePort()

返回客户机所使用的网络端口号。

getLocalAddr()

返回WEB服务器的IP地址。

getLocalName()

返回WEB服务器的主机名。

获取客户端请求头

方法

说明

getHeader(string name)

以 String 的形式返回指定请求头的值。如果该请求不包含指定名称的头,则此方法返回 null。如果有多个具有相同名称的头,则此方法返回请求中的第一个头。头名称是不区分大小写的。可以将此方法与任何请求头一起使用

getHeaders(String name)

以 String 对象的 Enumeration 的形式返回指定请求头的所有值

getHeaderNames()

返回此请求包含的所有头名称的枚举。如果该请求没有头,则此方法返回一个空枚举

获得客户机请求参数

方法

说明

getParameter(String name)

根据name获取请求参数(常用)

getParameterValues(String name)

根据name获取请求参数列表(常用)

getParameterMap()

返回的是一个Map类型的值,该返回值记录着前端(如jsp页面)所提交请求中的请求参数和请求参数值的映射关系。(编写框架时常用)

4、HttpServletResponse
HttpServletResponse对象代表服务器的响应。这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。查看HttpServletResponse的API,可以看到这些相关的方法。
response对象的功能分为以下四种:

  • 设置响应头信息
  • 发送状态码
  • 设置响应正文
  • 重定向
    4.1 设置响应头信息

方法

说明

addHeader(String name, String value)

添加具有给定名称和值的响应标头。此方法允许响应标头具有多个值。

addDateHeader(String name, long date)

添加具有给定名称和日期值的响应标头。 日期以自纪元以来的毫秒数指定。 此方法允许响应标头具有多个值

addIntHeader(String name, int value)

添加具有给定名称和整数值的响应标头。 此方法允许响应标头具有多个值

containsHeader(String name)

返回一个布尔值,指示是否已设置命名的响应标头

setHeader

同add方法

setDateHeader

同add方法

setIntHeader

同add方法

4.2 设置状态码

方法

说明

setStatus(int sc)

设置此响应的状态代码。 此方法用于设置没有错误时的返回状态码(例如,对于状态码 SC_OK 或 SC_MOVED_TEMPORARILY)。 如果出现错误,并且调用者希望调用 Web 应用程序中定义的错误页面,则应改用 sendError 方法

setStatus(int sc, String sm)

设置此响应的状态代码和消息

常用的状态码

Name

discribtion

释义

200

SC_OK

此次请求已经成功

301

SC_MOVED_PERMANENTLY

请求的网页已永久移动到新位置

302

SC_MOVED_TEMPORARILY

临时移动、请求地址不变

401

SC_UNAUTHORIZED

未授权、用户需登录

403

SC_FORBIDDEN

服务器拒绝了此次请求(权限问题)

404

SC_NOT_FOUND

服务器没找到URI匹配的

405

SC_METHOD_NOT_ALLOWED

调用的方法不允许使用(get、post不匹配)

500

SC_INTERNAL_SERVER_ERROR

服务器内部发生异常,请求中断

502

SC_BAD_GATEWAY

网关错误(如Nginx),无法收到服务器的响应

504

SC_GATEWAY_TIMEOUT

请求超时,在约定时间内没有收到Http响应

4.3 发送响应正文
4.3.1 使用OutputStream流输出中文

String data = "中国";
        OutputStream outputStream = response.getOutputStream();//获取OutputStream输出流
        response.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        /**
         * data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,
         * 如果是中文的操作系统环境,默认就是查找查GB2312的码表,
         * 将字符转换成字节数组的过程就是将中文字符转换成GB2312的码表上对应的数字
         * 比如: "中"在GB2312的码表上对应的数字是98
         *         "国"在GB2312的码表上对应的数字是99
         */
         /**
          * getBytes()方法如果不带参数,那么就会根据操作系统的语言环境来选择转换码表,如果是中文操作系统,那么就使用GB2312的码表
          */
         byte[] dataByteArr = data.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换
         outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组

4.3.2 使用PrintWriter流向客户端浏览器输出中文数据

//将字符以"UTF-8"编码输出到客户端浏览器
response.setCharacterEncoding("UTF-8")
//获取输出流,写入数据
response.getWriter().write("hello world")

4.4 重定向
重定向的方法

response.setHeader("Location", "http://www.itcast.cn");
response.sendRedirect("http://www.itcast.cn");

5、HandlerMethod
Spring MVC应用启动时会搜集并分析每个Web控制器方法,从中提取对应的"<请求匹配条件,控制器方法>“映射关系,形成一个映射关系表保存在一个RequestMappingHandlerMapping bean中。然后在客户请求到达时,再使用RequestMappingHandlerMapping中的该映射关系表找到相应的控制器方法去处理该请求。在RequestMappingHandlerMapping中保存的每个”<请求匹配条件,控制器方法>"映射关系对儿中,"请求匹配条件"通过RequestMappingInfo包装和表示,而"控制器方法"则通过HandlerMethod来包装和表示。
一个HandlerMethod对象,可以认为是对如下信息的一个包装 :

信息名称

介绍

Object bean

Web控制器方法所在的Web控制器bean。可以是字符串,代表bean的名称;也可以是bean实例对象本身

Class beanType

Web控制器方法所在的Web控制器bean的类型,如果该bean被代理,这里记录的是被代理的用户类信息

Method method

Web控制器方法

Method bridgedMethod

被桥接的Web控制器方法

MethodParameter[] parameters

Web控制器方法的参数信息:所在类所在方法,参数,索引,参数类型

HttpStatus responseStatus

注解@ResponseStatus的code属性

String responseStatusReason

注解@ResponseStatus的reason属性

下面,是HandlerMethodn属性字段的源码

public class HandlerMethod {
 
    // 虽然Object类型,但是注册handlerMethod时候构造的时候有可能传入的是一个String类型的bean name
	private final Object bean;
 
    // 见名知义,我调试的时候,传入的是DefaultListableBeanFactory,如果bean属性是Sring的beanName就可以用beanName获取到对应的bean作用Handler
	private final BeanFactory beanFactory;
 
    // 方法所属类
	private final Class<?> beanType;
 
    // 注册的方法
	private final Method method;
 
    // 被桥接的方法,如果method是原生的,这个属性的值就是method
	private final Method bridgedMethod;
 
    // 封装方法参数的类实例,一个MethodParameter就是一个参数
	private final MethodParameter[] parameters;
 
    // Http状态码
	private HttpStatus responseStatus;
 
    // ResponseStatus注解的reason值
	private String responseStatusReason;
 
	private HandlerMethod resolvedFromHandlerMethod;
 
    //...