怎么在Filter中使用HTTPServletRequest

本文探讨了在Java Web开发中从ServletRequest安全地转型为HttpServletRequest的过程,并解释了为何这种转型是安全的,尤其是在涉及接口继承的情况下。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  HttpServletRequest req = (HttpServletRequest) request;
  HttpServletResponse resp = (HttpServletResponse) response;

  }

    因为要用到HttpServletRequest中的方法,我对ServletRequest进行了强转,然后问题解决。可是后来一想不对啊,我们知道我 们对子类实现进行向上转型得到父类对象是安全的,因为子类会完全继承父类的方法,我们向上转型为父类,当我们调用父类的方法其实在子类实现中是能完全找到 的。反之向下转型是不安全的,我们子类除了完全继承父类的方法外还会拓展自己的方法,所以我们在调用子类方法时可能在父类实现中是找不到的,所以向下转型 不安全。

   可是本例中的实现却让我们困惑,我们不仅实现了向下转型,同时还调用了子类拓展的方法,是父类没有的,可是却实现了。这不是和我们所学矛盾吗?后来看了API和一些资料我终于明白了,这和我们所学其实并不冲突!我们先看API:

   public interface HttpServletRequest extends ServletRequest

   终于找到原因了,原来HttpServletRequest和ServletRequest都是接口,他们都只是定义了方法却没有提供相关实现。所以我们看到的ServletRequest request中的request对象其实并不是我们ServletRequest 的一个具体实现。

   这里我们要看我们提出的问题是否安全,其实主要看request 对象的具体实现类究竟是继承的哪个接口,如果继承自HttpServletRequest接口那么我们向下转型使用HttpServletRequest接口的方法就是安全的。测试如下:

    if(request instanceof HttpServletRequest){
        System.out.println("我是右边类的实例");
    }

   输出:我是右边类的实例,证明我们的request对象的确是HttpServletRequest的一个实例。

   疑问解决。


转载于:https://my.oschina.net/u/185335/blog/50559

