拦截器与过滤器的区别
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 应用程序中定义的错误页面,则应改用 |
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;
//...