老生常谈你真的理解过滤器拦截器ControllerAdvic
面试官:说说过滤器和拦截器的区别?这个问题面试题库算是比较经典的,这两个我相信很多同学在工作中都有接触过,但如果没有经过系统的整理,还真的不好说出个123来,那老湿机在此这面就把它俩和常用的AOP、ControllerAdvice放一起,带你做一个比较全面的认识。1。了解4种拦截方法的执行顺序
先上一个栗子,看四种拦截方法并驾齐驱使用时,谁先谁后:Filter过滤器Slf4jWebFilter(urlPatterns)publicclassDemoFilterimplementsFilter{OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainfilterChain)throwsIOException,ServletException{log。info(Filter进入);filterChain。doFilter(request,response);log。info(Filter退出);}}lInterceptor过滤器publicclassDemoInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{log。info(InterceptorpreHandle进入);returntrue;}OverridepublicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,ModelAndViewmodelAndView)throwsException{log。info(InterceptorpostHandle进入);}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{log。info(InterceptorafterCompletion进入);}}ControllerAdviceControllerAdvicepublicclassDemoControllerAdvice{InitBinderpublicvoidinit(WebDataBinderbinder){log。info(ControllerAdviceinit进入);binder。setFieldDefaultPrefix(user。);}ExceptionHandler(Exception。class)publicStringhandleException(Exceptione){log。info(ControllerAdvicehandleException进入);returnerror;}}AOPAspectComponentpublicclassDemoAspect{Pointcut((target(org。springframework。web。bind。annotation。RestController))within(cn。demo。api。。)execution(public(。。)))publicvoidpointcut(){}Around(valuepointcut())publicObjectaround(ProceedingJoinPointpjp)throwsThrowable{log。info(Aop进入);Objectproceedpjp。proceed();log。info(Aop退出);returnproceed;}}控制器入口RestControllerRequestMapping(demo)publicclassDemoController{RequestMapping(test)publicMapString,Stringtest(ModelAttribute(user)Useruser){log。info(业务:user。name:{},user。getName());MapString,StringresultnewHashMap(2);result。put(code,200);inti1;ii0;returnresult;}}复制代码
定义好Demo示例,接着来一发:http:localhost:8080demotest?user。name宫三公子:结果:Filter进入InterceptorpreHandle进入ControllerAdviceinit进入Aop进入业务:user。name:宫三公子ControllerAdvicehandleException进入InterceptorafterCompletion进入Filter退出复制代码
好了,我们似乎已经看到,4种拦截方法的执行顺序是这样子:
2。知晓4种拦截方法应用场景
那么针对这4种拦截方法,它们涉及的应用场景、实现技术、作用力度都各不相同,为了有一个比较清晰的对比,老司机我简单粗暴,直接整理好啦,大伙干:
3。掌握4种拦截方法的原理
如果只晓得执行顺序和应用场景可能过不了面试大大狠辣的毒眼,知其然并知其所以然,我们就自废10根头发,来研究一下它们的原理吧。3。1过滤器
虽然一个过滤器在一次请求中只能调用一次,但是根据具体业务需求,可以生成多个不同类型的Filter并依次执行,这就是过滤器的链式调用,可以通过指定Order排序,值越小越靠前(默认根据Filter的名称进行自然排序),新建了4个Filter,Order依次为03,演示一波,符合预期:
接下来我们关注整个链式调用的核心:FilterChain接口,内部定义了doFilter接口方法,tomcat容器提供了ApplicationFilterChain作为FilterChain的具体实现:publicfinalclassApplicationFilterChainimplementsFilterChain{privateApplicationFilterConfig〔〕filtersnewApplicationFilterConfig〔0〕;privateServletservlet;publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse){internalDoFilter(request,response);}privatevoidinternalDoFilter(ServletRequestrequest,ServletResponseresponse){if(this。posthis。n){ApplicationFilterConfigfilterConfigthis。filters〔this。pos〕;FilterfilterfilterConfig。getFilter();filter。doFilter(request,response,this);}else{this。servlet。service(request,response);}}}复制代码
内部定义了ApplicationFilterConfig〔〕filters过滤器配置列表,每一个ApplicationFilterConfig内部持有一个Filter实例,另一个比较重要的是Servlet,实例化后对应原生HttpServlet或SpringMVC的DispatcherServlet,当拦截器链路执行完成后,会调用Servlet中service方法做后续的Url路由映射、业务处理以及视图响应等流程了(这个后面研究SpringMVC的请求流程来详细再分析),好了,我们通过Debug可以看到,filters中除了服务默认的一些请求filter,我们自己定义的4个filter也以定义好的顺序排入其中了:
过滤器整体执行流程Likethis:
3。2拦截器
拦截器调用流程比较复杂,我这面根据源码梳理了核心请求流程和简要说明,感兴趣的同学可以继续一探究竟:源码位于:org。springframework。web。servlet。DispatcherServletdoDispatch方法
Tip一下:网上有些朋友说拦截器是基于反射、动态代理来实现的,通过司机对源码分析,反射倒是有用到,主要从Spring的IOC容器中获取了拦截器对象,并放在AbstractHandlerMapping的adaptedInterceptors全局对象中,在上图的第二步就匹配满足的拦截器作为当前请求的拦截器列表,没有动态代理就没有影子!。3。3ControllerAdvice
其实,ControllerAdvice和拦截器实现有异曲同工之处,要是说用什么技术手段,那应该也只能说是反射吧,在也主要在上一步的doDispatch方法中,它主要是在分布第4步t通过反射对InitBinder的参数的设置和第6步进行统一的异常捕获,重点看看第6步:在processDispatchResult处理结果方法内部,调用processHandlerException方法进行异常相关的处理逻辑,我们可以看到它的主要工作就是遍历handlerExceptionResolvers来进行异常对应的处理,我们自定义的全局异常ExceptionHandlerExceptionResolver实例控制着所有的Controller类,debug源码:
那什么时候进行设置的以及如何设置的呢,值得我们思考一下,怀着好奇心,我们点开ExceptionHandlerExceptionResolver来看看,原来重点是实现了InitializingBean的afterPropertiesSet方法,在容器启动时候检测带ControllerAdvice注解的类和类中带ExceptionHandler注解的方法,并加入到我们刚才看到的exceptionHandlerCache中:publicclassExceptionHandlerExceptionResolverextendsAbstractHandlerMethodExceptionResolverimplementsApplicationContextAware,InitializingBean{privatefinalMapClasslt;?,ExceptionHandlerMethodResolverexceptionHandlerCachenewConcurrentHashMap(64);重点实现了afterPropertiesSetpublicvoidafterPropertiesSet(){this。initExceptionHandlerAdviceCache();}privatevoidinitExceptionHandlerAdviceCache(){if(this。getApplicationContext()!null){寻找所有带有ControllerAdvice注解的类ListControllerAdviceBeanadviceBeansControllerAdviceBean。findAnnotatedBeans(this。getApplicationContext());Iteratorvar2adviceBeans。iterator();while(var2。hasNext()){ControllerAdviceBeanadviceBean(ControllerAdviceBean)var2。next();Classlt;?beanTypeadviceBean。getBeanType();寻找该类下面的方法是否有ExceptionHandler注解,如果有,则收集到mappedMethods列表中ExceptionHandlerMethodResolverresolvernewExceptionHandlerMethodResolver(beanType);如果不为空,则加入到全局异常拦截缓存中if(resolver。hasExceptionMappings()){this。exceptionHandlerAdviceCache。put(adviceBean,resolver);}}}}}复制代码3。4AOP
AOP这块想着网上其实很多同学梳理得很好了,到底要不要整理,但考虑到完整性,也避免同学们再额外找文章跳来跳去,就一并简化整理了,接着继续:前面说到,AOP是有两种实现方式,相信大家都耳熟能详:JDK动态代理和CGLib,带大家复习一下,先来一个JDK动态代理的栗子:1。动态代理是基于接口访问的,先定义用户服务接口publicinterfaceUserService{UsergetUser(Stringname);}2。具体用户服务实现类,也即被代理对象publicclassUserServiceImplimplementsUserService{OverridepublicUsergetUser(Stringname){UserusernewUser();user。setName(宫三公子);System。out。println(用户名:user。getName());returnuser;}}3。代理工具,用于服务增强publicclassJDKProxyrimplementsInvocationHandler{privateObjecttarget;publicJDKProxy(Objecttarget){this。targettarget;}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{System。out。println(beforeJDKProxy);Objectinvokemethod。invoke(target,args);通过反射执行,目标类的方法System。out。println(afterJDKProxy);returninvoke;}}4。测试方法publicclassJDKProxyTest{publicstaticvoidmain(String〔〕args){UserServiceImpluserServicenewUserServiceImpl();JDKProxyhandlernewJDKProxy(userService);UserServiceproxyInstance(UserService)Proxy。newProxyInstance(userService。getClass()。getClassLoader(),userService。getClass()。getInterfaces(),handler);UseruserproxyInstance。getUser(宫三公子);}}复制代码
测试结果:beforeJDKProxy用户名:宫三公子afterJDKProxy复制代码
CGLib的代理方式是为我们需要被代理的具体类生成一个子类,即将需被代理的方法进行Override重写:CGLib代理工具,用于生成业务增强publicclassCGLibProxyimplementsMethodInterceptor{publicObjectintercept(Objectarg0,Methodmethod,Object〔〕objects,MethodProxyproxy)throwsThrowable{System。out。println(beforeCGLibProxy);Objectresultproxy。invokeSuper(arg0,objects);System。out。println(afterCGLibProxy);returnresult;}}测试方法publicclassCGLibProxyTest{publicstaticvoidmain(String〔〕args){CGLibProxyproxynewCGLibProxy();EnhancerenhancernewEnhancer();enhancer。setSuperclass(UserServiceImpl。class);回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法enhancer。setCallback(proxy);UserServiceImpluserService(UserServiceImpl)enhancer。create();userService。getUser(宫三公子);打印增强效果System。out。println(打印userService的增强结果:);ReflectUtils。printMethods(userService。getClass());}}复制代码
打印结果,我们可以看到被代理的方法已经执行了增强逻辑:beforeCGLibProxy用户名:宫三公子afterCGLibProxy打印userService的增强结果:publicfinalgetUser(java。lang。String);publicsetCallback(int,net。sf。cglib。proxy。Callback);publicstaticCGLIBfindMethodProxy(net。sf。cglib。core。Signature);publicstaticCGLIBSETTHREADCALLBACKS(〔Lnet。sf。cglib。proxy。Callback;);publicstaticCGLIBSETSTATICCALLBACKS(〔Lnet。sf。cglib。proxy。Callback;);publicgetCallback(int);publicgetCallbacks();publicsetCallbacks(〔Lnet。sf。cglib。proxy。Callback;);privatestaticfinalCGLIBBINDCALLBACKS(java。lang。Object);staticCGLIBSTATICHOOK1();finalCGLIBhashCode3();finalCGLIBclone4();finalCGLIBtoString2();finalCGLIBequals1(java。lang。Object);finalCGLIBgetUser0(java。lang。String);publicfinalequals(java。lang。Object);publicfinaltoString();publicfinalhashCode();protectedfinalclone();publicnewInstance(〔Lnet。sf。cglib。proxy。Callback;);publicnewInstance(〔Ljava。lang。Class;,〔Ljava。lang。Object;,〔Lnet。sf。cglib。proxy。Callback;);publicnewInstance(net。sf。cglib。proxy。Callback);复制代码
好了,我们看完了基于Jdk动态代理和CGLib代理的两种Demo,那我们继续看看Spring是怎么玩的吧。在Spring的启动过程中,有很多的扩展点,比较让我们熟知的BeanPostPorcessor,主要在Bean进行initializeBean初始化后,调用org。springframework。beans。factory。support。AbstractAutowireCapableBeanFactoryapplyBeanPostProcessorsAfterInitialization,内部对所有的BeanPostPorcessor对象进行遍历,调用postProcessAfterInitialization进行处理,而Aop就是基于该扩展点实现的:
沿着这条链路Debug调试,最终可以定位到具体决定调用JDK动态代理还是使用CGLib,我这代理的是DemoController,那生成的就是CGLig代理啦:
OK,关于过滤器、拦截器、ControllerAdvice以及AOP,本文从执行顺序、应用场景、实现技术以及原理等进行了总结,看完了,朋友们如果有收获就是对我最大得肯定。
来源:https:juejin。cnpost7212175872091029559
转型中的小米图片来源视觉中国文光子星球,作者吴先之,编辑王潘8月19日,小米披露2022年第二季度与半年报,数据显示,Q2小米实现营收702亿元,同比下滑20,经调净利润21亿……
人生赢家伊戈达拉很多球迷朋友,在提到伊戈达拉,第一时间想到的是抱勇士大腿,14赛季抢走库里总决赛MVP,混了4枚总冠军戒指,可在我看来,伊戈达拉就他NBA球员生涯来看,他是妥妥的人生赢家。……
他,30岁获杰青资助,40岁评上院士,刚刚团队成果登上Nat考研经验大家谈反芳香丁富烯的通用构筑方法自从1865年凯库勒首次提出苯环的结构以来,人们开发了大量关于苯及其衍生物的合成方法。在苯环的结构中,由于存在六元环的电子结……
Web3。0,泡沫还是机遇?来源Tech数字星球山雨欲来风满楼。关于Web3,从业者的呐喊声、旁观者的议论声犹如洪水一般袭来,有期待、有追捧、有质疑,到底哪一个描述才是真实的Web3?有人说所……
华硕也出手了,DIY电竞电脑未来者降价,配RTX3060售5前几天,我们介绍了联想的DIY品牌异能者,这几天,华硕的DIY品牌未来者就来了,其也是明显标注各种配件的厂家,型号,并提供3年质保,预装系统,让我们来看看。可以看到,未来……
罚罪最出人意料的卧底,特种强兵邱涛,最后命运让人担忧《罚罪》作为一部刑侦剧有两个类似无间道的看点:赵啸声手里的大王是谁?严国华手里的王牌卧底是谁?很多观众都会认为,这两个最隐秘的人物估计会一直潜伏到《罚罪》结尾,没想到的是……
华为mate50懂的人都觉的值华为发布会结束了,欢喜忧愁各有见解,不友好的每年重复的酸和幽怨,不明真相的人一看价格也跟风点火,不过没有关系,喜欢还有理性的人一直会继续喜欢。为什么说华为的Mate经典款……
热火与凯尔特人的完美交易,热火赢在未来,绿军现在就要夺冠凯尔特人夏天为了得到杜兰特,多次将杰伦布朗摆放在了谈判筹码中,这个行为无疑会伤害到布朗。尽管凯尔特人已经展开了安抚工作,但是破镜难重圆,裂痕已产生。凯尔特人对布朗的不尊重必然为……
北极变暖速度全球第一,1。4万亿吨甲烷或被释放!会有什么后果在我们的印象中,地球的南极和北极应该是地球上最冷的区域,放眼望去一望无际的肯定是茫茫冰雪,在极夜期间天空中挂满的将是绚烂多彩的极光,冷寂用来形容地球的两极应该是再恰当不过了。但……
你不了解的月嫂行业的前景,就会错失财富的光顾月嫂是专门照顾月子期间产妇和新生儿的一个职业,是集育婴师、催乳师、产康师、厨师、护士的工作性质于一身的高级家政服务人员。01hr育儿观念的转变中国目前的社会大环境、……
5年击败阿里,耻与马云并肩,郭凡生为啥这么狂?1hr大家注意看这段视频。视频是2005年的一段央视财经节目《赢在中国》,视频中这位戴眼镜的中年人叫郭凡生,他正与台下的一名观众辩论:他创立的慧聪网和阿里巴巴,哪一个更有前途?……
翟山鹰爆联想大料联想有关话题长期是民众和广大网民热议的话题,联想被热议的原因有很多,一是联想是知名科技公司,二是有关联想话题都是以前民众不知道的,三是联想有关话题和争议没有得到权威定论。……