AOP和spring AOP
什么是AOP?面向切面编程。要想理解面向切面编程是什么,得先回答出下面4个问题。
1什么是面向切面编程技术?
2为什么要有面向切面编程技术?
3它和面向对象编程有什么关系?
4什么是spring AOP?
先来看第二个问题:为什么要有面向切面编程技术?
假如我们在开发自己的业务逻辑时,公司规定,所有业务逻辑都需要加上日志记录、权限验证,那么我们的代码就可能如下:
class myservice{
日志对象 日志;
日志.记录日志();
自己的业务逻辑…
权限对象 权限;
权限.权限验证();
}
我们会发现,我们每写一个业务类,都要加上这些重复的代码!造成了代码冗余和业务逻辑混乱,有点搞不清我们这个业务类的主要功能了。所以,就出现了面向切面编程技术,把这些非核心的重复的业务逻辑交给一个统一的模块来管理!
我们希望我们自己的业务类都长成这样:
class myservice{
自己的业务逻辑…
}
但是在调用自己的业务时,那些非核心业务也可以执行!
再来看第一个问题:什么是面向切面编程技术?
这个技术的功能就是上面说的:我不写,但是我执行时你也要一起执行。说得具体一点,面向切面编程技术就是在执行主业务逻辑时,找到特定的时机,和切入点去执行非核心业务逻辑。我们把这样的时机、切入点的集合叫做切面。
再看第三个问题:它和面向对象编程有什么关系?
OOP让编程模块化,一个业务类解决一种问题,AOP把涉及多个模块的某一类问题统一管理。
最后看第四个问题:Spring AOP是什么?AOP是一种效果,Spring AOP是达到这种效果的一种技术,还有AspectJ技术。
总结:什么是AOP?AOP也叫面向切面编程,它解决外围业务代码与核心业务代码分离的问题,把涉及多个模块的非核心业务代码统一管理,使得在核心业务代码中不显示的写这些非核心业务代码,却在执行核心代码的时候找到一定的时机和切入点去执行非核心业务代码。
比如myservice{ 主业务逻辑代码 },这里面根本没有日志记录,权限验证的代码,但是执行myservice中的代码时就是可以执行日志记录和权限验证的操作,也就是一种我不写,你也要执行的流氓技术。
spring AOP的小案例
先来看spring AOP中的核心概念:
1切面(Aspect):是一个类,里面定义了通知与切点,统一管理执行非核心业务的时机、切入点、非核心业务逻辑的实现的类。
2切点(PointCut):表达式。就是告诉程序要在执行哪些核心业务的时候,执行非核心的业务。
3通知(advice):五种通知方式:这些注解标注在方法上,我们称这些方法叫任务。
@Before:前置通知,在调用目标方法之前执行通知定义的任务
@After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务
@After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
@After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
@Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务。
//我们来定义一个切面类
@Aspect//要用这个注解来注释切面类
public class MyAspect {
//再来定义切点PointCut,这是一个表达式,很复杂,我们记录几个常用的例子。
//匹配ProductService类里头的所有方法,也就是执行这些方法时就会产生下面的切入点,有切入点就可能会执行通知。
@Pointcut(“within(com.awakening.service.ProductService)”)
public void matchType(){}//这个方法和@Pointcut是一体的,matchType()就代表上面的表达式within(com.awakening.service.ProductService),不然太难写了。
//匹配所有以Service结尾的bean里头的方法
@Pointcut(“bean(Service)")
public void beanDemo(){}
//再定义两个不知道啥意思的切点,切点是要和通知配合在一起使用的。
@Pointcut("execution( springMVCmybatis…addController.addEmp(…))”)
private void pointCutMethod() { }
@Pointcut("execution(* springMVCmybatis.com.my.aop.UserServiceImp.(…))")
private void testAOP() {}
//定义一些通知
/
* 声明前置通知 ,JoinPont是srpring提供的静态变量,
* 通过joinPoint参数可以获得目标方法的类名,方法参数,方法名等信息,这个参数可有可无。
*/
@Before("pointCutMethod() || testAOP()") //通知要配合切点一起使用。
public void doBefore(JoinPoint joinPoint) {
System.out.println("@Before:开始添加--order=3");
}
//声明后置通知 ,如果result的类型与proceed执行的方法返回的参数类型不匹配那么就不会执行这个方法
@AfterReturning(pointcut = "pointCutMethod() || testAOP()", returning = "result")
public void doAfterReturning(String result) {
System.out.println("@AfterReturning:后置通知--order=3");
System.out.println("---" + result + "---");
}
//声明例外通知
@AfterThrowing(pointcut = "pointCutMethod() || testAOP()", throwing = "e")
public void doAfterThrowing(Exception e) {
System.out.println("@AfterThrowing:例外通知--order=3");
System.out.println(e.getMessage());
}
//声明最终通知
@After("pointCutMethod() || testAOP()")
public void doAfter() {
System.out.println("@After:最终通知--order=3");
}
/*
* 声明环绕通知
* 参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,
* proceed()的返回值就是环绕通知的返回值,proceedingJoinPoint是个接口,
* implement JoinPoint,所以也可以获得目标函数的类名,方法名等参数。
*/
@Around("pointCutMethod() || testAOP()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around:进入方法---环绕通知--order=3");
Object o = pjp.proceed();
System.out.println("@Around:退出方法---环绕通知--order=3");
return o;
}
}
这么多通知,当通知都满足切入点的条件,谁先执行?
正常情况下的执行顺序:环绕通知–前置通知–主业务–环绕通知–后置通知–后置且需要成功通知。
主业务执行出了异常的情况:环绕通知—前置通知–主业务–后置通知–后置且出了异常的通知。
切入点详解:切入点是一个表达式,一般来说都是在主业务执行某个方法时就是一个切入点,所以切入点一般都是方法,主业务中的方法,我们怎么用一个表达式表示多个方法呢?
execution(<修饰符模式>?<返回类型模式><方法所在类的完全限定名称模式>(<参数模式>)<异常模式>?)
execution(modifiers-pattern? ret-type-pattern fully-qualified-class-name (param-pattern) throws-pattern?)
其实如果单纯的给定这个表达式还是不容易记忆,下面对比方法的定义来记忆,一个java方法的全部定义方式可以表示成下面的方式:
public String springMVCmybatic.com.my.aop.UserServiceImp(String a, int b) throw Exception{
}
modifier-pattern?:表示方法的修饰符,可有可无;对应的就是 public
ret-type-pattern:表示方法的返回值;对应的就是 String
fully-qualified-class-name 方法所在类的完全限定名称;对应的就是 springMVCmybatic.com.my.aop.UserServiceImp
param-pattern:表示方法的参数;对应的就是 String a, int b
throws-pattern:表示方法抛出的异常,可有可无;对应的就是 throw Exception