基础普及
springframework做Java开发的同学应该都比较熟悉, 它的两大特性在面试的时候也是多次提到的一个知识点,但是对于AOP的实践,除非是在一些覆盖业务场景比较大的系统平台中会使用,大部分的情况还真的只是停留在知识点的阶段。
我之前使用过的切面变成的场景有:
- 多语言,网站收银台根据当前的所处的国际地区,加载不同的网页显示语言。
- 日志监控,这个比较常用,如要用来监控统一的日志配置。
- 方法耗时统计,系统方法进行执行优化的时候,进行的方法体监控
- 前端非法请求拦截,业务系统在提供服务的时候,可能会使用IP,HttpRequest中其他的参数进行黑白名单的控制。
以上的这些事我使用切面变成中的场景,其实的场景还是比较多的,毕竟是Spring的特性之一。
实现原理
spring AOP的实现原理是动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法动态代理两种方式,JDK动态代理和CGLIB动态代理:
- JDK :JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
- CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。(这里多说一句,AOP类是要检测类的子类哦)
HTTP拦截器使用
目前的应用中使用RESTFUL的请求较多,RESTful请求的写法大家风格迥异,主要的方式有:
- 实现HandlerInterceptor 接口 :
public class LoginInterceptor extends HandlerInterceptorAdapter {
/**
*预处理回调方法,实现处理器的预处理(如登录检查)。
*第三个参数为响应的处理器,即controller。
*返回true,表示继续流程,调用下一个拦截器或者处理器。
*返回false,表示流程中断,通过response产生响应。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("-------------------preHandle");
// 验证用户是否登陆
Object obj = request.getSession().getAttribute("username");
if (obj == null || !(obj instanceof String)) {
response.sendRedirect(request.getContextPath() + "/index.html");
return false;
}
return true;
}
/**
*当前请求进行处理之后,也就是Controller 方法调用之后执行,
*但是它会在DispatcherServlet 进行视图返回渲染之前被调用。
*此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("-------------------postHandle");
}
/**
*方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。
*这个方法的主要作用是用于进行资源清理工作的。
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("-------------------afterCompletion");
}
- 继承 HandlerInterceptorAdapter 类
然后配置web.xml,进行用户配置实现的加载:
<servlet>
<servlet-name>onlineServiceServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>onlineServiceServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
从上边可以看出来,拦截器的配置是在servlet-context.xml中,看下它里面配置了什么
<mvc:interceptors>
<!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 -->
<!--<bean class=""></bean>-->
<mvc:interceptor>
<!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->
<mvc:mapping path="/**" />
<bean class="com.didi.global.risk.asset.sill.RequestInterceptor"/> //这里就是用户实现的 inteceptor
</mvc:interceptor>
<!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
</mvc:interceptors>
以上就拦截器的实现,看到可以根据需要拦截的路径进行全路径或者局部路径的业务拦截实现。我在之前的使用过程中,发现了一个问题,那就是拦截器不生效!后来经过排查,返现需要去掉<mvc:annotation-driven />
这个spring标签,那它是做什么的呢? 标签会自动为我们注册DefaultAnnotationHandlerMapping
与AnnotationMethodHandlerAdapter
这两个bean。这两个bean中有一个interceptors的属性会注入不进去,因为默认注入的原因。
HTTPRequest的获取
刚才上边的拦截器,可进行全拦截和部分拦截,现在下面要说的是业务在不使用inteceptor的情况下,业务层进行 request的处理
request 的获取方式
方法体中传入了HttpServletRequest ,直接使用request
public class TestController {
@RequestMapping("/test")
public void test(HttpServletRequest request) throws InterruptedException {
// 模拟程序执行了一段时间
Thread.sleep(1000);
}
方法体中没有传入,使用
HttpServletRequest request = ((ServletRequestAttributes(RequestContextHolder.currentRequestAttributes())).getRequest();
使用自动注入
`
public class TestController{
@Autowired
private HttpServletRequest request; //自动注入request
@RequestMapping("/test")
public void test() throws InterruptedException{
//模拟程序执行了一段时间
Thread.sleep(1000);
}
`