AOP面向切面编程
目录
一、AOP概述1. AOP简介AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程 可以看之前写的动态代理, https://www.cnblogs.com/mengd/p/13429797.html
动态代理的作用:
2. 如何理解AOPAOP(Aspect Orient Programming)面向切面编程
理解:
二、AOP编程术语1. 切面(Aspect)表示增强的功能, 就是一堆代码,完成某个一个功能,非业务功能 常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证 2. 连接点(JoinPoint)连接业务方法和切面的位置,就某类中的业务方法 3. 切入点(Pointcut)指多个连接点方法的集合,多个方法 4. 目标对象(Target)给哪个类的方法增加功能, 这个类就是目标对象 5. 通知(Advice)通知表示切面功能执行的时间 一个切面有三个关键的要素:
三、Aspectj对AOP的实现aop是一个规范,是动态的一个规范化,一个标准 aop的技术实现框架:
aspectJ框架实现aop有两种方式:
1. Aspectj的通知类型AspectJ 中常用的通知有五种类型
2. Aspectj的切入点表达式以上表达式共4个部分
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 注意,表达式中黑色文字表示可省略部分,各部分间用空格分开 在其中可以使用以下符号: 常用的几个:
指定切入点的位置:任意的公共方法
指定切入点的位置:任何一个以set开始的方法
指定切入点的位置:定义在service包里的任意类的任意方法
指定切入点的位置:定义在service包或者子包里的任意类的任意方法
指定所有包下的service子包下所有类中所有的方法为切入点 3. Aspectj的开发环境1. maven依赖
2. 引入AOP约束 在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的 四、AspectJ基于注解的AOP实现1. 实现步骤1. 定义业务接口与实现类 @H_502_290@package com.md.b1; /** * @author MD * @create 2020-08-09 10:55 */ public interface SomeService { void doSome(String name,Integer age); } //------------------------- package com.md.b1; /** * @author MD * @create 2020-08-09 10:55 */ // 目标类 public class SomeServiceImpl implements SomeService { @Override public void doSome(String name,Integer age) { // 给doSome方法增加一个功能,在执行之前输出时间 System.out.println("目标方法doSome()"); } } 2. 定义切面类 类中定义了若干普通方法,将作为不同的通知方法,用来增强功能 注意点:
定义方法,方法是实现切面功能的 方法的要求:
@H_502_290@package com.md.b1; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import java.util.Date; /** * @author MD * @create 2020-08-09 10:58 */ @Aspect public class MyAspect { // 前置通知,具体的在下变 @Before(value = "execution(public void com.md.b1.SomeServiceImpl.doSome(String,Integer))") public void myBefore(){ // 就是你切面要执行的功能代码 System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date()); } } 3. 定义目标对象切面类对象 还是在src/main/resources下建立applicationContext.xml 整体结构如下:
4. 注册AspectJ自动代理 在上面文件的基础上添加
<aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。 5. 测试类中的使用 @H_502_290@package com.md; import com.md.b1.SomeService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import sun.security.provider.Sun; /** * @author MD * @create 2020-08-09 15:28 */ public class MyTest01 { @Test public void test01(){ String config = "applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); // 从容器中获取目标对象,此时的目标对象是经过了aspectj修改后的代理对象 SomeService proxy = (SomeService) ac.getBean("someService"); //com.sun.proxy.$Proxy8 jdk动态代理 //System.out.println(proxy.getClass().getName()); // 通过代理的对象执行方法,实现目标方法执行,增强了功能 proxy.doSome("张三",19); // 前置通知,切面功能:在目标方法之前输出时间:Sun Aug 09 15:33:24 CST 2020 // 目标方法doSome() } } 2. @Before前置通知在目标方法执行之前执行 被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式 通过该参数,可获取切入点表达式、方法签名、目标对象等
@H_502_290@@Aspect public class MyAspect { // @Before():前置通知注解 // 属性:value 是切入点表达式,表示切面功能执行的位置 // 位置:在方法的上面 // 特点: // 1. 在目标方法之前执行 // 2. 不会改变目标方法的执行结果 // 3. 不会影响目标方法的执行 // @Before(value = "execution(public void com.md.b1.SomeServiceImpl.doSome(String,Integer))") // public void myBefore(){ // // 就是你切面要执行的功能代码 // System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date()); // } // @Before(value = "execution( * *..SomeServiceImpl.do*(..))") // public void myBefore(){ // // 就是你切面要执行的功能代码 // System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date()); // } /** * 指定通知方法中的参数 : JoinPoint * JoinPoint:业务方法,要加入切面功能的业务方法 * 作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。 * 如果你的切面功能中需要用到方法的信息,就加入JoinPoint. * 这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数 */ @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))") public void myBefore(JoinPoint jp){ // 获取方法的完整定义 System.out.println("方法的定义:"+jp.getSignature()); System.out.println("方法的名称:"+jp.getSignature().getName()); // 获取方法的实参 Object[] args = jp.getArgs(); for (Object arg:args){ System.out.println("参数:"+arg); } // 方法的定义:void com.md.b1.SomeService.doSome(String,Integer) // 方法的名称:doSome // 参数:张三 // 参数:19 // 就是你切面要执行的功能代码 System.out.println("前置通知,切面功能:在目标方法之前输出时间:"+new Date()); } } 3. @AfterReturning后置通知在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值 该注解的 returning 属性就是用于指定接收方法返回值的变量名的 所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型 增加接口的方法 @H_502_290@public interface SomeService { void doSome(String name,Integer age); String doOther(String name,Integer age); } //-------------------------------------------------- // 目标类 public class SomeServiceImpl implements SomeService { @Override public void doSome(String name,Integer age) { // 给doSome方法增加一个功能,在执行之前输出时间 System.out.println("目标方法doSome()"); } @Override public String doOther(String name,Integer age) { System.out.println("目标方法doOther()"); return "a"; } } 定义切面 @H_502_290@@Aspect public class MyAspect { /** * 后置通知定义方法,方法是实现切面功能的。 * 方法的定义要求: * 1.公共方法 public * 2.方法没有返回值 * 3.方法名称自定义 * 4.方法有参数的,推荐是Object ,参数名自定义 */ /** * @AfterReturning:后置通知 * 属性:1.value 切入点表达式 * 2.returning 自定义的变量,表示目标方法的返回值的。 * 自定义变量名必须和通知方法的形参名一样。 * 位置:在方法定义的上面 * 特点: * 1。在目标方法之后执行的。 * 2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能 * Object res = doOther(); * 3. 可以修改这个返回值,但不影响最后的调用结果 * * 后置通知的执行 * Object res = doOther(); * 参数传递: 传值, 传引用 * myAfterReturing(res); * System.out.println("res="+res) * */ @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res") public void myAfterReturing(Object res){ if (res.equals("a")){ // 你可以做一些功能 System.out.println("登陆成功"); }else{ System.out.println("登陆失败"); } // Object res:是目标方法执行后的返回值,可以根据返回值做切面功能处理 System.out.println("后置通知:获取的返回值是:"+res); // 修改目标方法的返回值,是否影响最后的方法调用结果 // 无影响, if (res != null){ res = "hello Aspectj"; } } } 4. @Around环绕通知在目标方法执行之前之后执行。 被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。 接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。 若目标方法有返回值,则该方法的返回值就是目标方法的返回值。 最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行 首先增加方法和实现 @H_502_290@public interface SomeService { void doSome(String name,Integer age); String doFirst(String name,Integer age); } //------------------------------------------- // 目标类 public class SomeServiceImpl implements SomeService { @Override public void doSome(String name,Integer age) { // 给doSome方法增加一个功能,在执行之前输出时间 System.out.println("目标方法doSome()"); } @Override public String doOther(String name,Integer age) { System.out.println("目标方法doOther()"); return "a"; } @Override public String doFirst(String name,Integer age) { System.out.println("目标方法doFirst()"); return "doFirst"; } } 切面类 @H_502_290@@Aspect public class MyAspect { /** * 环绕通知方法的定义格式 * 1.public * 2.必须有一个返回值,推荐使用Object * 3.方法名称自定义 * 4.方法有参数,固定的参数 ProceedingJoinPoint */ /** * @Around: 环绕通知 * 属性:value 切入点表达式 * 位置:在方法定义的上面 * 特点: * 1.它是功能最强的通知 * 2.在目标方法的前和后都能增强功能。 * 3.控制目标方法是否被调用执行 * 4.修改原来的目标方法的执行结果。 影响最后的调用结果 * * 环绕通知,等同于jdk动态代理的,InvocationHandler接口 * * 参数: ProceedingJoinPoint 就等同于 Method * 作用:执行目标方法的 * 返回值: 就是目标方法的执行结果,可以被修改。 * * 环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务 */ // @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))") // public Object myAround(ProceedingJoinPoint pjp) throws Throwable { // // Object result = null; // // // System.out.println("环绕通知,在目标方法之前加入通知:现在时间:"+new Date()); // // // 1. 目标方法调用,等同于method.invoke(); 在这里等同于Object result = doFirst(); // result = pjp.proceed(); // // // 2. 在目标方法前后加入功能 // System.out.println("环绕通知,在目标方法之后提交事务"); // // // 3. 返回目标方法的执行结果 // return result; // // } @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))") public Object myAround(ProceedingJoinPoint pjp) throws Throwable { // 可以获取到调用方法的参数 String name = ""; // 获取第一个参数的值 Object args[] = pjp.getArgs(); if (args!=null && args.length > 1){ Object arg = args[0]; name = (String) arg; } // 实现环绕通知 Object result = null; System.out.println("环绕通知,在目标方法之前加入通知:现在时间:"+new Date()); // 1. 目标方法调用,等同于method.invoke(); 在这里等同于Object result = doFirst(); if ("张三".equals(name)){ // 符合条件,调用目标方法 result = pjp.proceed(); } // 2. 在目标方法前后加入功能 System.out.println("环绕通知,在目标方法之后提交事务"); // 还可以修改目标方法的执行结果,影响方法最后的调用结果 if (result != null){ result = "修改了"; } // 3. 返回目标方法的执行结果 return result; } } 测试: @H_502_290@public class MyTest03 { @Test public void test01(){ String config = "applicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(config); // 从容器中获取目标对象 SomeService proxy = (SomeService) ac.getBean("someService"); // 通过代理的对象执行方法,实现目标方法执行,增强了功能 String str = proxy.doFirst("张三",20); System.out.println(str); } } //环绕通知,在目标方法之前加入通知:现在时间:Mon Aug 10 20:52:07 CST 2020 // 目标方法doFirst() // 环绕通知,在目标方法之后提交事务 // 修改了 5. @AfterThrowing 异常通知在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象 相当于try-catch中catch里面执行的 增加业务方法 @H_502_290@public interface SomeService { void doSome(String name,Integer age); void doSecond(); } //实现类----------------------------------------- @Override public void doSecond() { System.out.println("执行业务方法doSecond()" + (10/0)); } 切面类: @H_502_290@@Aspect public class MyAspect { /** * 异常通知方法的定义格式 * 1.public * 2.没有返回值 * 3.方法名称自定义 * 4.方法有个一个Exception, 如果还有是JoinPoint,*/ /** * @AfterThrowing:异常通知 * 属性:1. value 切入点表达式 * 2. throwinng 自定义的变量,表示目标方法抛出的异常对象。 * 变量名必须和方法的参数名一样 * 特点: * 1. 在目标方法抛出异常时执行的 * 2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。 * 如果有异常,可以发送邮件,短信进行通知 * * 执行就是: * try{ * SomeServiceImpl.doSecond(..) * }catch(Exception e){ * myAfterThrowing(e); * } */ @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex") public void myAfterThrowing(Exception ex) { System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage()); //发送邮件,短信,通知开发人员 } } 6. @After最终通知无论目标方法是否抛出异常,该增强均会被执行 相当于try-catch-finally中finally里面执行的 增加方法及实现 @H_502_290@public interface SomeService { void doSome(String name,Integer age); void doSecond(); void doThird(); } //------------------------ @Override public void doThird() { System.out.println("执行业务方法doThird()"+ (10/0)); } 切面类 @H_502_290@@Aspect public class MyAspect { /** * 最终通知方法的定义格式 * 1.public * 2.没有返回值 * 3.方法名称自定义 * 4.方法没有参数, 如果还有是JoinPoint,*/ /** * @After :最终通知 * 属性: value 切入点表达式 * 位置: 在方法的上面 * 特点: * 1.总是会执行 * 2.在目标方法之后执行的 * * try{ * SomeServiceImpl.doThird(..) * }catch(Exception e){ * * }finally{ * myAfter() * } * */ @After(value = "execution(* *..SomeServiceImpl.doThird(..))") public void myAfter(){ System.out.println("执行最终通知,总是会被执行的代码"); //一般做资源清除工作的。 } } 7. @Pointcut定义切入点当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法 五、总结前面的概念有些绕,看了代码就比较清晰了,感觉和python的装饰器很像,只不过py的没有这么绕,就是为已经存在的对象添加额外的功能 总的来说就是在一个方法前或一个方法后执行一些通用的方法,提高效率,把那些业务的方法写一块,那些非业务的方法或那些业务方法经常使用的方法写成切面类,使用方便还便于管理 1. 使用aspectj框架实现aop使用aop:目的是给已经存在的一些类和方法增加额外的功能,前提是不改变原来类的代码
2. Review强制使用cglib代理 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |