Spring Learning (25) Introduction to the Enhancement of Spring AOP

Course outline:
  • Basic Concepts of Spring AOP
  • Enhanced Types for Spring AOP
  • Pre-enhancement of Spring AOP
  • Post-enhancement of Spring AOP
  • Wraparound Enhancements for Spring AOP
  • Exception throwing enhancement of Spring AOP
  • Introducing Enhancements for Spring AOP
1. Basic concepts of Spring AOP enhancement
The professional term in Spring - advice , translated into Chinese means enhancement.
The so-called enhancement is actually to inject some logic code into each program to enhance the function of the original program.
2. Enhanced types of Spring AOP
First understand the inheritance relationship of enhanced interfaces
As shown in FIG:
The one with the Spring logo is the extension enhancement interface defined by Spring
The one with the aopalliance sign is the interface defined by the AOP Alliance

The enhancements can be divided into the following five categories according to the location of the increase in the connection point of the target class method :
  • Pre-enhancement   (org.springframework.aop.BeforeAdvice) means that the enhancement is implemented before the target method is executed
  • Post-enhancement   (org.springframework.aop.AfterReturningAdvice) indicates that the enhancement is implemented after the target method is executed
  • Surround enhancement   (org.aopalliance.intercept.MethodInterceptor) indicates that the enhancement is implemented before and after the execution of the target method
  • Exception Throwing Enhancement   (org.springframework.aop.ThrowsAdvice) indicates that the enhancement is implemented after the target method throws an exception
  • Introduction enhancement   (org.springframework.aop.introductioninterceptor) means adding some new methods and properties to the target class
Among them, the introduction enhancement is a special kind of enhancement. He can add properties and methods to the target class , define an interface through interception, and let the target proxy implement this interface. His connection point is at the class level, while the previous ones are at the method level.
Among them, the surround enhancement is the interface defined by the AOP Alliance, and the other four enhanced interfaces are the interfaces defined by Spring.

In fact, AOP enhancement is very simple:
By implementing these enhanced interfaces, defining cross-cutting logic in the methods that implement these interfaces, and then configuring Spring's configuration file to weave the enhancements into the target method.

Supplement: Enhancement includes both cross-cutting logic and some connection point information.

3. Pre-enhancement of Spring AOP

1. Enhancement through code
In Spring, only method-level enhancements are supported, implemented using MethodBeforeAdvice , which means that enhancements are implemented before the target method is executed.
Example demo:
Mandatory specification of the service terms of waiters. We assume that the waiter only needs to do two things: 1. Welcome the customer 2. Serve the customer
So the main steps of the sample code we created are as follows:
  1. Create a business interface class: Waiter.java
  2. Create a business implementation class: NativeWaiter.java
  3. Create a business enhancement class: GreetingBeforeAdvice.java
  4. Create an enhanced test class: 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配置文件方式,建议在以后开发中使用后者,这样可以避免代码的耦合度,方便后期维护。


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325717002&siteId=291194637