Spring学习(二十五)Spring AOP之增强介绍

课程概要:
  • Spring AOP的基本概念
  • Spring AOP的增强类型
  • Spring AOP的前置增强
  • Spring AOP的后置增强
  • Spring AOP的环绕增强
  • Spring AOP的异常抛出增强
  • Spring AOP的引介增强
一.Spring AOP 增强 的基本概念
Spring当中的专业术语-advice,翻译成中文就是增强的意思。
所谓增强,其实就是向各个程序内部注入一些逻辑代码从而增强原有程序的功能。
二.Spring AOP的增强类型
首先先了解一下增强接口的继承关系
如上图所示:
其中带Spring标志的是Spring定义的扩展增强接口
其中带aopalliance标志的是AOP联盟所定义的接口

按照增加在目标类方法连接点的位置可以将增强划分为以下五类:
  • 前置增强   (org.springframework.aop.BeforeAdvice)   表示在目标方法执行前来实施增强
  • 后置增强   (org.springframework.aop.AfterReturningAdvice)   表示在目标方法执行后来实施增强
  • 环绕增强   (org.aopalliance.intercept.MethodInterceptor)   表示在目标方法执行前后同时实施增强
  • 异常抛出增强   (org.springframework.aop.ThrowsAdvice)   表示在目标方法抛出异常后来实施增强
  • 引介增强   (org.springframework.aop.introductioninterceptor)   表示在目标类中添加一些新的方法和属性
其中,引介增强是一种特殊的增强。他可以在目标中添加属性和方法,通过拦截定义一个接口,让目标代理实现这个接口。他的连接点是级别的,而前面的几种则是方法级别的。
其中,环绕增强是AOP联盟定义的接口,其他四种增强接口则是Spring定义的接口。

其实,AOP增强很简单:
通过实现这些增强接口,在实现这些接口的方法当中定义横切逻辑,然后通过配置Spring的配置文件就可以完成将增强织入到目标方法当中了。

补充:增强既包含了横切逻辑同时又包含了部分连接点信息。

三.Spring AOP的前置增强

1.通过代码实现增强
在Spring当中,仅支持方法级别的增强,利用MethodBeforeAdvice实现,表示在目标方法执行前实施增强。
示例演示:
对服务生的服务用语进行强制规范。我们假设服务生只需要干两件事情:1.欢迎顾客 2.对顾客提供服务
那么我们创建的示例代码的主要步骤如下:
  1. 创建业务接口类:Waiter.java
  2. 创建业务实现类:NativeWaiter.java
  3. 创建业务增强类:GreetingBeforeAdvice.java
  4. 创建增强测试类:TestAdvice.java
接下来我们分别在IDEA中创建相应的类。
服务生接口Waiter.java
public interface Waiter {
    void greetTo(String name);
    void serverTo(String name);
}
服务生实现类NativeWaiter.java
public class NativeWaiter implements Waiter{
    public void greetTo(String name) {
        System.out.println("greet to"+name+"...");
    }

    public void serverTo(String name) {
        System.out.println("serving"+name+"...");
    }
}
服务生业务增强类GreetingBeforeAdvice.java
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
    /**
    * 前置增强方法
    * 当该方法发生异常时,将阻止目标方法的执行
    * @param method 目标类方法
    * @param objects 目标类方法入参
    * @param o 目标类对象实例
    * @throws Throwable
    */
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        String clientName=(String)objects[0];
        System.out.println("How Are You! mr."+clientName);
    }
}

增强测试类TestBeforeAdvice.java
public class TestBeforeAdvice {
    public static void main(String[] args){
        //创建目标对象
        Waiter target=new NativeWaiter();
        //创建增强类对象
        BeforeAdvice advice=new GreetingBeforeAdvice();
        //创建代理工厂对象
        ProxyFactory factory=new ProxyFactory();
        //设置代理类
        factory.setTarget(target);
        //添加增强类
        factory.addAdvice(advice);
        //获取代理类
        Waiter proxy=(Waiter)factory.getProxy();
        //调用目标类方法
        proxy.greetTo("icarus");
        proxy.serverTo("icarus");

    }
}
程序运行结果:
How Are You! mr.icarus
greet toicarus...
How Are You! mr.icarus
servingicarus...

