热门标签 | 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之增强介绍


推荐阅读
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 本文探讨了如何通过预处理器开关选择不同的类实现,并解决在特定情况下遇到的链接器错误。 ... [详细]
  • Nginx 反向代理与负载均衡实验
    本实验旨在通过配置 Nginx 实现反向代理和负载均衡,确保从北京本地代理服务器访问上海的 Web 服务器时,能够依次显示红、黄、绿三种颜色页面以验证负载均衡效果。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本文深入探讨了SQL数据库中常见的面试问题,包括如何获取自增字段的当前值、防止SQL注入的方法、游标的作用与使用、索引的形式及其优缺点,以及事务和存储过程的概念。通过详细的解答和示例,帮助读者更好地理解和应对这些技术问题。 ... [详细]
  • 目录一、salt-job管理#job存放数据目录#缓存时间设置#Others二、returns模块配置job数据入库#配置returns返回值信息#mysql安全设置#创建模块相关 ... [详细]
  • 本文介绍 SQL Server 的基本概念和操作,涵盖系统数据库、常用数据类型、表的创建及增删改查等基础操作。通过实例帮助读者快速上手 SQL Server 数据库管理。 ... [详细]
  • 本文介绍如何在Spring Boot项目中集成Redis,并通过具体案例展示其配置和使用方法。包括添加依赖、配置连接信息、自定义序列化方式以及实现仓储接口。 ... [详细]
  • 本文介绍了如何利用Python进行批量图片尺寸调整,包括放大和等比例缩放。文中提供了详细的代码示例,并解释了每个步骤的具体实现方法。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • This request pertains to exporting the hosted_zone_id attribute associated with the aws_rds_cluster resource in Terraform configurations. The absence of this attribute can lead to issues when integrating DNS records with Route 53. ... [详细]
  • 本文介绍了如何在多线程环境中实现异步任务的事务控制,确保任务执行的一致性和可靠性。通过使用计数器和异常标记字段,系统能够准确判断所有异步线程的执行结果,并根据结果决定是否回滚或提交事务。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • 本文详细介绍了如何使用 PHP 接收并处理微信支付的回调结果,确保支付通知能够被正确接收和响应。 ... [详细]
  • 本文详细介绍了Grand Central Dispatch (GCD) 的核心概念和使用方法,探讨了任务队列、同步与异步执行以及常见的死锁问题。通过具体示例和代码片段,帮助开发者更好地理解和应用GCD进行多线程开发。 ... [详细]
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社区 版权所有