当前位置: 首页 > 科技观察

深入理解Spring中的AOP

时间:2023-03-22 13:06:37 科技观察

说到AOP,大家都知道是面向切面编程,但是你真的了解Spring中的AOP吗?SpringAOP、JDK动态代理、CGLIB、AspectJ有什么联系和区别?Spring中的AOP包括两个概念。一种是Spring官方基于JDK动态代理和CGLIB实现的SpringAOP;另一个是面向切面编程的集成神器AspectJ。SpringAOP和AspectJ不是竞争关系。基于代理的框架SpringAOP和成熟的框架AspectJ都是有价值的,它们是相辅相成的。Spring无缝集成了SpringAOP、IoC和AspectJ,实现了AOP的所有能力。SpringAOP默认使用标准的JDK动态代理进行AOP代理,可以代理任何接口。但是如果没有面向接口编程,只有业务类,就用CGLIB。当然你也可以强制全部使用CGLIB,只要设置proxy-target-class="true"即可。AOP(Advice)中的术语通知(Advice)Spring切面可以应用5种通知:预通知(Before):在调用目标方法之前调用通知函数;post-advice(After):在目标方法完成后调用通知,此时你不关心方法的输出是什么;返回通知(After-returning):目标方法执行成功后的调用通知;异常通知(After-throwing):目标方法抛出异常后的调用通知;环绕通知(Aroundnotification)):通知包裹被通知的方法,在被通知的方法调用前后执行自定义行为。Joinpoint、Poincut、Aspect、Introduction、Weaving等术语在其他博文中有很多解释,这里不再赘述。用两个例子来说明SpringAOP和AspectJ的用法现在有这样一个场景,页面传入当前页面的参数以及每个页面显示多少条数据行,我们需要写一个拦截器来转换页面并将limit参数转化为MySQL分页语句的偏移量、行数。先看SpringAOP实现1、实现MethodInterceptor,截取方法publicclassMethodParamInterceptorimplementsMethodInterceptor{@Override@SuppressWarnings("unchecked")publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{Object[]params=invocation.getArguments();if(ArrayUtils){paramUtils.returninvocation.proceed();}for(Objectparam:params){//如果参数类型是Mapif(paraminstanceofMap){MapparamMap=(Map)param;processPage(paramMap);break;}}returninvocation.proceed();}/****@paramparamMap*/privatevoidprocessPage(MapparamMap){if(!paramMap.containsKey("page")&&!paramMap.containsKey("limit")){return;}intpage=1;introws=10;for(Map.条目条目:paramMap.entrySet()){Stringkey=entry.getKey();Stringvalue=entry.getValue().toString();if("page".equals(key)){page=NumberUtils.toInt(value,page);}elseif("limit".equals(key)){rows=NumberUtils.toInt(value,rows);}else{//TODO}}intoffset=(page-1)*rows;paramMap.put("offset",偏移量);paramMap.put("rows",rows);}}2.定义后处理器并将方法拦截器添加到顾问程序中。我们通过注解@Controller拦截所有的Controller,@RestController继承自Controller,所以统一拦截。publicclassRequestParamPostProcessorextendsAbstractBeanFactoryAwareAdvisingPostProcessorimplementsInitializingBean{privateClassvalidatedAnnotationType=Controller.class;@OverridepublicvoidafterPropertiesSet()throwsException{Pointcutpointcut=newAnnotationMatchingPointcut(this.validatedAnnotationType,true);this.advisor=newDefaultPointcutAdvisor(pointcut,newMethodParamInterceptor());}}3、万事俱备只欠东风,Processor也写了,只需要让Processor生效即可。@ConfigurationpublicclassMethodInterceptorConfig{@BeanpublicRequestParamPostProcessorconverter(){returnnewRequestParamPostProcessor();}}`这里有一个坑需要注意,如果在配置类中注入业务bean。@ConfigurationpublicclassMethodInterceptorConfig{@AutowiredprivateUserServiceuserService;@BeanpublicRequestParamPostProcessorconverter(){returnnewRequestParamPostProcessor();}}启动时,会出现:2019-11-0814:55:50.954INFO51396---[main]trationDelegate$BeanPostProcessor'sqlChecker'Bean.apache.ibatis.session.defaults.DefaultSqlSessionFactory]isnoteligibleforgettingprocessedbyallBeanPostProcessors(例如:noteligibleforauto-proxying)2019-11-0814:55:50.960INFO51396---[main]trationDelegate$BeanPostProcessorChecker:Bean'sqlSession.springTemplate'oftype[.SqlSessionTemplate]isnoteligibleforgettingprocessedbyallBeanPostProcessors(forexample:noteligibleforauto-proxying)2019-11-0814:55:51.109INFO51396---[main]trationDelegate$BeanPostProcessorChecker:Bean'rememberMapper'oftype[com.sun.proxy.$Proxy84]isnoteligibleforgettingprocessedbyallBeanPostProcessors(forexample:noteligibleforauto-proxying)2019-11-0814:55:53.406INFO51396---[main]trationDelegate$BeanPostProcessorChecker:Bean'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration'oftype[org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration]isnoteligibleforgettingprocessedbyallBeanPostProcessors(forexample:noteligibleforauto)定义的Bean,自定义Bean优先级最低,加载并由具有最低优先级的BeanPostProcessor初始化。但是,为了加载RequestParamPostProcessor,必须先加载低优先级的bean。此时事务处理器的AOP还没有加载,注解事务初始化失败。但是Spring提示一个INFO级别的提示,然后剩下的bean由优先级最低的BeanPostProcessor正常处理。AspectJ方法实现切面@Component@Aspect@Slf4jpublicclassMethodParamInterceptor{@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")publicvoidparamAspect(){}@Before("paramAspect()")publicvoidbeforeDataSource(JoinPointjoinPoint){Arrays.stream(joinPoint.getArgs()).forEach(paramObject->{if(paramObjectinstanceofMap){Mapparameter=(Map)paramObject;processPage(parameter);}});}privatevoidprocessPage(MapparamMap){if(null==paramMap){return;}if(!paramMap.containsKey("page")&&!paramMap.containsKey("limit")){return;}intpage=1;introws=10;for(Map.条目entry:paramMap.entrySet()){Stringkey=entry.getKey();Stringvalue=entry.getValue().toString();if("page".equals(key)){page=NumberUtils.toInt(值,页);}elseif("limit".equals(key)){rows=NumberUtils.toInt(值,行);}}intoffset=(page-1)*rows;paramMap.put("offset“,偏移量);paramMap。put("rows",rows);}@After("paramAspect()")publicvoidafterDataSource(JoinPointjoinPoint){}}从上面两个例子,我们可以对比一下SpringAOP和AspectJ两种不同的用法,但是能力实现是一样的SpingAOP更适合组织和抽象代码的场景,而AspectJ用于简单的切面更简洁的实现某个功能。【本文为栏目机构“周朴数据”微信公众号“周朴数据(id:zhoupudata)”原创文章】点此查看本作者更多好文