• 近期将进行后台系统升级,如有访问不畅,请稍后再试!
  • 极客文库-知识库上线!
  • 极客文库小编@勤劳的小蚂蚁,为您推荐每日资讯,欢迎关注!
  • 每日更新优质编程文章!
  • 更多功能模块开发中。。。

Spring AOP是什么?你都拿它做什么?


为什么会有面向切面编程(AOP)?我们知道 Java 是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时,只能在在每个对象里引用公共行为。这样做不便于维护,而且有大量重复代码。AOP的出现弥补了 OOP 的这点不足。

为了阐述清楚 Spring AOP,我们从将以下方面进行讨论:

  1. 代理模式
  2. 静态代理原理及实践
  3. 动态代理原理及实践
  4. Spring AOP 原理及实战

1. 代理模式


代理模式:为其他对象提供一种代理以控制对这个对象的访问。这段话比较官方,但我更倾向于用自己的语言理解:比如 A 对象要做一件事情,在没有代理前,自己来做;在对 A 代理后,由 A 的代理类 B 来做。代理其实是在原实例前后加了一层处理,这也是 AOP 的初级轮廓。

2. 静态代理原理及实践

静态代理模式:静态代理说白了,就是在程序运行前就已经存在代理类的字节码文件、代理类和原始类的关系在运行前就已经确定。废话不多说,我们看一下代码。为了方便阅读,博主把单独的 class 文件合并到接口中,读者可以直接复制代码运行:

package test.staticProxy;

// 接口
publicinterfaceIUserDao{
   voidsave();
   voidfind();
}

//目标对象
classUserDaoimplementsIUserDao{
   @Override
   publicvoidsave(){
       System.out.println(“模拟:保存用户!”);
   }
   @Override
   publicvoidfind(){
       System.out.println(“模拟:查询用户”);
   }
}

/**
 * 静态代理
 * 特点:
 * 2. 目标对象必须要实现接口
 * 2. 代理对象,要实现与目标对象一样的接口
*/
classUserDaoProxyimplementsIUserDao{

   // 代理对象,需要维护一个目标对象
   private IUserDao target = new UserDao();

   @Override
   publicvoidsave(){
       System.out.println(“代理操作: 开启事务…”);
       target.save();   // 执行目标对象的方法
       System.out.println(“代理操作:提交事务…”);
   }

   @Override
   publicvoidfind(){
       target.find();
   }
}

测试结果:


静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象。如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

3. 动态代理原理及实践

动态代理模式:动态代理类的源码是在程序运行期间,通过 JVM 反射等机制动态生成。代理类和委托类的关系是运行时才确定的。实例如下:

package test.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口
publicinterfaceIUserDao{
   voidsave();
   voidfind();
}

//目标对象
classUserDaoimplementsIUserDao{

   @Override
   publicvoidsave(){
       System.out.println(“模拟: 保存用户!”);
   }

   @Override
   publicvoidfind(){
       System.out.println(“查询”);
   }
}

/**
* 动态代理:
* 代理工厂,给多个目标对象生成代理对象!
*
*/
classProxyFactory{

   // 接收一个目标对象
   private Object target;

   publicProxyFactory(Object target){
       this.target = target;
   }

   // 返回对目标对象(target)代理后的对象(proxy)
   public Object getProxyInstance(){
       Object proxy = Proxy.newProxyInstance(
           target.getClass().getClassLoader(),  // 目标对象使用的类加载器
           target.getClass().getInterfaces(),   // 目标对象实现的所有接口
           new InvocationHandler() {            // 执行代理对象方法时候触发

               @Override
               public Object invoke(Object proxy, Method method, Object[] args)
                       throws Throwable {

                   // 获取当前执行的方法的方法名
                   String methodName = method.getName();
                   // 方法返回值
                   Object result = null;
                   if (“find”.equals(methodName)) {
                       // 直接调用目标对象方法
                       result = method.invoke(target, args);
                   } else {
                       System.out.println(“开启事务…”);
                       // 执行目标对象方法
                       result = method.invoke(target, args);
                       System.out.println(“提交事务…”);
                   }
                   return result;
               }
           }
       );
       return proxy;
   }

}


测试结果如下:


IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();

其实是 JDK 动态生成了一个类去实现接口,隐藏了这个过程:

class $jdkProxyimplementsIUserDao{}

使用 JDK 生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用 JDK 动态代理。所以 CGLIB 代理就是解决这个问题的。

CGLIB 是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类,如下:

CGLIB 使用的前提是目标类不能为 final 修饰。因为 final 修饰的类不能被继承。

现在,我们可以看看 AOP 的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑

通过定义和前面代码我们可以发现 3 点:

  • AOP 是基于动态代理模式。
  • AOP 是方法级别的。
  • AOP 可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。

4. Spring AOP

