Joinpoint(链接点): 目标类中的全部方法都是链接点 Pointcut(切入点): 目标类类中会被加强的方法都是切入点 Advice(通知): 所谓通知是指拦截到Pointcut以后所要作的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知 Aspect(切面): 通知与切入点的结合(所谓的切面就是用来讲明通知与切入点的关系,即:通知在切入点执行的何时执行) Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction能够在运行期为类动态地添加一些方法或Field Target(目标对象): 目标类对象(要加强的类) Proxy(代理对象): 一个目标类被AOP织入加强后,就产生一个结果代理类 Weaving(织入): 是把加强功能应用到目标的过程,即:把advice应用到target的过程
需求:在不修改已有代码的前提下,在项目现有全部类的方法先后打印日志java
接口spring
public interface AccountService { /** * 模拟保存帐户 */ void saveAccount(); /** * 模拟更新帐户 */ void updateAccount(int i); /** * 模拟删除帐户 */ int deleteAccount(); }
实现类express
public class AccountServiceImpl implements AccountService { public void saveAccount() { System.out.println("执行了保存"); } public void updateAccount(int i) { System.out.println("执行了更新"); } public int deleteAccount() { System.out.println("执行了删除"); return 0; } }
通知类app
/** * 日志打印类 */ public class Logger { /** * 用于打印日志,计划让其在切入点方法执行前执行(切入点方法就是业务层方法) */ public void printLog() { System.out.println("Logger的printLog()执行了"); } }
配置文件框架
<?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"> <!-- 配置spring的IOC,将业务层的AccountServiceImpl配置到容器中 --> <bean id="accountServiceImpl" class="com.itcast.service.impl.AccountServiceImpl"></bean> <!-- spring中基于xml的AOP配置: 1.将通知Bean配置到spring的IOC容器中 2.使用<aop:config>标签代表开始AOP的配置 3.使用<aop:aspect>标签代表开始配置切面 属性: id:切面的惟一标识符 ref:用于指向通知bean的id 4.在<aop:aspect>标签的内部使用对应的标签来配置通知的类型 前置通知:<aop:before> method属性:用于指定Logger类中的哪一个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义是指对业务层的哪些方法加强 切入点表达式的语法形式: 关键字:execution(表达式) 表达式: 访问权限 返回值 包名.包名...类名.方法名(参数列表) 标准的表达式写法: public void com.itcast.service.impl.AccountServiceImpl.saveAccount() 访问修饰符能够省略: void com.itcast.service.impl.AccountServiceImpl.saveAccount() 返回值可使用通配符,表示任意返回值 * com.itcast.service.impl.AccountServiceImpl.saveAccount() 包名可使用通配符,表示任意包,可是有几级包,就须要写几个*. * *.*.*.*.AccountServiceImpl.saveAccount() 包名可使用..表示当前包及其子包 * *..AccountServiceImpl.saveAccount() 包名和方法名也可使用*来实现通配 * *..*.*() 参数列表 能够直接写数据类型 基本类型直接写名称:* *..*.*(int) 引用类型写包名.类名:* *..*.*(java.lang.String) 可使用通配符表示任意类型,但必须是有参数:* *..*.*(*) 可使用..表示有无参数都可:* *..*.*(..) 全通配写法: * *..*.*(..) 实际开发中实际切入点表达式的一般写法:切到业务层下的全部实现类 * com.itcast.service.impl.*.*(..) 后置通知:<aop:after> 异常通知:<aop:after-throwing> 最终通知:<app:after> 环绕通知:<aop:around> 咱们如今示例是让printLog()方法在切入点方法执行以前先执行,属于前置通知 --> <bean id="logger" class="com.itcast.utils.Logger"></bean> <aop:config> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置通知的类型,而且创建通知方法和切入点方法的关联关系 --> <aop:before method="printLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:before> </aop:aspect> </aop:config> </beans>
测试类ide
public class TestAOP { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class); accountService.saveAccount(); accountService.updateAccount(1); accountService.deleteAccount(); } }
测试结果工具
Logger的printLog()执行了 执行了保存 Logger的printLog()执行了 执行了更新 Logger的printLog()执行了 执行了删除
XML中的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"> <!-- 配置spring的IOC,将业务层的AccountServiceImpl配置到容器中 --> <bean id="accountServiceImpl" class="com.itcast.service.impl.AccountServiceImpl"></bean> <bean id="printLog" class="com.itcast.utils.Logger"></bean> <aop:config> <!-- 配置切入点表达式,id属性用于指定表达式的惟一标识符,expression属性用于指定表达式内容,此标签写在 <aop:aspect>标签内部,只能当前切面内使用。它能够配置<aop:aspect>标签为,这样就能够全部的切面均可以复用 --> <aop:pointcut id="pt1" expression="execution(* com.itcast.service.impl.*.*(..))"/> <aop:aspect id="logAdvice" ref="printLog"> <!------------------------------- 使用<aop:pointcut>标签简化配置前 -------------------------------> <!-- <!-- 前置通知 --> <aop:before method="beforePrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:before> <!-- 后置通知 --> <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:after-returning> <!-- 异常通知 --> <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:after-throwing> <!-- 最终通知 --> <aop:after method="afterPrintLog" pointcut="execution(* com.itcast.service.impl.*.*(..))"></aop:after> --> <!------------------------------- 使用<aop:pointcut>标签简化配置后 -------------------------------> <!-- 前置通知 --> <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before> <!-- 后置通知 --> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning> <!-- 异常通知 --> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> <!-- 最终通知 --> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>
测试类代理
public class TestAOP { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class); accountService.saveAccount(); } }
测试结果日志
前置通知:Logger的printLog()执行了 执行了保存 后置通知:Logger的printLog()执行了 最终通知:Logger的printLog()执行了
通知类
/** * 用于记录日志的工具类 */ public class Logger { /** * 前置通知 */ public void beforePrintLog() { System.out.println("前置通知:Logger的printLog()执行了"); } /** * 后置通知 */ public void afterReturningPrintLog() { System.out.println("后置通知:Logger的printLog()执行了"); } /** * 异常通知 */ public void afterThrowingPrintLog() { System.out.println("异常通知:Logger的printLog()执行了"); } /** * 最终通知 */ public void afterPrintLog() { System.out.println("最终通知:Logger的printLog()执行了"); } /** * 环绕通知 * 问题: * 当咱们配置了环绕通知以后,切入点方法没有执行,而通知方法执行了 * 分析: * 经过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用 * 解决: * Spring框架为咱们提供了一个接口,Pro */ public Object aroundPrintLog(ProceedingJoinPoint joinPoint) { // 调用proceed()方法即调用切入点方法 Object retValue = null; try { System.out.println("前置通知:Logger的aroundPrintLog()执行了"); // 获取切入点方法执行时所需的参数 Object args[] = joinPoint.getArgs(); retValue = joinPoint.proceed(args); System.out.println("后置通知:Logger的aroundPrintLog()执行了"); return retValue; }catch (Throwable throwable) { System.out.println("异常通知:Logger的aroundPrintLog()执行了"); throw new RuntimeException(throwable); }finally { System.out.println("最终通知:Logger的aroundPrintLog()执行了"); } } }
XML中的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"> <!-- 配置spring的IOC,将业务层的AccountServiceImpl配置到容器中 --> <bean id="accountServiceImpl" class="com.itcast.service.impl.AccountServiceImpl"></bean> <bean id="printLog" class="com.itcast.utils.Logger"></bean> <aop:config> <!-- 配置切入点表达式,id属性用于指定表达式的惟一标识符,expression属性用于指定表达式内容,此标签写在 <aop:aspect>标签内部,只能当前切面内使用。它能够配置<aop:aspect>标签为,这样就能够全部的切面均可以复用 --> <aop:pointcut id="pt1" expression="execution(* com.itcast.service.impl.*.*(..))"/> <aop:aspect id="logAdvice" ref="printLog"> <!-- 配置环绕通知,详细注释请查询Logger类 --> <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config> </beans>
测试类
public class TestAOP { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = applicationContext.getBean("accountServiceImpl",AccountService.class); accountService.saveAccount(); } }
测试结果
前置通知:Logger的aroundPrintLog()执行了 执行了保存 后置通知:Logger的aroundPrintLog()执行了 最终通知:Logger的aroundPrintLog()执行了
接口
/** * 帐户的业务层接口 */ public interface AccountService { /** * 模拟保存帐户 */ void saveAccount(); /** * 模拟更新帐户 * @param i */ void updateAccount(int i); /** * 删除帐户 * @return */ int deleteAccount(); }
实现类
/** * 帐户的业务层实现类 */ @Service("accountService") public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("执行了保存"); } @Override public void updateAccount(int i) { System.out.println("执行了更新"); } @Override public int deleteAccount() { System.out.println("执行了删除"); return 0; } }
XML配置文件
<?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" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring建立容器时要扫描的包--> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置spring开启注解AOP的支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
通知类
在这里插入代码片/** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") //表示当前类是一个切面类 @Aspect public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 前置通知 */ @Before("pt1()") public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ @After("pt1()") public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } }
测试类
/** * 测试AOP的配置 */ public class AOPTest { public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); } }
测试结果
前置通知Logger类中的beforePrintLog方法开始记录日志了。。。 执行了保存 最终通知Logger类中的afterPrintLog方法开始记录日志了。。。 后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。
观察返回结果就会发现通知的输出顺序有问题,这个时spring在使用注解环绕通知时存在的问题
基于注解的AOP,使用环绕通知不会存在顺序问题
/** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") //表示当前类是一个切面类 @Aspect public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 环绕通知 * 问题: * 当咱们配置了环绕通知以后,切入点方法没有执行,而通知方法执行了。 * 分析: * 经过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而咱们的代码中没有。 * 解决: * Spring框架为咱们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就至关于明确调用切入点方法。 * 该接口能够做为环绕通知的方法参数,在程序执行时,spring框架会为咱们提供该接口的实现类供咱们使用。 * * spring中的环绕通知: * 它是spring框架为咱们提供的一种能够在代码中手动控制加强方法什么时候执行的方式。 */ @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//获得方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
测试类
/** * 测试AOP的配置 */ public class AOPTest { public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); } }
测试结果
Logger类中的aroundPringLog方法开始记录日志了。。。前置 执行了保存 Logger类中的aroundPringLog方法开始记录日志了。。。后置 Logger类中的aroundPringLog方法开始记录日志了。。。最终