SpringBoot中如何使用Aop(aop,springboot,开发技术)

时间:2024-05-09 19:40:33 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

什么是aop

AOP(Aspect OrientedProgramming):面向切面编程,面向切面编程(也叫面向方面编程),是目前软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

使用场景

利用AOP可以对我们边缘业务进行隔离,降低无关业务逻辑耦合性。提高程序的可重用性,同时提高了开发的效率。一般用于日志记录,性能统计,安全控制,权限管理,事务处理,异常处理,资源池管理。使用场景

为什么需要面向切面编程

面向对象编程(OOP)的好处是显而易见的,缺点也同样明显。当需要为多个不具有继承关系的对象添加一个公共的方法的时候,例如日志记录、性能监控等,如果采用面向对象编程的方法,需要在每个对象里面都添加相同的方法,这样就产生了较大的重复工作量和大量的重复代码,不利于维护。面向切面编程(AOP)是面向对象编程的补充,简单来说就是统一处理某一“切面”的问题的编程思想。如果使用AOP的方式进行日志的记录和处理,所有的日志代码都集中于一处,不需要再每个方法里面都去添加,极大减少了重复代码。

技术要点

  • 通知(Advice)包含了需要用于多个应用对象的横切行为,完全听不懂,没关系,通俗一点说就是定义了“什么时候”和“做什么”。

  • 连接点(Join Point)是程序执行过程中能够应用通知的所有点。

  • 切点(Poincut)是定义了在“什么地方”进行切入,哪些连接点会得到通知。显然,切点一定是连接点。

  • 切面(Aspect)是通知和切点的结合。通知和切点共同定义了切面的全部内容——是什么,何时,何地完成功能。

  • 引入(Introduction)允许我们向现有的类中添加新方法或者属性。

  • 织入(Weaving)是把切面应用到目标对象并创建新的代理对象的过程,分为编译期织入、类加载期织入和运行期织入。

整合使用

导入依赖

在springboot中使用aop要导aop依赖

<!--aop切面--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

注意这里版本依赖于spring-boot-start-parent父pom中的spring-boot-dependencies

编写拦截的bean

这里我们定义一个controller用于拦截所有请求的记录

@RestControllerpublicclassAopController{@RequestMapping("/hello")publicStringsayHello(){System.out.println("hello");return"hello";}}

定义切面

SpringBoot在使用切面的时候采用@Aspect注解对POJO进行标注,该注解表明该类不仅仅是一个POJO,还是一个切面容器

定义切点

切点是通过@Pointcut注解和切点表达式定义的。

@Pointcut注解可以在一个切面内定义可重用的切点。

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且实际中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。如图是execution表达式的语法:

SpringBoot中如何使用Aop

execution表示在方法执行的时候触发。以“”开头,表明方法返回值类型为任意类型。然后是全限定的类名和方法名,“”可以表示任意类和任意方法。对于方法参数列表,可以使用“..”表示参数为任意类型。如果需要多个表达式,可以使用“&&”、“||”和“!”完成与、或、非的操作。

定义通知

通知有五种类型,分别是:

  • 前置通知(@Before):在目标方法调用之前调用通知

  • 后置通知(@After):在目标方法完成之后调用通知

  • 环绕通知(@Around):在被通知的方法调用之前和调用之后执行自定义的方法

  • 返回通知(@AfterReturning):在目标方法成功执行之后调用通知

  • 异常通知(@AfterThrowing):在目标方法抛出异常之后调用通知

代码中定义了三种类型的通知,使用@Before注解标识前置通知,打印“beforeAdvice...”,使用@After注解标识后置通知,打印“AfterAdvice...”,使用@Around注解标识环绕通知,在方法执行前和执行之后分别打印“before”和“after”。这样一个切面就定义好了,代码如下:

@Aspect@ComponentpublicclassAopAdvice{@Pointcut("execution(*com.shangguan.aop.controller.*.*(..))")publicvoidtest(){}@Before("test()")publicvoidbeforeAdvice(){System.out.println("beforeAdvice...");}@After("test()")publicvoidafterAdvice(){System.out.println("afterAdvice...");}@Around("test()")publicvoidaroundAdvice(ProceedingJoinPointproceedingJoinPoint){System.out.println("before");try{proceedingJoinPoint.proceed();}catch(Throwablet){t.printStackTrace();}System.out.println("after");}}

运行结果

SpringBoot中如何使用Aop

案例场景

这里我们通过一个日志记录场景来完整的使用Aop切面业务层只需关心代码逻辑实现而不用关心请求参数和响应参数的日志记录

那么首先我们需要自定义一个全局日志记录的切面类GlobalLogAspect

然后在该类添加@Aspect注解,然后在定义一个公共的切入点(Pointcut),指向需要处理的包,然后在定义一个前置通知(添加@Before注解),后置通知(添加@AfterReturning)和环绕通知(添加@Around)方法实现即可