前文提到 JDK 代理和 CGLIB 代理两种动态代理。优秀的 Spring 框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?

  1. 创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
  2. 如果目标对象有实现接口,使用 JDK 代理。如果目标对象没有实现接口,则使用 CGLIB 代理。然后从容器获取代理后的对象,在运行期植入“切面”类的方法。通过查看 Spring 源码,我们在 DefaultAopProxyFactory 类中,找到这样一段话。

简单的从字面意思看出:如果有接口,则使用 JDK 代理,反之使用 CGLIB ,这刚好印证了前文所阐述的内容。Spring AOP 综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且 class 为 final 修饰的,则不能进行 Spring AOP 编程!

知道了原理,现在我们将自己手动实现 Spring 的 AOP:

package test.spring_aop_anno;

import org.aspectj.lang.ProceedingJoinPoint;

publicinterfaceIUserDao{
   voidsave();
}

// 用于测试 CGLIB 动态代理
classOrderDao{
   publicvoidsave(){
       //int i =1/0; 用于测试异常通知
       System.out.println(“保存订单…”);
   }
}

//用于测试 JDK 动态代理
classUserDaoimplementsIUserDao{
   publicvoidsave(){
       //int i =1/0; 用于测试异常通知
       System.out.println(“保存用户…”);
   }
}

//切面类
classTransactionAop{

   publicvoidbeginTransaction(){
       System.out.println(“[前置通知]  开启事务..”);
   }

   publicvoidcommit(){
       System.out.println(“[后置通知] 提交事务..”);
   }

   publicvoidafterReturing(){
       System.out.println(“[返回后通知]”);
   }

   publicvoidafterThrowing(){
       System.out.println(“[异常通知]”);
   }

   publicvoidarroud(ProceedingJoinPoint pjp)throws Throwable {
       System.out.println(“[环绕前:]”);
       pjp.proceed(); // 执行目标方法
       System.out.println(“[环绕后:]”);
   }
}

Spring 的 XML 配置文件:

<?xml version=“1.0” encoding=“UTF-8”?>
   xsi:schemaLocation=
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd”>
   <!– dao 实例加入容器 –>
   <beanid=“userDao”class=“test.spring_aop_anno.UserDao”></bean>

   <!– dao 实例加入容器 –>
   <beanid=“orderDao”class=“test.spring_aop_anno.OrderDao”></bean>

   <!– 实例化切面类 –>
   <beanid=“transactionAop”class=“test.spring_aop_anno.TransactionAop”></bean>

   <!– Aop 相关配置 –>
   <aop:config>
       <!– 切入点表达式定义 –>
       <aop:pointcutexpression=“execution(* test.spring_aop_anno.*Dao.*(..))”id=“transactionPointcut”/>
       <!– 切面配置 –>
       <aop:aspectref=“transactionAop”>
           <!– 【环绕通知】 –>
           <aop:aroundmethod=“arroud”pointcut-ref=“transactionPointcut”/>
           <!– 【前置通知】 在目标方法之前执行 –>
           <aop:beforemethod=“beginTransaction”pointcut-ref=“transactionPointcut” />
           <!– 【后置通知】 –>
           <aop:aftermethod=“commit”pointcut-ref=“transactionPointcut”/>
           <!– 【返回后通知】 –>
           <aop:after-returningmethod=“afterReturing”pointcut-ref=“transactionPointcut”/>
           <!– 异常通知 –>
           <aop:after-throwingmethod=“afterThrowing”pointcut-ref=“transactionPointcut”/>
       </aop:aspect>
   </aop:config>
</beans>

切入点表达式不在这里介绍。参考 Spring AOP 切入点表达式

代码的测试结果如下:

到这里,我们已经全部介绍完 Spring AOP。回到开篇的问题,我们拿它做什么?

  1. Spring 声明式事务管理配置:请参考博主的另一篇文章:分布式系统架构实战 demo:SSM+Dubbo
  2. Controller 层的参数校验:参考 Spring AOP 拦截 Controller 做参数校验
  3. 使用 Spring AOP 实现 MySQL 数据库读写分离案例分析
  4. 在执行方法前,判断是否具有权限
  5. 对部分函数的调用进行日志记录:监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。
  6. 信息过滤,页面转发等等功能

博主一个人的力量有限,只能列举这么多,欢迎评论区对文章做补充。

Spring AOP 还能做什么,实现什么魔幻功能,就在于我们每一个平凡而又睿智的程序猿!

参考文章
  • Spring AOP 切入点表达式:http://blog.csdn.net/keda8997110/article/details/50747923
  • 分布式系统架构实战 demo:SSM+Dubbo:https://my.oschina.net/liughDevelop/blog/1480061
  • Spring AOP 拦截 Controller 做参数校验:https://my.oschina.net/liughDevelop/blog/1480061
  • 使用 Spring AOP 实现 MySQL 数据库读写分离案例分析:http://blog.csdn.net/xlgen157387/article/details/53930382


丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:Spring AOP 是什么?你都拿它做什么?
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

欢迎 注册账号 登录 发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00