Spring学习之AOP基础
前言
最近在学Spring,这两天碰到AOP这个概念,一开始不是很理解其背后的思想,经过这两天的学习,基本上大致理解了其含义以及目的,故将学习过程的笔记整理出来,以供日后回顾使用,以及与各位正在学习Spring的朋友分享
AOP的介绍
AOP,全程是Apsect Orientation Programming,翻译过来就是面向切面的编程,说到面向切面,首先需要谈到的就是OOP,也就是比较熟悉的面向对象编程,在面向对象编程中,当重复性的代码出现比较多次的时候,一般我们就会将公共部分其抽取出来,形成父类,这样,通过继承的手段,就能极大地减少代码的重复。然而,在实际开发过程中却有一些代码是冗余是,但是通过面向对象开发的方式却是无法将其抽取出来,比如说,上一篇文章中提到的,日志管理,或者常用的事务管理等,这些类型的代码有具有一个很明显的特点,那就是他们紧紧围绕在业务代码,或者说目标代码的周围(前/后),这种形式的代码采用面向对象的方式是无法进行抽取的。于是,需求诞生了解决方案,面向切面的方式就开始投入了使用。
所谓的面向切面编程,其实就是从横向的角度来处理冗余的代码(面向对象的编程方式基本都是纵向的角度),比如说,日志管理,由于日志管理代码紧紧围绕在业务代码中,从横向的角度来看这些代码,日志管理代码就好像是一层外衣,紧紧地包围着业务代码,而面向切面编程的核心思想,就是将这些具有横向特征的代码抽取出来,并且将他们集中在特定的类中,然后通过织入的方式,将他们织入到对应的业务方法中,这样子,这些代码就好像是一层独立与业务代码的代码了,而将他们抽取出来并且在需要的时候将他们整合进去的方式,就是面向切面编程了。
这里需要注意一点,面向切面编程的出现,不是要替代面向对象编程,而是对其进行补充,扩展面向对象编程的能力。在前面的一小节,动态代理中也提到了,动态代理技术是Spring中AOP应用的背景,也就是说,Spring是通过动态代理技术来实现面向切面编程的,有了动态代理技术的基础,接下来我们就来学习面向切面编程
AOP编程常用术语
在具体学习AOP编程之间,有几个比较重要的概念需要弄清楚,这几个概念是面向切面编程的基础,也是其核心。
- 连接点 Jointpoint
- 所谓的连接点,指的是代码中可以进行增强的点,比如说方法调用前、方法调用后、方法调用前后、类初始化前、类初始化后、异常抛出后
- 切点 Cutpoint
- 切点,是指要进行增强的点,这里需要注意区分连接点和切点,连接点是所有符合条件的点,而切点是所要进行织入的点,连接点包含了切点,但同时一个切点可能匹配多个连接点
- 增强 Advice
- 增强,指的是所要在切点上执行的代码,也就是具体的想要增强的代码,一般还包含了方位信息(方法前/后、返回等,但不包含具体的方法信息,也就是不包含切点信息)
- 目标类 Target
- 目标类,指的是所要进行增强的类,这个比较好理解
- 引介 Introduction:
- 引介,一种特殊的增强,主要作用于类级别上,通常用于为类动态实现接口等的操作,也就是作用于类上的增强
- 织入 Weaving
- 织入,指的是将通过切点信息,将对象的增强添加到目标类的过程
- 代理 Proxy
- 代理,这个比较好理解,指的就是通过增强之后所产生的对象,也就是原来目标类经过增强之后的代理类
- 切面 Aspect:
- 切面,由切点和增强组成,包含了横切逻辑和方位的定义,一个切点可以描述多个连接点,加上增强,就形成了面,也就是所谓的切面了。
原始的Spring AOP编程方式
这里通过比较原始的Spring AOP编程方式来详细讲解上面所提到的概念,以增强对上面的概念的认识,需要注意的是,通过这种方式的目的是为了更好地理解AOP编程的概念,在日常的开发过程中,由于这种方式比较底层,而且操作麻烦,基本是不怎么使用的,还有一点需要注意的是,Spring仅支持方法的增强,也就是说,Spring中的切点只能描述方法,增强只能作用在方法上。
/**
* 方法调用前增强
*/
class LogManager implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("log something");
}
}
/**
* UserService接口
*/
interface UserService{
void login();
void logout();
}
/**
* UseService实现类
*/
class UserServiceImpl implements UserService{
@Override
public void login() {
System.out.println("someone login....");
}
@Override
public void logout() {
System.out.println("someone logout....");
}
}
对应的Spring配置文件如下
<bean id="userServiceTarget" class="cn.xuhuanfeng.aop.UserServiceImpl"/>
<bean id="advice" class="cn.xuhuanfeng.aop.LogManager"/>
<!--指定代理类,用于为目标类进行增强-->
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置对应的目标类,也就是要对其进行增强的类-->
<property name="target" ref="userServiceTarget"/>
<!--配置对应的增强类-->
<property name="interceptorNames" value="advice"/>
<!--是否使用CGLib动态代理,如果没有配置接口,则默认是-->
<property name="optimize" value="true"/>
</bean>
测试结果如下所示
log something
someone login....
log something
someone logout....
从上面的结果可以看出,我们已经成功为UserService的方法配置增强,并且UserService对此并不知情
接下来再看另外几个例子
/**
* 方法调用后增强
*/
class LogManagerAfter implements AfterReturningAdvice{
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("after returning");
}
}
/**
* 环绕增强
*/
class LogManagerAround implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("before ...");
Object object = methodInvocation.proceed();
System.out.println("after ...");
return object;
}
}
在配置文件中进行配置的具体代码基本同上面的内容,这里就不进行展示了,从上面的内容可以看出,如果想对一个对象应用AOP方式进行增强,首先需要为其定义对应的增强,同时为其描述对应的切点(上面没有明确指出是切点,所以默认是对所有的方法进行增强),以及对应的目标类,然后通过Spring的ProxyFactorybean来负责进行织入,产生对应的增强类。
不过,从上面的声明增强的方式中,我们也可以看出通过这种方式配置的缺点
- 只能通过硬编码指定增强,并且需要为每个需要增强的类创建增强类
- 无法明确指定切点,只能通过硬编码进行编写,且编写过程繁琐
由于通过这种方式的配置有着无法忍受的缺点,所以一般我们在实际开发过程中也没有这么做,至于一般怎么做,将在后面两个小节进行介绍
总结
由于面向对象编程本身存在着的不可避免的问题,于是提出了面向切面编程的概念,并且,随着前辈们的努力,AOP编程技术现在已经变得非常成熟了。在AOP中编程中,有几个比较重要的概念,切点,增强 ,引介,切面,织入,只有对这几个概念比较熟悉,才能更好地使用AOP技术来提高效率,在原始的Spring AOP技术中,如果需要声明一个增强,则需要实现对应的接口,这种方式是不太方便的,因为通过这种方式没有办法明确指定对应的切点,而且基本上必须为需要增强的对象编写对应的增强,在后面我们将看到,经过努力之后的Spring AOP配置方式的简便性。
网友评论