Spring中的AOP(四)

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为”横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为”Aspect”,即切面。所谓”切面”,简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用”横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

一、Spring AOP

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

二、AOP核心概念
  1. 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
  2. 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。
  3. 连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
  4. 切入点(pointcut):对连接点进行拦截的定义。
  5. 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
  6. 目标对象:代理的目标对象。
  7. 织入(weave):将切面应用到目标对象并导致代理对象创建的过程。
  8. 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
三、Demo
  • 导入aop模块,Spring AOP:(spring-aspects);
1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
  • 定义一个业务逻辑类MathCalculator,在业务逻辑运行的时候将日志进行打印(方法之前、方法之后、方法出现异常等);
1
2
3
4
5
6
7
public class MathCalculator {

public int div(int i,int j){
System.out.println("MathCalculator...div...");
return i/j;
}
}
  • 定义一个切面类LogAspects,切面类里面的方法需要动态感知MathCalculator.div运行到哪里,然后执行;
  • 给切面类的目标方法标注何时何地运行(相应的通知注解);
1
2
3
4
5
6
这些方法就是通知方法:
前置通知(@Before):logStart,在目标方法MathCalculator.div运行之前运行
后置通知(@After):logEnd,在目标方法MathCalculator.div运行结束之后运行(无论方法正常结束还是异常结束都会被调用)
返回通知(@AfterReturning):logReturn,在目标方法MathCalculator.div正常返回之后运行
异常通知(@AfterThrowing):logException,在目标方法MathCalculator.div出现异常之后运行
环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LogAspects {

//抽取公共的切入点表达式
//1、本类引用:直接调用切入点表达式方法pointcut()即可
//2、其它切面引用:直接调用切入点表达式方法com.jt.aop.LogAspects.pointcut()即可
@Pointcut("execution(public int com.jt.aop.MathCalculator.*(..))")
public void pointcut(){}

//@Before在目标方法之前切入,切入点表达式(指定在"public int com.jt.aop.MathCalculator.div(int, int)"切入)
//JoinPoint作为参数时必须放在参数表的第一个位置
@Before("pointcut()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println(joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
}

@After("pointcut()")
public void logEnd(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature().getName()+"结束。。。@After");
}

//returning指定通过某个参数封装返回值
@AfterReturning(value="pointcut()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
System.out.println(joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
}

//throwing指定通过某个参数封装异常信息
@AfterThrowing(value="pointcut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println(joinPoint.getSignature().getName()+"异常。。。@AfterThrowing:异常信息:{"+exception.getMessage()+"}");
}
}
  • 将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class MainConfigOfAOP {

//业务逻辑类
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}

//注册切面类
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
  • 必须告诉Spring哪个类是切面类(给切面类加上注解@Aspect);
1
2
3
4
5
6
7
8
9
/**
* 切面类
*
* @Aspect:告诉Spring这是一个切面类
* */
@Aspect
public class LogAspects {
//...
}
  • 给配置类中增加@EnableAspectJAutoProxy【开启基于注解的aop模式】,Spring中有很多@Enablexxx的注解具有相应的功能;
1
2
3
4
5
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
//...
}
  • Junit Test;
1
2
3
4
5
6
7
8
9
@Test
public void test01(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
//必须从容器中获取mathCalculator对象,而不是直接new一个对象
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(1, 0);

applicationContext.close();
}
四、Spring AOP三步走
  • 将业务逻辑组件和切面类都加入到容器中,告诉Spring相应的切面类(@Aspect)。
  • 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)。
  • 开启基于注解的aop模式:@EnableAspectJAutoProxy。

文首参考:http://www.cnblogs.com/hongwz/p/5764917.html

-------------本文结束感谢您的阅读-------------