热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解SpringAOP:增强机制详解与应用

本课程详细解析了SpringAOP的核心概念及其增强机制,涵盖前置增强、后置增强和环绕增强等类型。通过具体示例,深入探讨了如何在实际开发中有效运用这些增强技术,以提升代码的模块化和可维护性。此外,还介绍了SpringAOP在异常处理和性能监控等场景中的应用,帮助开发者更好地理解和掌握这一强大工具。
课程概要:
  • 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代理

接下来我们使用配置文件对上面的示例代码进行配置:


    
    
    



接下来我们创建对应的测试文件
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配置到文件当中

接下来得在ProxyFactory Bean当中添加织入的bean
p:interceptorNames="gerrtingBefore,gerrtingAfter"
完整的配置文件如下:


    
    
    
    



测试文件和上面的保持不变,运行测试类,测试结果为:
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;
    }
}

接下来在配置文件中对其进行配置:


    
    
    
    
    




启动测试类,观察打印结果:
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接口,ThrowsAdvice接口并没有定义任何的方法,他只是一个标志接口。
在运行期,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("成功回滚事务");
    }
}
接下来我们编写对应的配置文件


    
    
    

创建相应的测试类进行测试
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 performaceRecord = new ThreadLocal();
  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 MOnitorStatusMap= new ThreadLocal();
  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();
    }
}
创建配置文件来将所设置的代码组合起来:



  
  
  


创建对应的测试类
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配置文件方式,建议在以后开发中使用后者,这样可以避免代码的耦合度,方便后期维护。


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


推荐阅读
  • 深入解析 Spring MVC 的核心原理与应用实践
    本文将详细探讨Spring MVC的核心原理及其实际应用,首先从配置web.xml文件入手,解析其在初始化过程中的关键作用,接着深入分析请求处理流程,包括控制器、视图解析器等组件的工作机制,并结合具体案例,展示如何高效利用Spring MVC进行开发,为读者提供全面的技术指导。 ... [详细]
  • 在Spring框架中,基于Schema的异常通知与环绕通知的实现方法具有重要的实践价值。首先,对于异常通知,需要创建一个实现ThrowsAdvice接口的通知类。尽管ThrowsAdvice接口本身不包含任何方法,但开发者需自定义方法来处理异常情况。此外,环绕通知则通过实现MethodInterceptor接口来实现,允许在方法调用前后执行特定逻辑,从而增强功能或进行必要的控制。这两种通知机制的结合使用,能够有效提升应用程序的健壮性和灵活性。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • 深入解析Spring Boot自动配置机制及其核心原理
    Spring Boot 的自动配置机制是其核心特性之一,旨在简化开发过程并提高效率。本文将深入探讨这一机制的工作原理,解释其如何通过智能化的类路径扫描和条件注解实现自动装配。通过对 Spring Boot 自动配置的详细解析,读者将能够更好地理解和应用这一强大功能,从而在实际项目中更加高效地利用 Spring Boot。 ... [详细]
  • 本文详细介绍了 MiniGUI 中静态控件(CTRL_STATIC)的使用方法及其不同风格的应用。具体而言,采用 SS_SIMPLE 风格的静态控件仅支持单行文本显示,不具备自动换行功能,且文本始终为左对齐。而 SS_LEFT、SS_CENTER 和 SS_RIGHT 风格则分别实现了文本的左对齐、居中和右对齐布局,提供了更多的排版灵活性。此外,文章还探讨了这些控件在实际开发中的应用场景和最佳实践。 ... [详细]
  • 探索JavaScript倒计时功能的三种高效实现方法及代码示例 ... [详细]
  • 本文将介绍一种扩展的ASP.NET MVC三层架构框架,并通过使用StructureMap实现依赖注入,以降低代码间的耦合度。该方法不仅能够提高代码的可维护性和可测试性,还能增强系统的灵活性和扩展性。通过具体实践案例,详细阐述了如何在实际开发中有效应用这一技术。 ... [详细]
  • 在C#和ASP.NET开发中,TypeParse 是一个非常实用的类型解析扩展方法库,提供了简便的类型转换功能。例如,通过 `var int1 = "12".TryToInt();` 可以将字符串安全地转换为整数,如果转换失败则返回0。此外,还支持更多复杂的类型转换场景,如 `var int2 = "22x".TryToInt();` 和 `var int3 = "3.14".TryToInt();`,确保了代码的健壮性和易用性。 ... [详细]
  • 为了深入了解某些测试框架的工作原理,并在培训中构建一个简单的测试框架,我系统地研究了 should.js 的源代码。本文将分享我的学习过程和分析结果,帮助读者更好地掌握 should.js 的核心机制。 ... [详细]
  • 本文探讨了在PowerShell中高效管理和操作大规模内存对象的技术与实践。详细介绍了如何启用PowerShell的大内存支持功能,并提供了优化性能和减少资源消耗的具体方法。此外,还讨论了常见问题及其解决方案,旨在帮助用户在处理复杂数据集时提高效率和稳定性。 ... [详细]
  • 深入解析Spring框架中的双亲委派机制突破方法
    在探讨Spring框架中突破双亲委派机制的方法之前,首先需要了解类加载器的基本概念。类加载器负责将类的全限定名转换为对应的二进制字节流。每个类在被特定的类加载器加载后,其唯一性得到保证。然而,这种机制在某些场景下可能会限制灵活性,因此Spring框架提供了一些策略来突破这一限制,以实现更加动态和灵活的类加载。这些策略不仅能够提升系统的可扩展性,还能在复杂的运行环境中确保类的正确加载和管理。 ... [详细]
  • 探讨 `org.openide.windows.TopComponent.componentOpened()` 方法的应用及其代码实例分析 ... [详细]
  • 在Unity3D的第13天学习中,我们深入探讨了关节系统和布料模拟技术。关节系统作为Unity中的关键物理组件,能够实现游戏对象间的动态连接,如刚体间的关系、门的开合动作以及角色的布娃娃效果。铰链关节涉及两个刚体的交互,能够精确模拟复杂的机械运动,为游戏增添了真实感。此外,布料模拟技术则进一步提升了角色衣物和环境装饰物的自然表现,增强了视觉效果的真实性和沉浸感。 ... [详细]
  • IIS 7及7.5版本中应用程序池的最佳配置策略与实践
    在IIS 7及7.5版本中,优化应用程序池的配置是提升Web站点性能的关键步骤。具体操作包括:首先定位到目标Web站点的应用程序池,然后通过“应用程序池”菜单找到对应的池,右键选择“高级设置”。在一般优化方案中,建议调整以下几个关键参数:1. **基本设置**: - **队列长度**:默认值为1000,可根据实际需求调整队列长度,以提高处理请求的能力。此外,还可以进一步优化其他参数,如处理器使用限制、回收策略等,以确保应用程序池的高效运行。这些优化措施有助于提升系统的稳定性和响应速度。 ... [详细]
  • 多种实现 Windows 定时自动执行任务的专业技巧与方案
    在Windows系统中,实现定时自动执行任务有多种专业技巧和方案。常见的方法包括:使用Windows任务计划程序、开发Windows服务以及利用SQL Server Agent作业。这些方法被广泛应用于各种自动化场景,多数技术人员对此都有所了解。 ... [详细]
author-avatar
W于小北B
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有