最近做一个数据库分离的功能,其中用到了spring aop,主要思路就是在service层的方法执行前根据注解(当然也可以根据方法名称,如果方法名称写的比较统一的话)来判断具体使用哪个库。所以想着再回头来看看aop的详细用法。
因为spring aop的话原理涉及到动态代理,了解动态代理的可以查看我的另一篇博客:java动态代理
这里主要学习讨论spring aop的用法和注意事项。
AOP(Aspect Oriented Programming),即面向切面编程。我们知道在学习java的时候有一个OOP(Object Oriented Programming,面向对象编程),其中还涉及到它的一些特性:封装,继承,多态。这些其实就是定义了类本身以及类和类之间的关系。这里不再赘述。那么AOP是用在什么场合呢?
下面的例子可以大致说明面向切面编程的一个场景,代码如下:
public class User {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这里User就是一个普通的pojo类,现在假设有个需求说如果user对象为空,则进行判空处理,否则还是正常处理。这个时候有两种解决思路:一种是在每个方法里面判断,代码如下:
public class User {
private int age;
private String name;
private User user;
public int getAge() {
if(user == null){
return 0;
}
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
if(user == null){
return "user is null";
}
return name;
}
public void setName(String name) {
this.name = name;
}
}
如果方法很多个,那么就要在每个方法里面判断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
这里举一个简单的例子,首先定义一个接口Output:
public interface Output {
void out();
}
定义接口的实现类Subject:
public class Subject implements Output{
@Override
public void out(){
System.out.println("被代理对象的输出");
}
}
接着定义一个切面AopAspect,切面可以看做是切入点,通知等的抽象集合。
public class AopAspect {
public void before() {
System.out.println("方法执行前");
}
public void after() {
System.out.println("方法执行后");
}
}
然后在xml中配置如下:
<bean id="subject" class="aop.Subject"></bean>
<bean id="aopAspect" class="aop.AopAspect"></bean>
<aop:config>
<aop:aspect id="aopAspect" ref="aopAspect">
<aop:pointcut id="allMethod" expression="execution(* aop.Subject.*(..))"/>
<aop:before method="before" pointcut-ref="allMethod"></aop:before>
<aop:after method="after" pointcut-ref="allMethod"></aop:after>
</aop:aspect>
</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或者这个属性被省略,那么基于接口的代理将起作用。