2.ProxyFactory介绍
其实ProxyFactory代理技术就是利用jdk代理或者cglib代理的技术,将增强应用到目标类当中。
Spring定义的AOP Proxy类具有两个final类型的实现类,如下图所示:



其中:
Cglib2AopProxy是使用cglib代理技术来创建代理
JdkDynamicAopProxy是使用jdk代理技术来创建代理
那么使用JDK代理来实现上面的代码则为:
//创建代理工厂对象
ProxyFactory factory=new ProxyFactory();
//设置代理接口
factory.setInterfaces(target.getClass().getInterfaces());
//设置代理类
factory.setTarget(target);
//设置增强类
factory.addAdvice(advice);

使用CGLib代理则为:
ProxyFactory factory=new ProxyFactory();
//设置代理接口
factory.setInterfaces(target.getClass().getInterfaces());
//启用cglib代理方式
factory.setOptimize(true);
//设置代理类
factory.setTarget(target);
//添加增强类
factory.addAdvice(advice);

可以观察到,ProxyFactory通过addAdvice来增加一个增强。
用户可以使用该方法增加多个增强,通过增强形成一个增强链,他们的调用顺序和添加顺序是一致的
3.通过配置文件实现增强
我们也可以通过配置文件来实现Spring的前置增强,并且大多数情况下都是使用配置文件方式。
首先我们介绍下ProxyFactory Bean配置文件当中常用的属性:
  • target:我们需要代理的目标对象
  • proxyInterfaces:代理所要实现的接口,可以是多个接口
  • interceptorNames:需要织入的目标对象的Bean的列表(增强类的Bean列表),使用Bean的名称来指定。
  • singleton:确定返回的代理是不是单实例的,系统默认返回的是单实例的。
  • optimize:当值为true时,强制使用cglib代理。当是singleton的实例时我们推荐使用cglib代理,当是其他作用域的时候,推荐使用JDK的代理。原因是cglib创建代理速度比较慢,但是运行效率高。JDK代理则刚好相反。
  • proxyTargetClass:是否对进行代理而不是对接口进行代理,当值为true的时候使用cglib代理

接下来我们使用配置文件对上面的示例代码进行配置:
<?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:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="gerrtingBefore" class="cn.lovepi.chapter07.aop.advice.GreetingBeforeAdvice"/>
    <bean id="target" class="cn.lovepi.chapter07.aop.advice.NativeWaiter"/>
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="cn.lovepi.chapter07.aop.advice.Waiter"
          p:interceptorNames="gerrtingBefore"
          p:target-ref="target"
          />

</beans>

接下来我们创建对应的测试文件
public class TestBeforeAdviceByXml {
    public static void main(String[] args){
        String path="src/conf/conf-advice.xml";
        ApplicationContext context=new FileSystemXmlApplicationContext(path);
        Waiter waiter=context.getBean("waiter",Waiter.class);
        waiter.greetTo("icarus");
        waiter.serverTo("icarus");

    }
}

可以看到输出结果为:
How Are You! mr.icarus
greet toicarus...
How Are You! mr.icarus
servingicarus...

和我们通过代码实现增强的结果相同

四.Spring AOP的后置增强
后置增强在目标方法调用后执行,例如上面的例子中,在服务生每次服务后,也需要向客人问候,可以通过后置增强来实施这一要求,步骤如下:
  1. 创建业务接口类:Waiter.java
  2. 创建业务实现类:NativeWaiter.java
  3. 创建业务增强类:GreetingAfterAdvice.java
  4. 创建配置文件:conf-advice.xml
  5. 创建增强测试类:TestAdvice.java
