Spring(三)基于XML的AOP配置

AOP 相关术语

Joinpoint(连接点):

所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。

Pointcut(切入点):

所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

Advice(通知/增强):

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction(引介):

引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

Target(目标对象):

代理的目标对象。

Weaving(织入):

是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

Proxy(代理) :

一个类被 AOP 织入增强后,就产生一个结果代理类。

Aspect(切面):

是切入点和通知(引介)的结合。

基于 XML 的 AOP 配置

第一步: 创建 maven 工程并导入坐标

 <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>5.0.2.RELEASE</version>
       </dependency>
       <dependency>
           <groupId>org.aspectj</groupId>
           <artifactId>aspectjweaver</artifactId>
           <version>1.8.7</version>
       </dependency>

第二步: 准备必要的代码

public interface IAccountService {
   void save();
   void update();
   void delete();
}

// 模拟保存用户
public class AccountServiceImpl implements IAccountService {
   public void save() {
       System.out.println("save OK!");
       int i = 1 / 0;
   }
   public void update() {
       System.out.println("update OK!");
   }
   public void delete() {
       System.out.println("delete OK!");
   }
}
// 要增强的类
public class Logger {
   // 前置通知
   public void beforePrintLog() {
       System.out.println("前置");
   }
   // 后置通知
   public void afterReturningPrintLog() {
       System.out.println("后置");
   }
   // 异常通知  
   public void afterThrowingPrintLog() {
       System.out.println("异常");
   }
   // 最终通知
   public void afterPrintLog() {
       System.out.println("最终");
   }
   /**
    * 配置环绕通知
    * 问题:
    * 当我们配置了环绕通知之后,当运行时,切入点方法没有执行,而环绕通知执行了。
    * 分析:
    * 我们的环绕通知中,没有明确的切入点方法调用。
    * 解决:
    * Spring框架给我们提供了一个接口:ProceedingJoinPoint。该接口可以作为环绕通知的方法参数来用。
    * 在程序运行期,spring会给我们提供该接口的实现类,我们在环绕通知中直接使用即可。
    * 接口中有个方法:
    * proceed(),它的作用就相当于method.invoke()。明确调用切入点方法
    * <p>
    * spring中的环绕通知:
    * 它是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的机制。
    */
   public Object aroundPrintLog(ProceedingJoinPoint pjp){
       Object rtValue = null;
       try{
           System.out.println("环绕通知执行了。。。前置");
           //获取参数
           Object[] args = pjp.getArgs();
           //执行方法
           rtValue = pjp.proceed(args);//明确的切入点方法调用
           System.out.println("环绕通知执行了。。。后置");
           return rtValue;

       }catch (Throwable e){
           System.out.println("环绕通知执行了。。。异常");
           throw new RuntimeException(e);
       }finally {
           System.out.println("环绕通知执行了。。。最终");
       }
   }
}
<!--bean.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"
      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">

   <!--配置service-->
   <bean id="accountService" class="fun.chenqi.service.impl.AccountServiceImpl">
   </bean>
   <!--第一步 把通知类交给spring来管理 通知是什么:增强的类是谁-->
   <bean id="logger" class="fun.chenqi.logger.Logger"/>
   <!-- 配置切面AOP 使用的是aop:config表示开始配置-->
   <aop:config>
       <!-- 配置通用的切入点表达式。如果写在切面标签内部,则表示当前切面可用。如果写在切面标签外部,表示所有切面可用-->
       <aop:pointcut id="po" expression="execution(* fun.chenqi.service.impl.*.*(..))"/>
       <!--第二步  使用aop:aspect开始配置切面 id属性是用于指定切面的id  ref用于指定通知的id。
                  切面是什么:对哪些方法增强-->
       <aop:aspect id="logAdvice" ref="logger">
         <!--第三步:使用切入点表达式,指定对哪些方法增强
               是配置pointcut属性。
               属性的取值是切入点表达式。
               切入点表达式是基于aspectJ语言的,也叫aspectJ表达式。需要用到aspectJweaver的坐标
               表达式写法:
                   关键字:execution(表达式)
                   表达式:
                       访问修饰符  返回值   包名.包名.包名.类名.方法名(参数列表)
                   全匹配方式:
                       public void fun.chenqi.service.impl.AccountServiceImpl.saveAccount()
                   访问修饰符可以省略:
                   返回值可以使用*,表示任意返回值
                   包名可以使用*,表示任意包,但是有几级包需要写几个*
                   包名可以使用..表示当前包及其子包
                   类型和方法名都可以使用*,表示任意类和任意方法
                   参数列表,可以使用具体类型。
                       基本类型使用类型名称,引用类型是包名.类名
                   参数列表的类型,可以使用*,表示任意参数类型,但是必须有参数
                   参数列表可以使用..,表示有无参数均可,有参数可以是任意类型
                   全通配方式:
                       * *..*.*(..)
                    开发中基本都是对业务层增强,写法一般是:
                       * fun.chenqi.service.impl.*.*(..)
           -->
          <!-- <aop:after method="afterPrintLog" pointcut="execution(*                      fun.chenqi.service.impl.*.*(..))"/>-->
           <!--配置前置通知 :在切入点方法执行前 todo 通知类别:代码啥时候执行-->
           <aop:before method="beforePrintLog" pointcut-ref="po"/>
           <!-- 最终通知:无论切入点方法是否正常执行,它都会在其后面执行-->
           <aop:after method="afterPrintLog" pointcut-ref="po"/>
           <!--后置通知:在切入点方法正常执行之后。后置通知和异常通知只能有一个执行-->
           <aop:after-returning method="afterReturningPrintLog" pointcut-ref="po"/>
           <!-- 异常通知:在切入点方法执行产生异常之后-->
           <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="po"/>
           <!-- 配置环绕通知-->
         <!--  <aop:around method="aroundPrintLog" pointcut-ref="po"/>-->
       </aop:aspect>
   </aop:config>
</beans>

发表评论