https://www.bilibili.com/video/av47952931  p56~65
 
 
AOP概述 Aspect Oriented Programming 面向切面编程
作用及优势 作用:
优势:
Spring中的AOP 相关术语 Joinpoint 连接点 类中可以被增强的方法
Pointcut 切入点 类中实际增强的方法
Advice 通知/增强 切入点上扩展的功能
前置增强:在方法之前执行 
后置增强:在方法正常执行之后执行 
最终增强:在最后执行,无论是否有异常 
环绕增强:在之前和之后执行 
异常增强:方法出现异常时执行 
 
Introduction 引介 一种特殊的增强,在不修改类代码的前提下,可以在运行期间为类动态地添加一些方法或Field
Target 目标对象 要增强的类
Weaving 织入 把增强应用到目标对象的过程
Proxy 代理 一个类被AOP织入增强后,就产生一个结果代理类
Aspect 切面 切入点 + 通知/引介
Spring基于XML的AOP配置 配置步骤 
配置增强Bean
 
使用aop:config标签表明开始AOP的配置
 
使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id
 
在aop:aspect标签的内部使用对应标签来配置通知的类型
aop:before:表示配置前置通知(实例中让printLog方法在切入点方法前执行) method属性:用于指定类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 切入点表达式的写法: 	关键字:execution(表达式) 	表达式: 		访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表) 		 	标准的表达式写法: 		public void com.itheima.service.impl.AccountServiceImpl.saveAccount() 	访问修饰符可以省略 		void com.itheima.service.impl.AccountServiceImpl.saveAccount() 	返回值可以使用通配符,表示任意返回值 		* com.itheima.service.impl.AccountServiceImpl.saveAccount() 	包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. 		* *.*.*.*.AccountServiceImpl.saveAccount()) 	包名可以使用..表示当前包及其子包 		* *..AccountServiceImpl.saveAccount() 	类名和方法名都可以使用*来实现通配 		* *..*.*() 	参数列表: 		可以直接写数据类型: 			基本类型直接写名称           int 			引用类型写包名.类名的方式   java.lang.String 			可以使用通配符表示任意类型,但是必须有参数 			可以使用..表示有无参数均可,有参数可以是任意类型 	全通配写法: 		* *..*.*(..) 	实际开发中切入点表达式的通常写法: 		切到业务层实现类下的所有方法 			* com.itheima.service.impl.*.*(..) 
 
示例 xml配置 在Spring Framework Documentation的Core中搜索xmlns:aop,导入约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?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" >         <bean  id ="accountService"  class ="com.itheima.service.impl.AccountServiceImpl" > </bean >               <bean  id ="logger"  class ="com.itheima.utils.Logger" > </bean >           <aop:config >                   <aop:aspect  id ="logAdvice"  ref ="logger" >                           <aop:before  method ="printLog"  pointcut ="execution(* com.itheima.service.impl.*.*(..))" > </aop:before >          </aop:aspect >      </aop:config >  </beans > 
 
业务层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  interface  IAccountService   {        void  saveAccount ()  ;         void  updateAccount (int  i)  ;         int   deleteAccount ()  ; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  class  AccountServiceImpl  implements  IAccountService  {         public  void  saveAccount ()   {         System.out.println("执行了保存" );     }     public  void  updateAccount (int  i)   {         System.out.println("执行了更新" +i);     }     public  int  deleteAccount ()   {         System.out.println("执行了删除" );         return  0 ;     } } 
 
通知 1 2 3 4 5 6 7 8 9 10 public  class  Logger   {         public   void  printLog ()  {         System.out.println("Logger类中的pringLog方法开始记录日志了。。。" );     } } 
 
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public  class  AOPTest   {    public  static  void  main (String[] args)   {                  ApplicationContext ac = new  ClassPathXmlApplicationContext("bean.xml" );                  IAccountService as = (IAccountService)ac.getBean("accountService" );                  as.saveAccount();         as.updateAccount(1 );         as.deleteAccount();     } } 
 
 
配置切入点表达式 id属性用于指定表达式的唯一标识
expression属性用于指定表达式内容
该标签写在aop:aspect标签内部只能当前切面使用
还可以写在aop:aspect外面,此时就变成了所有切面可用
注意:必须放在切面之前,否则会报错(还不提醒怎么错的)
4种常用通知类型 继续在刚才的实例上加其它通知类型
使用切入点表达式简化配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <aop:config >          <aop:pointcut  id ="pt1"  expression ="execution(* com.itheima.service.impl.*.*(..))" > </aop:pointcut >           <aop:aspect  id ="logAdvice"  ref ="logger" >                   <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 > 
 
切入点方法无异常时输出
1 2 3 4 前置通知Logger类中的beforePrintLog方法开始记录日志了... 执行了保存 后置通知Logger类中的afterReturningPrintLog方法开始记录日志了... 最终通知Logger类中的afterPrintLog方法开始记录日志了... 
 
切入点方法有异常时输出
1 2 3 4 5 6 前置通知Logger类中的beforePrintLog方法开始记录日志了... 执行了保存 异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了... 最终通知Logger类中的afterPrintLog方法开始记录日志了... Exception in thread "main" java.lang.ArithmeticException: / by zero ... 
 
后置和异常只会执行其中一个
环绕通知 下面这样配置环绕通知是不行的
1 2 3 public   void  aroundPringLog ()  {       System.out.println("Logger类中的afterThrowingPrintLog方法开始记录日志了..." );    } 
 
这样配置后,切入点方法不执行了,只执行通知方法
分析:对比动态代理中,发现其中的环绕通知有明确的切入点调用,而这样没有
解决:使用Spring的ProceedingJoinPoint接口。该接口有一个proceed()方法,在程序执行时,Spring会提供该接口的实现类供我们使用
这样可以控制增强方法何时执行,前置、后置、异常、最终都可以实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  class  Logger   {	 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方法开始记录日志了...最终" );         }     } } 
 
1 2            <aop:around  method ="aroundPringLog"  pointcut-ref ="pt1" > </aop:around >  
 
Spring基于注解的AOP配置 示例 xml配置 约束比基于xml的多了context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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" >         <context:component-scan  base-package ="com.itheima" > </context:component-scan >           <aop:aspectj-autoproxy > </aop:aspectj-autoproxy >  </beans > 
 
Logger配置(4种常用类型)![建议别用,有bug] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @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方法开始记录日志了..." );     } } 
 
运行输出:
1 2 3 4 前置通知Logger类中的beforePrintLog方法开始记录日志了... 执行了保存 最终通知Logger类中的afterPrintLog方法开始记录日志了... 后置通知Logger类中的afterReturningPrintLog方法开始记录日志了... 
 
发现最终在后置之前执行了(异常也一样)
没有办法,因为Spring基于注解的AOP中,调用顺序确实有问题,实际开发中应该慎重
Logger配置(环绕通知) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Component("logger") @Aspect  public  class  Logger   {         @Pointcut("execution(* com.itheima.service.impl.*.*(..))")      private  void  pt1 ()  {}     @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方法开始记录日志了...最终" );         }     } } 
 
此时就没有顺序问题了
1 2 3 4 Logger类中的aroundPringLog方法开始记录日志了...前置 执行了保存 Logger类中的aroundPringLog方法开始记录日志了...后置 Logger类中的aroundPringLog方法开始记录日志了...最终