接下来我们在IDEA中创建相应的代码:
我们继续使用上面的例子,由于 Waiter.java和 NativeWaiter.java已经创建好了
我们只需创建 GreetingAfterAdvice.java
public class GreetingAfterAdvice  implements AfterReturningAdvice{
    /**
    * 后置增强代码实现
    * @param o 代理返回对象
    * @param method 目标对象方法
    * @param objects 目标对象方法参数
    * @param o1 目标对象
    * @throws Throwable
    */
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("please enjoy youself!");
    }
}

接下来我们修改对应的配置文件
首先得将后者增强类作为bean配置到文件当中
<bean id="gerrtingAfter" class="cn.lovepi.chapter07.aop.advice.GreetingAfterAdvice"/>
接下来得在ProxyFactory Bean当中添加织入的bean
p:interceptorNames="gerrtingBefore,gerrtingAfter"
完整的配置文件如下:
<?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:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="gerrtingBefore" class="cn.lovepi.chapter07.aop.advice.GreetingBeforeAdvice"/>
    <bean id="gerrtingAfter" class="cn.lovepi.chapter07.aop.advice.GreetingAfterAdvice"/>
    <bean id="target" class="cn.lovepi.chapter07.aop.advice.NativeWaiter"/>
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="cn.lovepi.chapter07.aop.advice.Waiter"
          p:interceptorNames="gerrtingBefore,gerrtingAfter"
          p:target-ref="target"
          />

</beans>

测试文件和上面的保持不变,运行测试类,测试结果为:
How Are You! mr.icarus
greet toicarus...
please enjoy youself!
How Are You! mr.icarus
servingicarus...
please enjoy youself!

五.Spring AOP的环绕增强
环绕增强允许在目标类方法调用前后织入横切逻辑,它综合实现了前置,后置增强两者的功能,下面是我们用环绕增强同时实现上面的我们的示例。步骤如下:
  1. 创建业务接口类:Waiter.java
  2. 创建业务实现类:NativeWaiter.java
  3. 创建业务增强类:GreetingInterceptor.java
  4. 创建配置文件:conf-advice.xml
  5. 创建增强测试类:TestAdvice.java
接下来我们在IDEA中来实现。
首先创建 GreetingInterceptor.java
public class GreetingInterceptor implements MethodInterceptor{
    /**
    * 业务逻辑实现类
    * @param methodInvocation 封装了目标方法和入参数组以及目标方法所带的实例对象
    * @return 代理对象
    * @throws Throwable
    */
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //获取目标方法的入参
        Object[] args=methodInvocation.getArguments();
        //获取方法名称
        String clickName= (String) args[0];
        System.out.println("GreetingInterceptor:How are you!");
        //利用反射机制来调用目标方法
        Object object=methodInvocation.proceed();
        System.out.println("GreetingInterceptor: please enjoy youself!");
        return object;
    }
}

接下来在配置文件中对其进行配置:
<?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:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="gerrtingBefore" class="cn.lovepi.chapter07.aop.advice.GreetingBeforeAdvice"/>
    <bean id="gerrtingAfter" class="cn.lovepi.chapter07.aop.advice.GreetingAfterAdvice"/>
    <bean id="gerrtingAround" class="cn.lovepi.chapter07.aop.advice.GreetingInterceptor"/>
    <bean id="target" class="cn.lovepi.chapter07.aop.advice.NativeWaiter"/>
    <bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyInterfaces="cn.lovepi.chapter07.aop.advice.Waiter"
          p:interceptorNames="gerrtingBefore,gerrtingAfter,gerrtingAround"
          p:target-ref="target"
          />

</beans>


启动测试类,观察打印结果:
How Are You! mr.icarus
GreetingInterceptor:How are you!
greet toicarus...
GreetingInterceptor: please enjoy youself!
please enjoy youself!
How Are You! mr.icarus
GreetingInterceptor:How are you!
servingicarus...
GreetingInterceptor: please enjoy youself!
please enjoy youself!

可以看到,我们成功在示例中实现了前置增强,后者增强以及环绕增强。