日志信息类

packagecn.soboys.core;importlombok.Data;/***@authorkenx*@version1.0*@date2021/6/1818:48*日志信息*/@DatapublicclassLogSubject{/***操作描述*/privateStringdescription;/***操作用户*/privateStringusername;/***操作时间*/privateStringstartTime;/***消耗时间*/privateStringspendTime;/***URL*/privateStringurl;/***请求类型*/privateStringmethod;/***IP地址*/privateStringip;/***请求参数*/privateObjectparameter;/***请求返回的结果*/privateObjectresult;/***城市*/privateStringcity;/***请求设备信息*/privateStringdevice;}

全局日志拦截

packagecn.soboys.core;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.reflect.MethodSignature;importjava.lang.reflect.Method;/***@authorkenx*@version1.0*@date2021/6/1814:52*切面*/publicclassBaseAspectSupport{publicMethodresolveMethod(ProceedingJoinPointpoint){MethodSignaturesignature=(MethodSignature)point.getSignature();Class<?>targetClass=point.getTarget().getClass();Methodmethod=getDeclaredMethod(targetClass,signature.getName(),signature.getMethod().getParameterTypes());if(method==null){thrownewIllegalStateException("无法解析目标方法:"+signature.getMethod().getName());}returnmethod;}privateMethodgetDeclaredMethod(Class<?>clazz,Stringname,Class<?>...parameterTypes){try{returnclazz.getDeclaredMethod(name,parameterTypes);}catch(NoSuchMethodExceptione){Class<?>superClass=clazz.getSuperclass();if(superClass!=null){returngetDeclaredMethod(superClass,name,parameterTypes);}}returnnull;}}

GlobalLogAspect

packagecn.soboys.core;importcn.hutool.core.date.DateUtil;importcn.hutool.core.date.TimeInterval;importcn.hutool.json.JSONUtil;importcn.soboys.core.utils.HttpContextUtil;importio.swagger.annotations.ApiOperation;importlombok.extern.slf4j.Slf4j;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.AfterThrowing;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.springframework.stereotype.Component;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestParam;importjavax.servlet.http.HttpServletRequest;importjava.lang.reflect.Method;importjava.lang.reflect.Parameter;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjava.util.Map;/***@authorkenx*@version1.0*@date2021/6/1815:22*全局日志记录器*/@Slf4j@Aspect@ComponentpublicclassGlobalLogAspectextendsBaseAspectSupport{/***定义切面Pointcut*/@Pointcut("execution(public*cn.soboys.mallapi.controller.*.*(..))")publicvoidlog(){}/***环绕通知**@paramjoinPoint*@return*/@Around("log()")publicObjectdoAround(ProceedingJoinPointjoinPoint)throwsThrowable{LogSubjectlogSubject=newLogSubject();//记录时间定时器TimeIntervaltimer=DateUtil.timer(true);//执行结果Objectresult=joinPoint.proceed();logSubject.setResult(result);//执行消耗时间StringendTime=timer.intervalPretty();logSubject.setSpendTime(endTime);//执行参数Methodmethod=resolveMethod(joinPoint);logSubject.setParameter(getParameter(method,joinPoint.getArgs()));HttpServletRequestrequest=HttpContextUtil.getRequest();//接口请求时间logSubject.setStartTime(DateUtil.now());//请求链接logSubject.setUrl(request.getRequestURL().toString());//请求方法GET,POST等logSubject.setMethod(request.getMethod());//请求设备信息logSubject.setDevice(HttpContextUtil.getDevice());//请求地址logSubject.setIp(HttpContextUtil.getIpAddr());//接口描述if(method.isAnnotationPresent(ApiOperation.class)){ApiOperationapiOperation=method.getAnnotation(ApiOperation.class);logSubject.setDescription(apiOperation.value());}Stringa=JSONUtil.toJsonPrettyStr(logSubject);log.info(a);returnresult;}/***根据方法和传入的参数获取请求参数*/privateObjectgetParameter(Methodmethod,Object[]args){List<Object>argList=newArrayList<>();Parameter[]parameters=method.getParameters();Map<String,Object>map=newHashMap<>();for(inti=0;i<parameters.length;i++){//将RequestBody注解修饰的参数作为请求参数RequestBodyrequestBody=parameters[i].getAnnotation(RequestBody.class);//将RequestParam注解修饰的参数作为请求参数RequestParamrequestParam=parameters[i].getAnnotation(RequestParam.class);Stringkey=parameters[i].getName();if(requestBody!=null){argList.add(args[i]);}elseif(requestParam!=null){map.put(key,args[i]);}else{map.put(key,args[i]);}}if(map.size()>0){argList.add(map);}if(argList.size()==0){returnnull;}elseif(argList.size()==1){returnargList.get(0);}else{returnargList;}}}
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:SpringBoot中如何使用Aop的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:如何利用Node实现内容压缩下一篇:

7 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18