最近做一个数据库分离的功能,其中用到了spring aop,主要思路就是在service层的方法执行前根据注解(当然也可以根据方法名称,如果方法名称写的比较统一的话)来判断具体使用哪个库。所以想着再回头来看看aop的详细用法。
因为spring aop的话原理涉及到动态代理,了解动态代理的可以查看我的另一篇博客:java动态代理
这里主要学习讨论spring aop的用法和注意事项。
AOP(Aspect Oriented Programming),即面向切面编程。我们知道在学习java的时候有一个OOP(Object Oriented Programming,面向对象编程),其中还涉及到它的一些特性:封装,继承,多态。这些其实就是定义了类本身以及类和类之间的关系。这里不再赘述。那么AOP是用在什么场合呢?
下面的例子可以大致说明面向切面编程的一个场景,代码如下:
1 public class User { 2 private int age; 3 private String name; 4 public int getAge() { 5 return age; 6 } 7 public void setAge(int age) { 8 this.age = age; 9 } 10 public String getName() { 11 return name; 12 } 13 public void setName(String name) { 14 this.name = name; 15 } 16 }
这里User就是一个普通的pojo类,现在假设有个需求说如果user对象为空,则进行判空处理,否则还是正常处理。这个时候有两种解决思路:一种是在每个方法里面判断,代码如下:
1 public class User { 2 private int age; 3 private String name; 4 private User user; 5 public int getAge() { 6 if(user == null){ 7 return 0; 8 } 9 return age; 10 } 11 public void setAge(int age) { 12 this.age = age; 13 } 14 public String getName() { 15 if(user == null){ 16 return "user is null"; 17 } 18 return name; 19 } 20 public void setName(String name) { 21 this.name = name; 22 } 23 }
如果方法很多个,那么就要在每个方法里面判断user是否为空,这个比较麻烦,而且容易出错。另一种方式是面向切面。就是在执行User类方法的前面先判断,这样的话判断条件就比较集中。具体怎么判断呢?先来看一下aop的具体知识点。
AOP它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象,所以切面一般对应一个类
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
单纯的记忆这些概念不好记,下面会根据具体实例来一一对号。
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
- 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
- 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
- 定义普通业务组件
- 定义切入点,一个切入点可能横切多个业务组件
- 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
使用Spring AOP,要成功运行起代码,只用Spring提供给开发者的jar包是不够的,请额外上网下载两个jar包:
- aopalliance.jar
- aspectjweaver.jar
1 public interface Output { 2 void out(); 3 }
定义接口的实现类Subject:
1 public class Subject implements Output{ 2 @Override 3 public void out(){ 4 System.out.println("被代理对象的输出"); 5 } 6 }
接着定义一个切面AopAspect,切面可以看做是切入点,通知等的抽象集合。
1 public class AopAspect { 2 public void before() { 3 System.out.println("方法执行前"); 4 } 5 public void after() { 6 System.out.println("方法执行后"); 7 } 8 }
然后在xml中配置如下:
1 <bean id="subject" class="aop.Subject"></bean> 2 <bean id="aopAspect" class="aop.AopAspect"></bean> 3 <aop:config> 4 <aop:aspect id="aopAspect" ref="aopAspect"> 5 <aop:pointcut id="allMethod" expression="execution(* aop.Subject.*(..))"/> 6 <aop:before method="before" pointcut-ref="allMethod"></aop:before> 7 <aop:after method="after" pointcut-ref="allMethod"></aop:after> 8 </aop:aspect> 9 </aop:config>
其实在xml中能够更清晰的对应相应的概念:
横切关注点:这里就是对Subject类的out()方法进行拦截,在out()方法前面执行before(),之后执行after(),这些成为横切关注点
切面(aspect):很明显,就是AopAspect
连接点(joinpoint):被拦截到的点,这里是一个方法,也就是out()方法
切入点(pointcut):在哪里进行拦截,这里定义的是Sub ject类下面的所有方法。这里有一个expression表达式来描述拦截的地方:常用的就是execution,这里的表达式的含义如下:
通知(advie):拦截到连接点之后要执行的代码,这里就是before()和after()了
目标对象:就是Subject
织入:切面应用到目标对象,就是把切面AopAspect织入到Subject中
使用Spring AOP的其他细节
1、如果在加入一个切面,那么这两个切面的执行顺序是怎样的?
- 默认以aspect的定义顺序作为织入顺序
- aspect里面有一个order属性,order属性的数字就是横切关注点的顺序
2、强制使用CGLIB生成代理
Spring使用动态代理或是CGLIB生成代理是有规则的,高版本的Spring会自动选择是使用动态代理还是CGLIB生成代理内容,当然我们也可以强制使用CGLIB生成代理,那就是<aop:config>里面有一个"proxy-target-class"属性,这个属性值如果被设置为true,那么基于类的代理将起作用,如果proxy-target-class被设置为false或者这个属性被省略,那么基于接口的代理将起作用。