AOP (Aspect Oriented Programming),翻译过来就是 面向切面编程。AOP是一种编程思想,基于OOP基础之上的新的编程思想;指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式。 场景:我们使用计算器运行计算器方法的时候进行日志记录,传统的我们有以下几种方法 定义一个LogUtils 用来作为日志类 减少耦合 通过以上代码的输出结果和编码,我们可以发现 方式一方式二: 直接将日志写在方法内部;不推荐,维护麻烦 我们希望的是: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样的好是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能. 举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子 如果目标对象实现了接口,默认情况下会采用JDK的动态代理来实现AOP,废话不多说上代码 此时我们方法的执行就呢,就不要自己去创建对象去调用了,而是交给我们的代理对象 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。 导入jar包 **前置通知[Before advice]:**在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 … 标识切点选择任意的play方法,无论该方法的入参是什么。 举例: 我们先上代码,围着代码来分析 在配置文件上加上 当然这里也需要注解一个小细节:从ioc容器中获取目标对象,如果想要用类型,一定要用它的接口类型,不要用本类 如你看到的,上面我们写的多个通知使用了相同的切点表达式,对于像这样频繁出现的相同的表达式,比如我们只切add方法 sub()方法不切,这时候我们就要考虑一个问题了,修改起来是不是很麻烦,我们可以使用 @Pointcut注解声明切点表达式,然后使用表达式, 修改代码如下: 程序运行结果没有变化。 声明了一个切点表达式,该方法 point 的内容并不重要,方法名也不重要,实际上它只是作为一个标识,供通知使用。 我们给通知方法的输出加上方法名和计算结果的值 上代码: 输出结果: 看到了这里你是否发现还有一个问题,对,我们想的一样,那就是通知方法的执行顺序: 从输出结果可以看出: @Around: 环绕:是psring中强大的通知 四合一通知就是环绕通知 我们发现我们的目标方法被阻塞了,这时我们就需要在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法。 Object proceed= pjp.proceed(args); 动态代理的method.invoke() 帮我们执行目标方法 好处: AspectLogUtils LogUtils 输出: 咦~我们发现了什么 AspectLogUtils前置通知先执行,却最后出去,后进来的LogUtils却先出去这是为什么?来我们画个图 我们在以上代码添加上: 输出: 分析: LogUtils前置通知进入目标方法,接着AspectLogUtils进入目标方法,目标方法要先执行,LogUtils有环绕通知,我们的环绕通知要放行,以前的思路,在目标方法执行后切到环绕通知去执行,但是环绕通知在切目标方法之前,AspectLogUtils已经进来了,跟LogUtils已经没关系了,所以AspectLogUtils已经在执行目标方法了,目标方法才能出去,LogUtils才能再出去 本文简单记录了 AOP 的编程思想,然后介绍了 Spring 中 AOP 的相关概念 相比于 AspectJ 的面向切面编程,Spring AOP 也有一些局限性,但是已经可以解决开发中的绝大多数问题了,如果确实遇到了 Spring AOP 解决不了的场景,如有不足,请指出
一、AOP——另一种编程思想
1.1什么是AOP
在学习AOP前我们需要了解什么是动态代理
1.1.1直接写在方法内部
方式一
定义计算器接口
public interface Calculator { int add(int i, int k); int sub(int i, int k); }
实现计算器接口 并写上日志记录
public class CalculatorImpl implements Calculator { public int add(int i, int k) { System.out.println("【add】方法开始了,它是用的参数是【" + i + "," + k + "】"); int result = i + k; System.out.println("【add】方法结束了,它是结果是【" + result + "】"); return result; } public int sub(int i, int k) { System.out.println("【sub】方法开始了,它是用的参数是【" + i + "," + k + "】"); int result = i - k; System.out.println("【sub】方法结束了,它是结果是【" + result + "】"); return result; } }
测试方法
@org.junit.Test public void calTest() { Calculator calculator=new CalculatorImpl() ; calculator.add(1,3); }
输出:
【add】方法开始了,它是用的参数是【1,3】 【add】方法结束了,它是结果是【4】
方式二
LogUtils
public class LogUtils { public static void logStart(Object... args) { System.out.println("【xxx】方法开始执行了,参数列表【" + Arrays.asList(args) + "】"); } public static void logEnd(Object result) { System.out.println("【xxx】执行结果是---" + result); } }
修改CalculatorImpl 以add方法为例
public class CalculatorImpl implements Calculator { public int add(int i, int k) { LogUtils.logStart(i, k); int result = i + k; LogUtils.logEnd(result); return result; } }
测试方法不变输出结果:
【xxx】方法开始执行了,参数列表【[1, 3]】 【xxx】执行结果是---4
总结
//核心方法 public int add(int i, int k) { int result = i + k; return result; }
这时我们就希望有一张机制:动态代理
什么是动态代理呢,就拿jdk中自带的动态代理来说吧
JDK自带的动态代理
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法在Spirng当中动态代理的使用
CalculatorImpl实现类
public class CalculatorImpl implements Calculator { public int add(int i, int k) { int result = i + k; return result; } public int sub(int i, int k) { int result = i - k; return result; } }
二、动态代理
1.首先定义logUtils类
public class LogUtils { public static void logStart(Method method, Object... args) { System.out.println("【" + method.getName() + "】方法开始执行了,参数列表【" + Arrays.asList(args) + "】"); } public static void logReturn(Method method, Object result) { System.out.println("【" + method.getName() + "】执行结果是---" + result); } public static void logEnd(Method method) { System.out.println("【" + method.getName() + "】执行结束"); } public static void logException(Method method, Exception e) { System.err.println("【" + method.getName() + "】出现异常,异常信息:" + e); } }
2.定义CalcutorProxy类
public class CalculatorProxy { public static Calculator getProxy(final Calculator calculator) { /** *调用处理器 * @param proxy 代理对象 给jdk对象使用 任何时候不要使用 * @param method 当前要执行的目标对象方法 * @param args 方法调用时外界传入的参数值 * @return * @throws Throwable */ InvocationHandler h = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { //目标方法执行前 LogUtils.logStart(method, args); //利用反射执行目标方法 result = method.invoke(calculator, args); //目标方法执行后 LogUtils.logReturn(method, result); } catch (Exception e) { //异常 LogUtils.logException(method, e); } finally { //结束 LogUtils.logEnd(method); } //反正值必须返回 外界才能拿到 return result; } }; //定义了代理类的ClassLoder ClassLoader loader = calculator.getClass().getClassLoader(); //代理类实现的接口列表 Class<?>[] interfaces = calculator.getClass().getInterfaces(); //Proxy为目标对象创建代理对象 Object proxy = Proxy.newProxyInstance(loader, interfaces, h); return (Calculator)proxy; } }
3.测试方法
@org.junit.Test public void calTest() { //计算器对象 Calculator calculator = new CalculatorImpl(); //如果拿到了这个对象的代理对象,代理对象执行加减乘除 Calculator proxy = CalculatorProxy.getProxy(calculator); proxy.sub(1, 2); System.out.println("calculator的接口" + Arrays.asList(calculator.getClass().getInterfaces())); //代理对象和被代理对象唯一产生的关联就是实现了同一个接口 System.out.println("代理对象的接口" + Arrays.asList(proxy.getClass().getInterfaces())); }
输出结果:
【sub】方法开始执行了,参数列表【[1, 2]】 【sub】执行结果是----1 【sub】执行结束 calculator的接口[interface com.xiamu.cal.Calculator] 代理对象的接口[interface com.xiamu.cal.Calculator]
通过上面案例,我们发现动态代理已经面向切面了,但它对于我们的Spring来说还是有缺点的
所以呢,我们的spring就实现了AOP,底层动态代理
(spring简化了面向切面编程)三、AOP
AOP术语
连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
切点(PointCut): 可以插入增强处理的连接点。
切面(Aspect): 切面是通知和切点的结合。
引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。3.1.1
3.1.2写配置文件
spring aop通知(advice)分成五类:
**正常返回通知[After returning advice]:**在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
**异常返回通知[After throwing advice]:**在连接点抛出异常后执行。
**返回通知[After (finally) advice]:**在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
**环绕通知[Around advice]:**环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义切入点表达式
多个匹配之间我们可以使用链接符 &&、||、!来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。
限定该切点仅匹配的包是 com.xiamu.cal.impl.CalculatorImpl,可以使用
execution(* com.xiamu.cal.impl.CalculatorImpl.add(…)) && within(com.xiamu.cal.impl.CalculatorImpl.)
在切点中选择 bean,可以使用
execution( com.xiamu.cal.impl.CalculatorImpl.add(…)) && bean(calculatorImpl)先定义切面类 要注意个细节 必须给切面类加上@Aspect注解 否则是不生效的
Component @Aspect public class AspectLogUtils { //在方法运行前执行 想在执行目标方法之前运行,写入切入点表达式 //execution (访问权限符 返回值类型 方法签名) //joinPoint 封装了当前目标方法的详细信息 @Before("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logStart(JoinPoint joinPoint) { System.out.println("xxx方法开始执行了,参数列表【" +Arrays.asList(joinPoint.getArgs())+ "】"); } //通知方法会在目标方法返回后调用 @AfterReturning("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logReturn(){ System.out.println("xxx方法开始执行完成,计算结果是"); } //通知方法会在目标方法抛出异常后调用 @AfterThrowing("execution(public int com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logException() { System.out.println("xxx抛出了异常---"); } //方法结束时候执行 @After("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void logAfter() { System.out.println("xxx执行结束"); } //环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义 }
当然加了注解后spring是不知道的 这时就由我们来告诉Spring 开启aop功能了
<context:component-scan base-package="com.xiamu"></context:component-scan> <!--开启注解的aop功能,aop名称空间--> <aop:aspectj-autoproxy />
测试类
ApplicationContext ioc = new ClassPathXmlApplicationContext("application.xml"); Calculator calculator = (Calculator)ioc.getBean(Calculator.class); calculator.add(1, 2); calculator.sub(1,2);
输出
xxx方法开始执行了,参数列表【[1, 2]】 xxx执行结束 xxx方法开始执行完成,计算结果是 xxx方法开始执行了,参数列表【[1, 2]】 xxx执行结束 xxx方法开始执行完成,计算结果是
@Component @Aspect public class AspectLogUtils { /** * 抽取可重用的切入点表达式 * 1.随便生命一个没有实现的返回void的空方法 * 2.给切入点表达式加上@Pointcut注解 */ @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point(){ } //在方法运行前执行 想在执行目标方法之前运行,写入切入点表达式 //execution (访问权限符 返回值类型 方法签名) //joinPoint 封装了当前目标方法的详细信息 @Before("point()") public void logStart(JoinPoint joinPoint) { System.out.println("xxx方法开始执行了,参数列表【" +Arrays.asList(joinPoint.getArgs())+ "】"); } //通知方法会在目标方法返回后调用 @AfterReturning("point()") public void logReturn(){ System.out.println("xxx方法开始执行完成,计算结果是"); } //通知方法会在目标方法抛出异常后调用 @AfterThrowing("point()") public void logException() { System.out.println("xxx抛出了异常---"); } //方法结束时候执行 @After("point()") public void logAfter() { System.out.println("xxx执行结束"); } }
这里,我们使用@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))") public void point(){}
此时细心的你肯定发现了把 通知方法没有输出方法的参数 和计算的结果,这时我们就需要注解来处理参数了
通过注解处理通知中的参数
@Component @Aspect public class AspectLogUtils { /** * 抽取可重用的切入点表达式 * 1.随便生命一个没有实现的返回void的空方法 * 2.给切入点表达式加上@Pointcut注解 */ @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point(){ } @Before("point()") public void logStart(JoinPoint joinPoint) { System.out.println( "【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】"); } //通知方法会在目标方法返回后调用 @AfterReturning(value = "point()",returning = "result") public void logReturn( JoinPoint joinPoint,Object result) { System.out.println("【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是"+result); } //通知方法会在目标方法抛出异常后调用 @AfterThrowing("point()") public void logException( JoinPoint joinPoint) { System.out.println("【" + joinPoint.getSignature().getName() + "】抛出了异常---"); } //方法结束时候执行 @After("point()") public void logAfter(JoinPoint joinPoint) { System.out.println("【" + joinPoint.getSignature().getName() + "】执行结束"); } //环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义 }
【add】方法开始执行了,参数列表【[1, 2]】 【add】执行结束 【add】方法开始执行完成,计算结果是3 【sub】方法开始执行了,参数列表【[1, 2]】 【sub】执行结束-- 【sub】方法开始执行完成,计算结果是-1
通知方法执行顺序
不是5种通知吗 我们只写了4种?别急环绕通知这就来了它是spring通知中强大的通知
@Around:环绕:动态代理try{ //前置通知 method.invoke(obj,args); //返回通知 }catch(e){ //异常通知 }finally{ //后置通知 }
修改源码增加环绕通知
@Component @Aspect public class AspectLogUtils { /** * 抽取可重用的切入点表达式 * 1.随便生命一个没有实现的返回void的空方法 * 2.给切入点表达式加上@Pointcut注解 */ @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point(){ } @Before("point()") public void logStart(JoinPoint joinPoint) { System.out.println( "【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】"); } //通知方法会在目标方法返回后调用 @AfterReturning(value = "point()",returning = "result") public void logReturn( JoinPoint joinPoint,Object result) { System.out.println("【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是"+result); } //通知方法会在目标方法抛出异常后调用 @AfterThrowing("point()") public void logException( JoinPoint joinPoint) { System.out.println("【" + joinPoint.getSignature().getName() + "】抛出了异常---"); } //方法结束时候执行 @After("point()") public void logAfter(JoinPoint joinPoint) { System.out.println("【" + joinPoint.getSignature().getName() + "】执行结束"); } //环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义 @Around("point()")public void logAround(){ System.out.println("环绕哈哈"); } }
输出:
环绕哈哈 【add】执行结束 【add】方法开始执行完成,计算结果是null
修改源码
@Around("point()") public Object logAround(ProceedingJoinPoint pjp) throws Throwable { Object proceed = null; //方法名 String name = pjp.getSignature().getName(); try { //参数 Object[] args = pjp.getArgs(); //@Before System.out.println("【环绕通知】" + name + "方法开始"); //利用反射调用目标方法,等同于动态代理的method.invoke() //推进目标方法的执行 proceed = pjp.proceed(args); //@AfterReturning System.out.println("【环绕通知】" + name + "方法返回,返回值:" + proceed); } catch (Exception e) { //@AfterThrowing System.out.println("【环绕通知】" + name + "异常,异常信息:" + e); } finally { //@After System.out.println("【环绕通知】" + name + "方法结束:" ); } //反射后调用的值也一定返回出去 return proceed; }
输出:
【环绕通知】add方法开始 【环绕通知】add方法返回,返回值:3 【环绕通知】add方法结束: 【环绕通知】sub方法开始 【环绕通知】sub方法返回,返回值:-1 【环绕通知】sub方法结束:
在来看一组多切面运行
@Component @Aspect public class AspectLogUtils { /** * 抽取可重用的切入点表达式 * 1.随便生命一个没有实现的返回void的空方法 * 2.给切入点表达式加上@Pointcut注解 */ @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point() { } @Before("point()") public void logStart(JoinPoint joinPoint) { System.out.println( "[AspectLogUtils-前置]【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】"); } //通知方法会在目标方法返回后调用 @AfterReturning(value = "point()", returning = "result") public void logReturn(JoinPoint joinPoint, Object result) { System.out.println("[AspectLogUtils-返回]【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是" + result); } //通知方法会在目标方法抛出异常后调用 @AfterThrowing("point()") public void logException(JoinPoint joinPoint) { System.out.println("[AspectLogUtils-异常]【" + joinPoint.getSignature().getName() + "】抛出了异常---"); } //方法结束时候执行 @After("point()") public void logAfter(JoinPoint joinPoint) { System.out.println("[AspectLogUtils-结束]【" + joinPoint.getSignature().getName() + "】执行结束"); } }
@Component @Aspect public class LogUtils { /** * 抽取可重用的切入点表达式 * 1.随便生命一个没有实现的返回void的空方法 * 2.给切入点表达式加上@Pointcut注解 */ @Pointcut("execution(* com.xiamu.cal.impl.CalculatorImpl.*(int,int))") public void point() { } @Before("point()") public void logStart(JoinPoint joinPoint) { System.out.println( "[LogUtils-前置]【" + joinPoint.getSignature().getName() + "】方法开始执行了,参数列表【" + Arrays.asList(joinPoint.getArgs()) + "】"); } //通知方法会在目标方法返回后调用 @AfterReturning(value = "point()", returning = "result") public void logReturn(JoinPoint joinPoint, Object result) { System.out.println("[LogUtils-返回]【" + joinPoint.getSignature().getName() + "】方法开始执行完成,计算结果是" + result); } //通知方法会在目标方法抛出异常后调用 @AfterThrowing("point()") public void logException(JoinPoint joinPoint) { System.out.println("[LogUtils-异常]【" + joinPoint.getSignature().getName() + "】抛出了异常---"); } //方法结束时候执行 @After("point()") public void logAfter(JoinPoint joinPoint) { System.out.println("[LogUtils-结束]【" + joinPoint.getSignature().getName() + "】执行结束"); } }
[AspectLogUtils-前置]【add】方法开始执行了,参数列表【[1, 2]】 [LogUtils-前置]【add】方法开始执行了,参数列表【[1, 2]】 [LogUtils-结束]【add】执行结束 [LogUtils-返回]【add】方法开始执行完成,计算结果是3 [AspectLogUtils-结束]【add】执行结束 [AspectLogUtils-返回]【add】方法开始执行完成,计算结果是3
总结:
我们再把多切面运行加上LogUtils环绕通知看看运行结果
@Around("point()") public Object logAround(ProceedingJoinPoint pjp) throws Throwable { Object proceed = null; //方法名 String name = pjp.getSignature().getName(); try { //参数 Object[] args = pjp.getArgs(); //@Before System.out.println("【环绕通知】" + name + "方法开始"); //利用反射调用目标方法,等同于动态代理的method.invoke() //推进目标方法的执行 proceed = pjp.proceed(args); //@AfterReturning System.out.println("【环绕通知】" + name + "方法返回,返回值:" + proceed); } catch (Exception e) { //@AfterThrowing System.out.println("【环绕通知】" + name + "异常,异常信息:" + e); } finally { //@After System.out.println("【环绕通知】" + name + "方法结束:" ); } //反射后调用的值也一定返回出去 return proceed; }
【环绕通知】add方法开始 [LogUtils-前置]【add】方法开始执行了,参数列表【[1, 2]】 [AspectLogUtils-前置]【add】方法开始执行了,参数列表【[1, 2]】 [AspectLogUtils-结束]【add】执行结束 [AspectLogUtils-返回]【add】方法开始执行完成,计算结果是3 【环绕通知】add方法返回,返回值:3 【环绕通知】add方法结束: [LogUtils-结束]【add】执行结束 [LogUtils-返回]【add】方法开始执行完成,计算结果是3
还有基于配置的AOP这里就不一一细述了
Aop应用场景
总结
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算