为什么要用AOP?
答案是解耦!
Aspect Oriented Programming 面向切面编程。解耦是程序员编码开发过程中一直追求的。AOP也是为了解耦所诞生。
具体思想是:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。
AOP 主要是利用代理模式的技术来实现的。具体的代理实现可以参考这篇文章,讲解的非常详细。https://www.cnblogs.com/yanbincn/archive/2012/06/01/2530377.html
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
常用的工作场景
- 事务控制
- 日志记录
本文没有过度深度学习原理,因为是菜鸟一个,先学会怎么不加班。
必须知道的概念
AOP 的相关术语
通知(Advice)
通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。
- 前置通知(Before):在目标方法调用前调用通知功能;
- 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
- 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;
- 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;
- 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。
连接点(JoinPoint)
通知功能被应用的时机。比如接口方法被调用的时候就是日志切面的连接点。
切点(Pointcut)
切点定义了通知功能被应用的范围。比如日志切面的应用范围就是所有接口,即所有 controller 层的接口方法。
切面(Aspect)
切面是通知和切点的结合,定义了何时、何地应用通知功能。
引入(Introduction)
在无需修改现有类的情况下,向现有的类添加新方法或属性。
织入(Weaving)
把切面应用到目标对象并创建新的代理对象的过程。
Spring 中使用注解创建切面
相关注解
- @Aspect:用于定义切面
- @Before:通知方法会在目标方法调用之前执行
- @After:通知方法会在目标方法返回或抛出异常后执行
- @AfterReturning:通知方法会在目标方法返回后执行
- @AfterThrowing:通知方法会在目标方法抛出异常后执行
- @Around:通知方法会将目标方法封装起来
- @Pointcut:定义切点表达式
切点表达式
指定了通知被应用的范围,表达式格式:
execution(方法修饰符返回类型方法所属的包.类名.方法名称(方法参数) //com.ninesky.study.tiny.controller包中所有类的public方法都应用切面里的通知execution(public*com.ninesky.study.tiny.controller.*.*(..)) //com.ninesky.study.tiny.service包及其子包下所有类中的所有方法都应用切面里的通知execution(*com.ninesky.study.tiny.service..*.*(..)) //com.ninesky.study.tiny.service.PmsBrandService类中的所有方法都应用切面里的通知execution(*com.macro.ninesky.study.service.PmsBrandService.*(..))
实战应用-利用AOP记录日志
从传统行业转行,以前都没想过打日志埋点,第一份工作,真的应该选择一个好的平台比较重要。
定义日志信息封装
用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息
publicclassWebLog { /*** 操作描述*/privateStringdescription; /*** 操作用户*/privateStringusername; /*** 操作时间*/privateLongstartTime; /*** 消耗时间*/privateIntegerspendTime; /*** 根路径*/privateStringbasePath; /*** URI*/privateStringuri; /*** URL*/privateStringurl; /*** 请求类型*/privateStringmethod; /*** IP地址*/privateStringip; /*** 请求参数*/privateObjectparameter; /*** 请求返回的结果*/privateObjectresult; //省略了getter,setter方法}
定义注解,通过注解减少代码量
RetentionPolicy.RUNTIME) (ElementType.METHOD) (publicOperationLog { Stringname();//调用接口的名称booleanintoDb() defaultfalse;//该条操作日志是否需要持久化存储}
统一日志处理切面
1) (publicclassWebLogAspect { privatestaticfinalLoggercontrolLog=LoggerFactory.getLogger("tmall_control"); "execution(public * com.yee.walnut.*.*.*(..))") (publicvoidwebLog() { } value="webLog()&& @annotation(OperationLog)") (publicvoiddoBefore(ControllerWebLogcontrollerWebLog) throwsThrowable { } value="webLog()&& @annotation(OperationLog)", returning="ret") (publicvoiddoAfterReturning(Objectret, ControllerWebLogcontrollerWebLog) throwsThrowable { } value="webLog()&& @annotation(OperationLog)") (publicObjectdoAround(ProceedingJoinPointjoinPoint, OperationLogoperationLog) throwsThrowable { longstartTime=System.currentTimeMillis(); //获取当前请求对象ServletRequestAttributesattributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequestrequest=attributes.getRequest(); //记录请求信息Object[] objs=joinPoint.getArgs(); WebLogwebLog=newWebLog(); Objectresult=joinPoint.proceed();//返回的结果,这是一个进入方法和退出方法的一个分界Signaturesignature=joinPoint.getSignature(); MethodSignaturemethodSignature= (MethodSignature) signature; Methodmethod=methodSignature.getMethod(); longendTime=System.currentTimeMillis(); StringurlStr=request.getRequestURL().toString(); webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); webLog.setIp(request.getRemoteUser()); webLog.setMethod(request.getMethod()); webLog.setParameter(getParameter(method, joinPoint.getArgs())); webLog.setResult(JSONUtil.parse(result)); webLog.setSpendTime((int) (endTime-startTime)); webLog.setStartTime(startTime); webLog.setUri(request.getRequestURI()); webLog.setUrl(request.getRequestURL().toString()); controlLog.info("RequestAndResponse {}", JSONObject.toJSONString(webLog)); //必须有这个返回值。可以这样理解,Around方法之后,不再是被织入的函数返回值,而是Around函数返回值returnresult; } /*** 根据方法和传入的参数获取请求参数*/privateObjectgetParameter(Methodmethod, Object[] args) { List<Object>argList=newArrayList<>(); Parameter[] parameters=method.getParameters(); for (inti=0; i<parameters.length; i++) { //将RequestBody注解修饰的参数作为请求参数RequestBodyrequestBody=parameters[i].getAnnotation(RequestBody.class); if (requestBody!=null) { argList.add(args[i]); } //将RequestParam注解修饰的参数作为请求参数RequestParamrequestParam=parameters[i].getAnnotation(RequestParam.class); if (requestParam!=null) { Map<String, Object>map=newHashMap<>(); Stringkey=parameters[i].getName(); if (!StringUtils.isEmpty(requestParam.value())) { key=requestParam.value(); } map.put(key, args[i]); argList.add(map); } else { argList.add(args[i]); } } if (argList.size() ==0) { returnnull; } elseif (argList.size() ==1) { returnargList.get(0); } else { returnargList; } } }
在方法上加上自定义注解即可
name="TurnOnOffStrategy") (publicStringdoOperation(GlobalDtoglobalDto, DeviceOperatordeviceOperator) { }