AOP的定义:AOP(Aspect Oriented Progamming)利用称为"横切"的技术,剖解开封装的对象内部,把多个类的公共行为封装到一个可重用模块中,便于减少重复代码,降低模块之间的耦合度,符合“开闭原则”。
上面这段关于AOP的定义是从网上抄的,是不是很拗口,我们就结合实际开发来先简单了解下AOP中的一些专业术语,然后再去学习使用它吧。
在通常的业务开发中,我们往往只需要让业务逻辑去关注业务本身,但是一些复杂的业务我们为了保证数据的完整、后序发生问题之后能够快速定位问题,通常伴随着安全、日志、事务等代码,业务写多了我们发现这些代码通常都是类似的,那么我们不就可以将这些类似的代码封装起来,再需要使用的地方进行复用吗?AOP就为提供了这么一种思路,但是AOP中的一些专业术语特别拗口,我们在这简单总结下,做下记录方便后面的学习。
1、通知(Advice):就是我们上面提到的除了业务代码本身之外,一些“可重用的一些代码”,先定义好,在需要的地方进行重用;
2、连接点(JoinPoint):允许你使用“通知”的地方,就是允许你重复使用代码的地方,方法执行的前后或者方法抛出异常时,Spring只支持方法级别的连接点;
3、切入点(Pointcut): 假如有若干方法,但是我们并不是想在所有的方法中“重用这段可复用的代码”,我们只想在某些特定的方法上使用通知,那么我们就可以使用切入点来进行这些连接点的“筛选了”;
4、切面(Aspect):切面简单来讲就是切入点(JoinPoint)和通知(Advice)的结合体。通知(Advice)决定了要干什么(通知的方法体)?在什么时候干?(定义通知的注解类型)。而切入点决定了要在哪儿干(即执行通知定义的方法体)。
5、引入(Introduction):允许我们向目标对象添加新的方法属性(即通过执行通知来控制对目标方法的访问);
6、目标对象(Target):引入中所提到的目标对象,也就是要被通知的对象,也就是执行真正的业务逻辑,可以在毫不知情的情况下,织入我们的切面;
7、代理(Proxy):Spring中的AOP都是通过动态代理来实现的,关于代理有不明白的可以参考设计模式之(8)——代理模式;
8、织入(Weaving):把切面应用到目标对象,创建代理对象的过程;
9、AOP方法:通知+目标对象的方法。
上面就是AOP编程中一些常用的属于,然后我们再看看AOP中最重要的通知(Advice)的分类,通知分为五类:
为了方便下面理解,我们可以认为“连接点就是一个目标方法”。
1、前置通知(Before Advice):在目标方法之前执行,前置通知不会影响目标方法的执行,除非前置通知抛出异常;
2、最终通知(After Advice):在目标方法执行完之后执行,不管目标方法是正常执行完,还是因为抛出异常退出,都会执行返回通知内的内容;
3、异常返回通知(AfterThrowing Advice):在目标方法抛出异常之后会执行;
4、后置通知(AfterReturning Advice):在目标方法正常执行完成之后执行,如果目标方法抛出异常,则不会执行;
5、环绕通知(Around Advice):环绕通知围绕在目标方法执行的前后,是一个功能最为强大的通知类型,可以在方法执行前后自定义一些操作,环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行,为了避免后面的空指针,环绕通知还需要返回方法的执行结果。
以下是我写的一个简单示例,通过一个自定义注解,来向需要的地方织入通知;
自定义注解:
package com.pep.process.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName: Audit * @Description: 描述这个类的作用 * @author: wwh * @date: 2023年3月2日 上午9:47:52 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface Audit { String action() default ""; String name() default ""; }
定义一个切面,切面中包含切入点和通知:
package com.pep.process.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @ClassName: AuditAop * @Description: 自定义切面 * @author: wwh * @date: 2023年3月2日 上午9:54:48 */ @Component @Aspect public class AuditAspect { /** * @Title: audit * @Description: 定义一个切入点 * void 返回类型 */ @Pointcut("@annotation(com.pep.process.annotation.Audit)") private void audit() { } /** * @Title: before * @Description: 前置通知 * void 返回类型 */ @Before("audit()") public void before() { System.err.println("我是前置通知..."); } /** * @Title: around * @Description: 环绕通知 * @param joinPoint * @return * Object 返回类型 * @throws Throwable */ @Around("audit()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.err.println("目标方法执行前..."); Object proceed = joinPoint.proceed(); System.err.println("目标方法执行后..."); return proceed; } /** * @Title: after * @Description: 描述这个方法的作用 * void 返回类型 */ @After(value="audit()") public void after() { System.err.println("我是后置通知..."); } /** * @Title: afterThrow * @Description: 返回异常通知 * @param ex * void 返回类型 */ @AfterThrowing(value="audit()",throwing="ex") public void afterThrow(Exception ex) { System.err.println("我是异常通知,发生的异常为:【" + ex + "】..."); } /** * @Title: afterReturning * @Description: 返回通知 * @param obj * @return * Object 返回类型 */ @AfterReturning(value="audit()",returning="obj") public Object afterReturning(Object obj) { System.err.println("我是返回通知,方法的执行结果是:【" + obj + "】"); return obj; } }
需要注意的是异常通知的注解中的throwing参数,通过这个参数,可以目标方法执行过程中抛出的异常,绑定到异常通知的方法参数中,并且这个参数值要和方法参数名称一致。
同理返回通知注解中的returning参数,也是将目标方法的执行结果和返回通知中方法的参数进行绑定,名称也必须一致。
通过使用自定义的方式来向需要的地方织入通知,实现方法功能“增强”或者控制方法访问。
package com.pep.process.controller; import net.sf.json.JSONObject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.pep.jxw.common.util.UUIDGenerator; import com.pep.process.annotation.Audit; @Controller public class LogController { @RequestMapping("/test.do") @Audit @ResponseBody public JSONObject log() { JSONObject json = new JSONObject(); json.put("id", UUIDGenerator.getUUID()); System.err.println("执行了目标方法..."); return json; } }
输出结果如下:
相信通过上面这个返回结果的分析,我们对各种通知的执行顺序有一个简单了解,后续的一些问题我们有空再议。
参考文章:
本文来自博客园,作者:一只烤鸭朝北走,仅用于技术学习,所有资源都来源于网络,部分是转发,部分是个人总结。欢迎共同学习和转载,转载请在醒目位置标明原文。如有侵权,请留言告知,及时撤除。转载请注明原文链接:https://www.cnblogs.com/wha6239/p/17172501.html