Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。(业务层接口中所有的方法)
Pointcut(切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义(被增强的方法)所有的切入点都是连接点。
Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知,通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象):
代理的目标对象(被代理对象)
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类(return返回的就是代理对象)
Aspect(切面):
是切入点和通知(引介)的结合。
a、开发阶段(我们做的) 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。 在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。 b、运行阶段(Spring 框架完成的) Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对 象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
1、导入jar包
2、创建配置文件导入aop的约束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
3、在配置好spring的IOC后,开始配置通知类
<!--配置spring的IOC,把service对象配置进来--> <bean id="accountService" class="com.itheima.service.Impl.AccountServiceImpl"></bean> <!--配置Logger类(通知类)--> <bean id="logger" class="com.itheima.utils.Logger"></bean>
4、配置AOP
<!--配置AOP--> <aop:config> <!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用 它还可以写在aop:aspect标签外面,此时就变成了所有切面可用(但要写在aop:aspect标签上面) --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.Impl.*.*(..))"/> <aop:aspect id="logAdvice" ref="logger"> <!--配置通知的类型,并且建立通知方法和切入点方法的关联--> <!--配置前置通知:在切入点方法执行之前执行--> <!-- <aop:before method="bforePrintLog" pointcut-ref="pt1"></aop:before>--> <!--配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个--> <!-- <aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>--> <!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--> <!-- <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>--> <!--配置最终通知:无论切入点方法是否正常执行它都会在其后执行--> <!--<aop:after method="afterFinalPrintLog" pointcut-ref="pt1"></aop:after>--> <!--配置环绕通知--> <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config>
spring中基于XML的AOP配置步骤
把通知bean(Logger)也交给spring来管理;
使用aop:config标签表明配置开始AOP配置;
使用aop:aspect标签表明配置切面;
id属性:是给切面提供一个唯一标识;
ref属性:是指定通知类bean的id。
在aop:aspect标签的内部使用对应标签来配置通知的类型;
我们现在示例是让printLog方法在切入点方法执行之前执行,所以是前置通知,
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
流程解释:
切入点表达式的写法: 关键字:execution(表达式) 表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表) 标准的表达式写法: public void com.itheima.service.Impl.AccountServiceImpl.save() 访问修饰符可以省略 void com.itheima.service.Impl.AccountServiceImpl.save() 返回值可以使用通配符,表示任意返回值 * com.itheima.service.Impl.AccountServiceImpl.save() 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. * *.*.*.*.AccountServiceImpl.save() 包名也可以使用..表示当前包及其子包 * *..AccountServiceImpl.save() 类名和方法名都可以使用*来实现通配 * *..*.() 参数列表: 可以直接写数据类型: 基本类型写名称 int 引用类型写包名.类名的方式 java.lang.String 可以使用通配符表示任意类型,但是必须有参数 可以使用..表示有无参数均可,有参数可以是任意类型 全通配写法: * *..*.*(..) 实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法 * com.itheima.service.Impl.*.*(..)
配置前置通知:在切入点方法执行之前执行 <aop:before method=“bforePrintLog” pointcut-ref=“pt1”>< /aop:before> 配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个 <aop:after-returning method=“afterReturnPrintLog” pointcut-ref=“pt1”>< /aop:after-returning> 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 <aop:after-throwing method=“afterThrowingPrintLog” pointcut-ref=“pt1”>< /aop:after-throwing> 配置最终通知:无论切入点方法是否正常执行它都会在其后执行 <aop:after method=“afterFinalPrintLog” pointcut-ref=“pt1”>< /aop:after>
<!--配置AOP--> <aop:config> <!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用 它还可以写在aop:aspect标签外面,此时就变成了所有切面可用(但要写在aop:aspect标签上面) --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.Impl.*.*(..))"/> <aop:aspect id="logAdvice" ref="logger"> <!--配置通知的类型,并且建立通知方法和切入点方法的关联--> <!--配置前置通知:在切入点方法执行之前执行--> <aop:before method="bforePrintLog" pointcut-ref="pt1"></aop:before> <!--配置后置通知:在切入点方法正常执行之后执行。它和异常通知永远只能执行一个--> <aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个--> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知:无论切入点方法是否正常执行它都会在其后执行--> <aop:after method="afterFinalPrintLog" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config>
之前能用,改个位置就不能用,要考虑约束(顺序和位置)
<!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用 它还可以写在aop:aspect标签外面,此时就变成了所有切面可用(但要写在aop:aspect标签上面) --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.Impl.*.*(..))"/>
环绕通知
问题:
当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。(即发现执行as.save()方法后只输出了printLog开始记录日志了。。。。,未输出save方法中的语句)
分析:
通过对比动态代理中的环挠通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
解决:
Spring框架为我们提供了一个接口,ProceedingJoinPoint。 该接口有个方法proceed(), 此方法就相当于明确调用切入点方法 。 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会 为我们提供该接口的实现类供我们使用。
spring中的环绕通知:
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
代码演示:
package com.itheima.utils; import org.aspectj.lang.ProceedingJoinPoint; /* * 用于记录日志的工具类,它里面提供了公共的代码 * */ public class Logger { /* * 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法) * 一、可以使用动态代理的方式,创建一个代理对象,在调用业务层方法之前调用printLog方法 * 二、使用spring配置 * */ /*前置通知*/ public void bforePrintLog(){ System.out.println("前置通知的bforePrintLog方法开始记录日志了。。。。"); } /*后置通知*/ public void afterReturnPrintLog(){ System.out.println("后置通知的afterReturnPrintLog方法开始记录日志了。。。。"); } /*异常通知*/ public void afterThrowingPrintLog(){ System.out.println("异常通知的afterThrowingPrintLog方法开始记录日志了。。。。"); } /*最终通知*/ public void afterFinalPrintLog(){ System.out.println("最终通知的afterFinalPrintLog方法开始记录日志了。。。。"); } /* * 环绕通知*/ public Object aroundPrintLog(ProceedingJoinPoint pjp){ Object rtValue=null; try { Object[] args = pjp.getArgs();//获取方法所需参数 System.out.println("printLog开始记录日志了。。。。前置"); rtValue= pjp.proceed(args); //明确调用业务层方法(切入点方法) System.out.println("printLog开始记录日志了。。。。后置"); return rtValue; } catch (Throwable t) { System.out.println("printLog开始记录日志了。。。。异常"); throw new RuntimeException(t); }finally { System.out.println("printLog开始记录日志了。。。。最终"); } } }
1、配置文件的约束修改以及相关配置
<!--配置spring开启注解的AOP的支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2、在配置好spring的Ioc后,在通知类上使用@Aspect注解声明为切面
@Component("logger") @Aspect //表示当前类是一个切面类 public class Logger {
3、在增强的方法上使用注解配置通知
@Before 作用: 把当前方法看成是前置通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterReturning 作用: 把当前方法看成是后置通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing 作用: 把当前方法看成是异常通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After 作用: 把当前方法看成是最终通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@Around 作用: 把当前方法看成是环绕通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
package com.itheima.utils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /* * 用于记录日志的工具类,它里面提供了公共的代码 * */ @Component("logger") @Aspect //表示当前类是一个切面类 public class Logger { /* * 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法) * 一、可以使用动态代理的方式,创建一个代理对象,在调用业务层方法之前调用printLog方法 * 二、使用spring配置 * */ @Pointcut("execution(* com.itheima.service.Impl.*.*(..))") public void pt1(){ } @Before("pt1()") /*前置通知*/ public void bforePrintLog(){ System.out.println("前置通知的bforePrintLog方法开始记录日志了。。。。"); } @AfterReturning /*后置通知*/ public void afterReturnPrintLog(){ System.out.println("后置通知的afterReturnPrintLog方法开始记录日志了。。。。"); } @AfterThrowing /*异常通知*/ public void afterThrowingPrintLog(){ System.out.println("异常通知的afterThrowingPrintLog方法开始记录日志了。。。。"); } @After("pt1()") /*最终通知*/ public void afterFinalPrintLog(){ System.out.println("最终通知的afterFinalPrintLog方法开始记录日志了。。。。"); } /* * 环绕通知*/ // @Around("pt1()") public Object aroundPrintLog(ProceedingJoinPoint pjp){ Object rtValue=null; try { Object[] args = pjp.getArgs();//获取方法所需参数 System.out.println("printLog开始记录日志了。。。。前置"); rtValue= pjp.proceed(args); //明确调用业务层方法(切入点方法) System.out.println("printLog开始记录日志了。。。。后置"); return rtValue; } catch (Throwable t) { System.out.println("printLog开始记录日志了。。。。异常"); throw new RuntimeException(t); }finally { System.out.println("printLog开始记录日志了。。。。最终"); } } }
使用注解在四种通知的调用顺序上会有差错,但是自己写的环绕通知没有
引用时带上方法名和小括号()
切入点表达式注解
@Pointcut 作用: 指定切入点表达式 属性 value:指定表达式的内容
使用方法:
@Pointcut("execution(* com.itheima.service.Impl.*.*(..))") public void pt1(){ } @Before("pt1()") //注意:千万别忘了写括号 /*前置通知*/ public void bforePrintLog(){ System.out.println("前置通知的bforePrintLog方法开始记录日志了。。。。"); }
5 不使用 XML 的配置方式
@Configuration @ComponentScan(basePackages="com.itheima") @EnableAspectJAutoProxy public class SpringConfiguration { }
因为转账中出现的错误,我们加上了事务控制,但是这样业务层的代码太臃肿,为了解决这个臃肿:把这些重复的代码抽取出来再调用,但是这样会出现方法之间的依赖。
为了将这些重复的代码在方法执行时加进去,想到了动态代理。因为动态代理的特点就是:在不改变源码的基础上,对已有方法进行增强。
所以动态代理的回顾及子类动态代理,上面都是思想和实现的过程,下面的是以后经常用到
最后用spring来实现对方法的增强,
spring的AOP:
创建代理对象都是固定的,怎么对方法进行增强呢?
上面是通过配的方式告诉spring:
1、增强的代码在哪?
在txManager里面
2、对谁增强?
对accountService进行增强
3、在什么时候增强?
看通知的类型(前置、后置)
作者:你好y
原文链接:https://blog.csdn.net/qq_43430343/article/details/118972492