六.Spring AOP的异常抛出增强
异常抛出增强表示在目标方法抛出异常后实施增强,最适 合的场景是事务管理,比如当参与事事务的方法抛出异常后需要回滚事务。
异常抛出增强类需要实现 ThrowsAdvice接口, ThrowsAdvic e接口并没有定义任何的方法,他只是一个标志接口。
在运行期,Spring采用反射的机制来进行判断。我们必须采用以下的形式来定义异常抛出的方法
public void afterThrowing(Method method,Object[] args,Object target,Throwable t)
其中:
方法名必须为 afterThrowing,方法入参中前三个入参是可选的,即要么同时存在,要么都没有
最后一个入参是 Throwable及其子类,必须得有。
也可以在异常增强类中定义多个方法,Spring会自动选择匹配的方法来进行调用。
在类的继承树上,两个类的距离越近,则两个类的相似度越高
那么当方法抛出异常时,会优先选取异常入参和抛出的异常相似度最高的afterThrowing方法。

接下来我们创建示例来演示一下,步骤如下:
  1. 创建业务实现类:ForumService.java
  2. 创建业务增强类:TransactionManager.java
  3. 创建配置文件:conf-advice.xml
  4. 创建增强测试类:TestAdvice.java
接下来我们在IDEA上分别创建对应的代码:
首先,我们创建业务逻辑类ForumService
public class ForumService {
    public void removeForum(){
        //进行相应的数据库操作,但这里只为演示抛出异常
        throw new RuntimeException("removeForum:Exception...");
    }
    public void updateForum(){
        //进行相应的数据库操作,但这里只为演示抛出异常
        throw new RuntimeException("updateForum:Exception...");
    }
}

接下来我们创建增强类TransactionManager
public class TransactionManager implements ThrowsAdvice{
    /**
    * 捕获异常并打印异常名称
    * @param method 目标对象对应方法
    * @param args 方法入参
    * @param target 目标对象
    * @param ex 运行方法所捕获的异常
    * @throws Throwable
    */
    public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{
        System.out.println("method:"+method.getName());
        System.out.println("抛出异常:"+ex.getMessage());
        System.out.println("成功回滚事务");
    }
}
接下来我们编写对应的配置文件
<?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:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="forumServiceTarget" class="cn.lovepi.chapter07.aop.advice.ForumService"/>
    <bean id="transactionManager" class="cn.lovepi.chapter07.aop.advice.TransactionManager"/>
    <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
          p:proxyTargetClass="true"
          p:target-ref="forumServiceTarget"
          p:interceptorNames="transactionManager"
          />
</beans>
创建相应的测试类进行测试
public static void testThrowAdvice(){
    String path="src/conf/conf-advice.xml";
    ApplicationContext context=new FileSystemXmlApplicationContext(path);
    ForumService forumService=context.getBean("forumService",ForumService.class);
    try {
        forumService.removeForum();
    }catch (Exception e){}
    try {
        forumService.updateForum();
    }catch (Exception e){}
}
运行结果为:
method:removeForum
抛出异常:removeForum:Exception...
成功回滚事务
method:updateForum
抛出异常:updateForum:Exception...
成功回滚事务


七.Spring AOP的引介增强
引介增强是一种比较特殊的增强类型,他不是在目标方法周围织入增强,而是为目标创建新的方法和属性,所以他的连接点是类级别的而非方法级别的。通过引介增强我们可以为目标类添加一个接口的实现即原来目标类未实现某个接口,那么通过引介增强可以为目标类创建实现某接口的代理。

接下来我们创建一个示例来演示下,步骤如下:
  • 创建接口类:Monitorable.java
  • 创建业务类:PerformanceMonitor.java
  • 创建增强类:ControllablePerformanceMonitor.java
  • 创建配置文件:conf-advice-introduce.xml
  • 创建增强测试类:TestIntroduce.java

接下来我们在IDEA上分别创建对应的代码:
首先创建性能监视接口Monitorable

