起因描述
在Springboot项目中,多次看到对controller进行切面AOP时在前置通知方法中通过使用下面的方法拿到Request对象:
//通过使用工具类RequestContextHolder拿到ServletRequestAttributes ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); //通过该实例方法获取到request对象 HttpServletRequest request = requestAttributes.getRequest();
接着通过该request对象获取到请求URL以及远程的ip地址,接着进行一系列的日志记录操作存储到数据库中:
提出疑问:为什么通过该工具类调用getRequest()就能够获取到请求对象?
实际应用(RequestContextHolder获取到request)
引入坐标+启动类
通过使用springboot框架来减少配置文件的编写,即拿即用:
<!-- web启动器:包含sping、springmvc、tomcat以及相关web集成工具、日志等 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.4.5</version> </dependency> <!-- 若想使用AOP,需要引入该jar包进行切面 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.2</version> </dependency>
接着添加一个Springboot启动类即可运行使用了!(内置了tomcat服务器,并且内部自动完成了spring、springmvc的配置)
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SmartparkingApplication { public static void main(String[] args) { SpringApplication.run(com.SmartparkingApplication.class, args); } }
接着我们编写一个控制器HelloController来接收指定请求:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @ClassName HelloController * @Author ChangLu * @Date 2021/4/24 11:13 * @Description TODO */ @Controller public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello"; //跳转到template目录下的hello.html,方便快速测试我们可以不使用thymeleaf以及html文件,能够达到日志切面效果即可 } }
此时我们启动通过启动类即可运行springboot项目了,特别简单!
AOP切面(使用RequestContextHolder)
@Component @Aspect public class LogAspect { //对控制器包下的所有类方法进行切面 @Pointcut("execution(* com.controller.*.*(..))") public void point(){} //前置通知 @Before("point()") public void before(JoinPoint joinPoint){ ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();//通过工具类来获取到 HttpServletRequest request = requestAttributes.getRequest(); System.out.println(request.getSession());//session对象 System.out.println(request.getMethod());//方法名 System.out.println(request.getRequestURI());//请求路径(仅仅是被代理方法上的uri) System.out.println(request.getRequestURL());//完整请求路径 System.out.println(request.getRemoteAddr());//可看做URI。返回发送请求的客户端或最后一个代理的Internet协议(IP)地址。 对于HTTP Servlet,与CGI变量REMOTE_ADDR的值相同 System.out.println(request.getRemoteHost());//可看做URL。返回客户端或发送请求的最后一个代理的标准名称。 如果引擎无法或选择不解析主机名(以提高性能),则此方法返回IP地址的点分字符串形式。 对于HTTP Servlet,与CGI变量REMOTE_HOST的值相同。 System.out.println(request.getRemotePort());//发送客户端的端口号 System.out.println(request.getRemoteUser());//主机用户 System.out.println(request.getContextPath());//上下文路径 } }
启动之后,分别使用浏览器或手机访问:http://localhost:8080/hello 或 http://172.23.153.250:8080/hello,通过request对象获取到了以下信息:
分析
对于通过ServletRequestAttributes调用getRequest()方法实际上获取的是HttpServletRequest接口的实现类:
通过该request能够获取到内容长度、类型、参数属性、协议、服务器名称、服务器端口、ip地址、发送请求的URI及URL、cookies…
认识RequestContextHolder
介绍
RequestContextHolder:持有上下文的Request容器,在该类中持有两个ThreadLocal保存当前线程下的request,其中常用方法如下:
若是想要获取到request、response,需要调用getRequestAttrebutes()获取到RequestAttrebutes,接着通过该对象来获取,如下:
//首先通过RequestContextHolder请求上下文执行者拿到ServletRequestAttributes //你可将ServletRequestAttributes看做是HttpServletRequest的容器,通过该容器存储 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest();//获取请求对象 HttpServletResponse response = requestAttributes.getResponse();//获取相应对象
简述:springmvc在执行service(),doGet(),doPost()这类方法时,其方法内部都有一个预处理方法processRequest(request, response),在预处理方法中有initContextHolders(request, localeContext, requestAttributes)初始化上下文执行者,其中包含了对应的三个参数,即把新的RequestAttributes设置进LocalThread保存,实际上保存的类型为ServletRequestAttributes。
getRequestAttributes()=》ServletRequestAttributes 查看下RequestContextHolder的getRequestAttributes()方法: public abstract class RequestContextHolder { private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); //有两个ThreadLocal存储的就是请求对象 private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<>("Request context"); //调用方法获取到ThreadLocal中的RequestAttributes @Nullable public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } }
ThreadLocal:其解决多线程的数据安全问题,保证在多个线程下一个线程能够关联绑定指定的数据。只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用
通过调用该方法返回获取到一个RequestAttributes该返回值是一个接口,实际返回的是其实现类ServletRequestAttributes
看一下该实现类的方法:
我们可以看到在该实现类中比其接口多出来几个方法(红圈画出来的),这也是为什么我们之前需要进行向下转型的原因!
这样的话我们在进行AOP切面时能够拿到request、response请求响应对象就能够做很多事情了,对一些请求信息进行保存等等的操作…