AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。 OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候, OOP则显得无能为力。OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能,日志代码往往水平地散布在所有对象 层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处 的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到 一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封 装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象” 是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部 的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分 是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的 作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思 想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行; 二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
package cn.timebusker.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON; import cn.timebusker.utils.DateUtil; /** * 面向切面编程 主要功能:日志记录、性能统计、安全控制、事务处理、异常处理等等 */ @Component @Aspect //使用 order对多个切面进行排序,参数越小,越在前 @Order(1) public class LoggerHandlerAop { private final static Logger logger = LoggerFactory.getLogger(LoggerHandlerAop.class); /** * AOP切面中的同步问题:用于监控业务处理性能 */ ThreadLocal<Long> startTime = new ThreadLocal<>(); // ==========================================================匹配切点表达式======================================================================================== /** * 一、execution(方法表达式) * 1、匹配方法时,只能匹配到实现类,匹配到接口类不能成功 * 2、匹配方法执行 */ // 匹配cn.timebusker.service包及子包下的任何方法执行 @Pointcut(value="execution(* cn.timebusker.service.*.*(..))") public void log1() { } // 匹配任何包下的service包及子包下的任何方法执行(该模式只能匹配到一级的子包,多级子包不适用) @Pointcut(value="execution(* *..service.*.*(..))") public void log2() { } // 匹配任何包下的service包及子包下的任何方法执行(该模式能匹配到任何多级的子包下的方法执行) @Pointcut(value = "execution(* *..service..*(..))") public void log3() { } // 匹配返回值类型为java.lang.String的任何包下的service包及子包下的方法执行 @Pointcut(value = "execution(java.lang.String *..service..*(..))") public void log4() { } // 匹配返回值类型为int的任何包下的service包及子包下的方法执行 @Pointcut(value = "execution(int *..service..*(..))") public void log5() { } // 匹配任何返回值类型的cn.timebusker包及任何子包下的以add开头的参数为Strign类型的方法执行 @Pointcut(value = "execution(* cn.timebusker..add*(String))") public void log6() { } // 匹配 OR、AND @Pointcut(value = "execution(* cn.timebusker.service.*.add*(int))") public void log7() { } // 匹配 OR、AND、 @Pointcut(value = "execution(* cn.timebusker.service.*.add*(int)) OR execution(* cn.timebusker..add*(String))") public void log8() { } /** * 二、within(类型表达式) * 1、匹配类型时,只能匹配到实现类,匹配到接口类不能成功 * 2、匹配指定类型内的方法执行; */ // 匹配指定类型内的方法执行--只能匹配类型 @Pointcut(value = "within(cn.timebusker.service.order.Impl.OrderInfoServiceImpl)") public void logw1() { } // 匹配指定类型内的方法执行(包下所有的类) @Pointcut(value = "within(cn.timebusker.service.order.Impl.*)") public void logw2() { } /** * 三、this(类型全限定名) * 1、可以直接匹配接口类型完成 类型全名限定匹配 * 2、注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符 */ // 匹配指定类型内的方法执行(包下所有的类) @Pointcut(value = "this(cn.timebusker.service.order.OrderInfoService)") public void logt1() { } /** * 四、target(类型全限定名)--匹配当前目标对象类型的执行方法 * 1、可以直接匹配接口类型完成 类型全名限定匹配 * 2、注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符 */ // 匹配指定类型内的方法执行(包下所有的类) @Pointcut(value = "target(cn.timebusker.service.order.OrderInfoService)") public void logt2() { } /** * 五、args(参数类型列表)--匹配当前执行的方法传入的参数为指定类型的执行方法 * 1、注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持; * 2、args属于动态切入点,是在运行时动态匹配的,这种切入点开销非常大,非特殊情况最好不要使用; * 3、此处不作示例 */ /** * 六、@within(注解类型)--匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名; * 1、注解类型也必须是全限定类型名; */ // 匹配被org.springframework.stereotype.Service这个注解标注的类----注解标注在接口上不起作用 @Pointcut(value = "@within(org.springframework.stereotype.Service)") public void logaw1() { } // 匹配 自定义注解标注的类----注解标注在接口上不起作用 @Pointcut(value = "@within(cn.timebusker.annotation.timebuskerBean)") public void logaw2() { } /** * 七、@target(注解类型)--匹配当前目标对象类型的执行方法 * 1、目标对象持有指定的注解; * 2、注解类型也必须是全限定类型名; * 3、此处不作示例 */ /** * 八、@annotation(注解类型)--匹配当前执行方法持有指定注解的方法 * 1、注解类型也必须是全限定类型名; */ // 匹配 自定义注解标注的类----注解标注在接口的方法上不起作用 @Pointcut(value = "@annotation(cn.timebusker.annotation.timebuskerMethod)") public void logaa1() { } // ==========================================================匹配切点表达式======================================================================================== /** * 前置通知:在连接点执行前的通知,但不能阻止连接点前的执行(除非它抛出一个异常) */ // 引用多个切入点 // @Before("log6() OR log7()") // 引用单个切入点 @Before("logaa1()") public void beforeAdvice(JoinPoint point) { logger.info("SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS"); logger.info("LoggerHandlerAop.beforeAdvice...time:" + DateUtil.now()); Signature signature = point.getSignature(); logger.info("所属类名称:" + signature.getDeclaringTypeName() + "\n代理类:" + signature.getClass() + "\n方法名称:" + signature.getName() + "\n所属类:" + signature.getDeclaringType()); Object[] args = point.getArgs(); logger.info("参数是:" + JSON.toJSONString(args)); logger.info("被织入的对象是:" + point.getTarget()); logger.info("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"); } /** * 返回后通知:在连接点正常执行完后执行的通知 */ @AfterReturning("logaa1()") public void afterReturningAdvice() { logger.info("LoggerHandlerAop.afterReturningAdvice...time:" + DateUtil.now()); } /** * 抛出异常后通知:在连接节点抛出异常退出时执行的通知 */ @AfterThrowing("logaa1()") public void afterThrowingAdvice() { logger.info("LoggerHandlerAop.afterThrowingAdvice...time:" + DateUtil.now()); } /** * 后置通知:当某连接节点退出的时候执行的通知(不论是正常返回还是异常退出) */ @After("logaa1()") public void afterFinallyAdvice() { logger.info("LoggerHandlerAop.afterFinallyAdvice...time:" + DateUtil.now()); } /** * 环绕通知:包围一个连接点(join point)的通知 * * @param pjp * @return */ @Around("logaa1()") public Object aroundAdvice(ProceedingJoinPoint pjp) { logger.info("LoggerHandlerAop.aroundAdvice...time:" + DateUtil.now()); Object obj = null; try { System.out.println("MoocAspect around 1."); obj = pjp.proceed(); System.out.println("MoocAspect around 2."); } catch (Throwable e) { e.printStackTrace(); } return obj; } }