public interface Monitorable {
    void setMonitorActive(boolean active);
}
创建测试接口Testable
public interface Testable {
  void test();
}
接下来创建业务类
public class PerformanceMonitor {
  private static ThreadLocal<MethodPerformace> performaceRecord = new ThreadLocal<MethodPerformace>();
  public static void begin(String method) {
      System.out.println("begin monitor...");
      MethodPerformace mp = performaceRecord.get();
      if(mp == null){
        mp = new MethodPerformace(method);
        performaceRecord.set(mp);
      }else{
          mp.reset(method);
      }
  }
  public static void end() {
      System.out.println("end monitor...");
      MethodPerformace mp = performaceRecord.get();
      mp.printPerformace();
  }
}

接下来创建增强类 ControllablePerformanceMonitor
public class ControllablePerformaceMonitor
      extends
        DelegatingIntroductionInterceptor implements Monitorable, Testable {
  private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
  public void setMonitorActive(boolean active) {
      MonitorStatusMap.set(active);
  }
  public Object invoke(MethodInvocation mi) throws Throwable {
      Object obj = null;
      if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
        PerformanceMonitor.begin(mi.getClass().getName() + "."
              + mi.getMethod().getName());
        obj = super.invoke(mi);
        PerformanceMonitor.end();
      } else {
        obj = super.invoke(mi);
      }
      return obj;
  }
  public void test() {
      // TODO Auto-generated method stub
      System.out.println("dd");
  }
}
接下来创建所要增强的方法类
public class ForumService {

  public void removeTopic(int topicId) {
      System.out.println("模拟删除Topic记录:"+topicId);
      try {
        Thread.currentThread().sleep(20);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }

  }

  public void removeForum(int forumId) {
      System.out.println("模拟删除Forum记录:"+forumId);
      try {
        Thread.currentThread().sleep(40);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
  }
}

public class MethodPerformace {
  private long begin;
  private long end;
  private String serviceMethod;
    public MethodPerformace(String serviceMethod){
      reset(serviceMethod);
    }
    public void printPerformace(){
        end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
    }
    public void reset(String serviceMethod){
      this.serviceMethod = serviceMethod;
      this.begin = System.currentTimeMillis();
    }
}
创建配置文件来将所设置的代码组合起来:
<?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:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean id="pmonitor" class="cn.lovepi.chapter07.aop.intorduce.ControllablePerformaceMonitor" />
  <bean id="forumServiceTarget" class="cn.lovepi.chapter07.aop.intorduce.ForumService" />
  <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:interfaces="cn.lovepi.chapter07.aop.intorduce.Monitorable"
      p:target-ref="forumServiceTarget"
      p:interceptorNames="pmonitor" p:proxyTargetClass="true" />

</beans>
创建对应的测试类
public class TestIntroduce {
  public static void main(String[] args) {
      testBeforeAdviceByCode();
  }

  private static void testBeforeAdviceByCode() {
      String configPath = "src/conf/conf-advice-introduce.xml";
      ApplicationContext ctx = new FileSystemXmlApplicationContext(configPath);
        ForumService forumService = (ForumService)ctx.getBean("forumService");
        forumService.removeForum(10);
        forumService.removeTopic(1022);
        Monitorable moniterable = (Monitorable)forumService;
        moniterable.setMonitorActive(true);
        forumService.removeForum(10);
        forumService.removeTopic(1022);
  }

}
程序运行结果为:

模拟删除Forum记录:10
模拟删除Topic记录:1022
begin monitor...
模拟删除Forum记录:10
end monitor...
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.removeForum花费40毫秒。
begin monitor...
模拟删除Topic记录:1022
end monitor...
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.removeTopic花费20毫秒。


总结
增强其实就是对原有的方法或类动态增加功能,可为方法执行前后以及所抛出的异常进行逻辑处理。实现增强的方式有两种:代码方式和XML配置文件方式,建议在以后开发中使用后者,这样可以避免代码的耦合度,方便后期维护。


猜你喜欢

转载自blog.csdn.net/icarus_wang/article/details/51737474