<think>我们正在讨论在Spring AOP中如何获取HttpServletRequest以及如何获取请求参数的问题。 根据引用[1],在AOP中可以通过RequestContextHolder获取RequestAttributes,进而获取HttpServletRequest。 引用[3]提到需要添加AOP依赖。 引用[5]展示了获取请求参数的方法:Enumeration parameter = request.getParameterNames(); 因此,在AOP中获取HttpServletRequest的步骤如下: 1. 通过RequestContextHolder获取RequestAttributes。 2. 从RequestAttributes中解析出HttpServletRequest对象。 3. 通过HttpServletRequest对象获取参数。 但是注意:在AOP中,我们通常是在方法执行前后进行拦截,所以需要确保当前线程绑定有请求上下文(在Web环境下,Spring MVC已经帮我们做了)。 另外,引用[2]指出,HttpServletRequest可以获取请求行、请求头和请求体,而@RequestBody是用于接收请求体的注解。在AOP中,我们可能更关心请求参数(包括URL参数和表单参数)以及请求头等。 特别提醒:在AOP中,如果我们要读取请求体(即@RequestBody对应的数据),需要注意请求体只能被读取一次,因此可能需要使用ContentCachingRequestWrapper来缓存请求体。 下面我们分两种情况讨论: 情况一:获取URL参数和表单参数(通过request.getParameterMap()或request.getParameterNames()) 情况二:获取请求体(JSON或XML等)数据。 由于在AOP中,我们可能只需要记录一些请求信息,通常不需要解析整个请求体(因为请求体可能很大,而且可能已经被Controller中的@RequestBody参数使用了)。 因此,本回答重点放在如何获取URL参数和表单参数,以及如何安全地获取请求头等信息。 步骤: 1. 添加AOP依赖(如引用[3]所示)。 2. 编写切面,在切面中获取HttpServletRequest。 3. 通过HttpServletRequest获取参数。 示例代码: 首先,确保pom.xml中有AOP依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 然后,编写切面类: ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @Aspect @Component public class RequestAspect { // 定义切点,这里以Controller层为例 @Before("execution(* com.example.demo.controller..*.*(..))") public void before(JoinPoint joinPoint) { // 获取HttpServletRequest HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); // 1. 获取请求URL和请求方法 String requestUrl = request.getRequestURL().toString(); String method = request.getMethod(); // 2. 获取所有请求头 Map<String, String> headers = new HashMap<>(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); headers.put(headerName, request.getHeader(headerName)); } // 3. 获取请求参数(包括URL参数和表单参数) Map<String, String[]> parameterMap = request.getParameterMap(); // 如果需要转换成Map<String, String>,注意一个参数名可能对应多个值,这里只取第一个值 Map<String, String> params = new HashMap<>(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String[] values = entry.getValue(); if (values != null && values.length > 0) { params.put(entry.getKey(), values[0]); // 这里简化处理,实际可能需要考虑多值情况 } } // 4. 获取请求体? 注意:这里不能直接读取,因为流只能读一次。如果确实需要,可以在过滤器中缓存请求体,然后在AOP中读取缓存的内容。 // 但是,在AOP中通常不建议读取请求体,因为可能影响性能,而且请求体可能已经被读取过了。 // 打印日志或进行其他操作 System.out.println("请求URL: " + requestUrl); System.out.println("请求方法: " + method); System.out.println("请求头: " + headers); System.out.println("请求参数: " + params); } } ``` 关于请求体:如果确实需要在AOP中获取请求体,可以在过滤器中使用ContentCachingRequestWrapper对请求进行包装,然后在AOP中通过该包装对象获取缓存的请求体内容。但是,这需要额外的配置。 配置过滤器的示例: 首先,创建一个过滤器: ```java import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Component public class RequestBodyCachingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // 使用ContentCachingRequestWrapper包装原始请求 ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(httpServletRequest); chain.doFilter(wrappedRequest, response); } } ``` 然后,在AOP中,我们可以这样获取请求体: ```java // 在切面中 HttpServletRequest request = ...; // 获取请求 if (request instanceof ContentCachingRequestWrapper) { ContentCachingRequestWrapper wrappedRequest = (ContentCachingRequestWrapper) request; byte[] bodyBytes = wrappedRequest.getContentAsByteArray(); String body = new String(bodyBytes, wrappedRequest.getCharacterEncoding()); // 解析JSON等操作 } ``` 但是请注意,使用ContentCachingRequestWrapper会有一定的性能开销,因为它会将整个请求体缓存到内存中。 总结: 在AOP中获取请求参数: - 对于URL参数和表单参数,直接使用request.getParameterMap()即可。 - 对于请求体,需要配合过滤器使用ContentCachingRequestWrapper,并且要谨慎使用。 另外,在引用[1]中,还展示了如何从请求头中获取token并解析用户信息。在AOP中,我们也可以类似地获取请求头中的信息。 最后,注意在AOP中获取HttpServletRequest的方式是通过RequestContextHolder,这要求当前线程是Web请求的线程(在Spring MVC中,请求线程已经绑定了RequestAttributes)。 相关问题: 1. 在AOP中获取HttpServletResponse的方法? 2. 如何避免在AOP中多次读取请求体导致的问题? 3. 在Spring Boot中如何配置全局过滤器来缓存请求体? 4. 在AOP中获取的请求参数如何传递给切面方法? 5. 如何通过AOP实现请求参数的统一校验? 请根据上述分析和代码示例,整理回答。 </think>在 Spring AOP 中通过 `HttpServletRequest` 获取请求参数,需结合 `RequestContextHolder` 获取请求上下文来实现。以下是具体方法和完整实现流程: --- ### 一、核心实现步骤 #### 1. 添加必要依赖 ```xml <!-- pom.xml --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> ``` #### 2. 在切面类中获取请求参数 ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @Aspect @Component public class RequestAspect { @Before("execution(* com.example.controller.*.*(..))") // 切点表达式 public void logRequestParameters(JoinPoint joinPoint) { // 1. 获取HttpServletRequest HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder .currentRequestAttributes()) .getRequest(); // 2. 获取URL参数和表单参数(GET/POST) Map<String, String> params = new HashMap<>(); Enumeration<String> paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { String name = paramNames.nextElement(); params.put(name, request.getParameter(name)); // 取第一个值 } // 3. 获取请求头 String token = request.getHeader("Authorization"); // 4. 使用参数(示例:打印日志) System.out.println("URL参数: " + params); System.out.println("Token: " + token); } } ``` --- ### 二、关键代码解析 #### 1. 获取 `HttpServletRequest` ```java HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder .currentRequestAttributes()) .getRequest(); ``` - 通过 `RequestContextHolder` 获取当前请求的上下文[^1] - 适用于所有 Web 请求线程(Spring MVC 自动绑定) #### 2. 获取请求参数类型 | **参数类型** | **获取方式** | **返回值格式** | |--------------------|---------------------------------------|--------------------------| | URL参数/表单参数 | `request.getParameterMap()` | `Map<String, String[]>` | | 单个参数值 | `request.getParameter("key")` | `String` | | 所有参数名 | `request.getParameterNames()` | `Enumeration<String>` | | 请求头 | `request.getHeader("headerName")` | `String` | #### 3. 处理请求体(如 JSON) 需配合过滤器缓存请求体(避免流只能读一次的问题): ```java // 1. 注册过滤器 @Bean public FilterRegistrationBean<ContentCachingRequestWrapper> contentCachingFilter() { FilterRegistrationBean<ContentCachingRequestWrapper> filter = new FilterRegistrationBean<>(); filter.setFilter(new ContentCachingRequestWrapperFilter()); filter.addUrlPatterns("/*"); return filter; } // 2. 在切面中读取缓存 if (request instanceof ContentCachingRequestWrapper) { ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request; byte[] body = wrapper.getContentAsByteArray(); String jsonBody = new String(body, StandardCharsets.UTF_8); // 解析JSON到Map Map<String, Object> bodyMap = new ObjectMapper().readValue(jsonBody, Map.class); } ``` --- ### 三、注意事项 1. **作用域限制** 仅适用于 Web 请求线程,异步任务中需手动传递 `RequestAttributes`[^1] 2. **请求体处理** - 直接读 `request.getInputStream()` 会导致流关闭 - 必须通过 `ContentCachingRequestWrapper` 缓存请求体[^2] 3. **参数类型差异** | **来源** | **GET请求** | **POST表单** | **POST-JSON** | |-------------|------------|-------------|--------------| | 获取方式 | `getParameter` | `getParameter` | 需解析请求体 | | 多值参数 | `request.getParameterValues()` 返回数组 | ✅ | ✅ | 4. **性能优化** 大文件上传场景避免缓存请求体,改用 `MultipartHttpServletRequest` --- ### 四、典型应用场景 ```java // 示例:记录所有Controller请求参数 @Before("@within(org.springframework.web.bind.annotation.RestController)") public void logAllControllers(JoinPoint joinPoint) { HttpServletRequest request = ... // 获取请求 Map<String, String> params = ... // 提取参数 // 记录到日志系统 log.info("请求 {} 参数: {}", request.getRequestURI(), params ); } ``` --- ### 相关问题 1. 如何在异步任务中传递 `HttpServletRequest` 上下文?[^1] 2. 如何通过 AOP 实现请求参数的自动验签? 3. `ContentCachingRequestWrapper` 缓存请求体的原理是什么?[^2] 4. 如何获取被 `@RequestBody` 注解修饰的参数? 5. 在过滤器和 AOP 中处理请求体的区别是什么? [^1]: 通过 `RequestContextHolder` 可在 AOP 中安全获取请求对象,其底层使用 `ThreadLocal` 存储上下文。 [^2]: `ContentCachingRequestWrapper` 通过缓存输入流实现请求体重复读取,适用于 JSON 解析等场景。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值