Spring AOP
Spring AOP当中的一些概念
什么是AOP
Aspect Oriented Programming 面向切面编程,在传统的oop开发过程中,逻辑是自上而下的,譬如我们实现一个登录功能,浏览器发起http请求到controller,controller负责接受请求,封装参数、验证参数等等,继而把数据传给service,service处理完之后调用dao,dao负责操作db后返回。这个过程当中会产生很多横切性问题,比如controller当中的日志记录、比如service当中的权限校验、比如dao当中的事务处理。这些横切性的问题和主业务逻辑是没有关系的,比如日志的记录失败与否并不会影响整个登录逻辑,这些横切的问题如果不去处理会散落在代码的各个地方,面向切面编程就是我们程序员编码只关心横切性问题的抽象。
什么事 Spring AOP
aop面向切面编程是一种编程目标,说白了是一个标准,而实现这种编程标准的技术手段有很多,比如你可以使用JDK动态代理来实现AOP,也可以使用aspectj这种静态编程技术来实现代理从而实现aop。Spring AOP就是实现AOP的一种技术手段,相当于AOP=发财(两种都是一种目的)。Spring AOP=卖肾,发财除了卖肾当然还可以有其他很多路子,比如可以努力工作也可以发财,所以实现AOP除了Spring AOP市面上还有很多技术来实现AOP
Spring AOP的核心原理
对于上述的横切性问题spirng的做法是把这些问题集中到一个切面(Aspect)当中,然后利用动态代理技术动态增强业务Bean。
Spring AOP的核心技术是JDK动态代理和CGLIB动态代理,加上spring内部的BeanPostProcessor一起工作,从而达到了对spring当中Bean完成增强
Spring AOP当中的专业术语
join point
连接点:程序执行当中的一个点。他是一个最终的结果,因为Spring AOP是最小连接单位的是一个方法,故而一个切点就是一个方法,(但是是被连接了的方法)
point cut
切点:一组连接点的集合。因为在开发 中可能连接点可能会很多,根据业务和功能的不同会进行分组,切点就是一组连接点的描述(相当于连接点=一条数据,切点=一张表,一个数据库当中可以存在多张表,表可以有多条数据)
Advice
通知:通知可以从三个纬度来理解。
1、通知的内容:也就是你增强一个方法的具体业务逻辑,比如日志记录,比如事务操作。
2、通知的时机:Before Advice、After Advice、After throwing Advice、After(finally) Advice、Around Advice
3、通知的目标:也就是这个通知需要作用到哪个或者哪些连接点上
Introduction
导入:Spring AOP可以在不修改代码的情况下让某个类强制实现某个接口,并且可以指定接口的默认实现方法
Target object
目标对象:由于Sping AOP是借助CGLIB或者JDK动态代理来实现增强的,目标对象的概念和动态代理当中的那个目标对象是一个概念----被代理的那个对象
AOP proxy
代理对象:一个被JDK或者CGLIB动态代理之后的对象
Weaving
织入:一个过程。把切面和应用程序对象连接的过程
Aspect
切面:上述所有概念在编码过程中存在的类成为切面、切点、连接点、通知等等存在的类称为一个切面
Spring AOP开发
1、启动@AspectJ
支持使用java Configuration启用@AspectJ支持,需要添加@AspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
使用xml配置启动@AspectJ
<aop:aspectj-autoproxy/>
2、声明一个Aspect
声明一个@Aspect注解类,并且定义为一个bean交给Spring管理
@Component
@Aspect
public class UserAspect {
}
3、声明一个pointCut
切入点表达式由@Pointcut注解表示,切入点声明有两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,该表达式确定我们对哪个方法执行感兴趣
切入点确定感兴趣的join points(连接点),从而使我们能够控制何时执行通知。Spring AOP只支持Spring Bean的方法执行join points(连接点),所以可以将切入点看做是匹配Spring Bean上方法的执行
@Aspect
@Component
public class UserAspect {
@Pointcut("execution(* transfer(..))")// 切入点表达式
private void pointcut() { //切入点签名
}
}
4、声明一个Advice通知
advice通知与pointcut切入点表达式相关联,并在切入点匹配的方法执行@Before之前,@After之后或前后运行
// 申明Aspect,并且交给spring管理
@Aspect
@Component
public class UserAspect {
/**
* 声明切入点,匹配User到所有的方法调用
* execution 匹配方法执行连接点
* within:将撇撇限制为特定的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* transfer(..))")// 切入点表达式
private void pointcut() { //切入点签名
}
/**
* 声明before通知,在pointcut切入点前执行
* 通知与切入点表达式相关联
* 并且切入点匹配的方法执行之前,之后或者前后运行
* 切入点表达式可以是指定切入点的简单引用,也可以是在适当的位置声明切入点表达式
*/
@Before("pointCut()")
public void adviceBefore() {
log.info("before aop");
}
}
各种连接点joinpoint的意义
1、execution
用于匹配方法执行join points连接点,最小粒度方法,在aop中主要使用
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号标识当前项目可以有也可以没有,其中各项的语义如下:
modifiers-pattern:方法的可见性,如public,protected
ret-type-pattern:方法的返回值类型,如int,void
declaring-type-pattern:方法所在类的全路径名,如com.spring.aspect
param-pattern:方法参数类型 比如java.lang.String
throws-pattern:方法跑出的异常类型 如java.lang.Exception
example:
@Pointcut("execution(* com.spring.dao.*.*(..))") // 匹配com.spring.dao下的任意接口和类的任意方法
@Pointcut("execution(public * com.spring.dao.*.*(..))") //匹配com.spring.dao下的任意接口和类的public方法
@Pointcut("execution(public * com.spring.dao.*.*())") //匹配com.spring.dao下的任意接口和类的public 无参方法
@Pointcut("execution(* com.spring.dao.*.*(java.lang.String, ..))") //匹配com.spring.dao下的任意接口和类第一个参数且是String类型的方法
@Pointcut("execution(* com.spring.dao.*.*(java.lang.String))") //匹配com.spring.dao下的任意接口和类只有一个参数且是String类型的方法
@Pointcut("execution(public * *(..))") //匹配任意接口和类public的方法
@Pointcut("execution(* te*(..))") //匹配任意接口和类te开头的方法
@Pointcut("execution(* com.spring.dao.ADao.*(..))") //匹配ADao类下的所有方法
@Pointcut("execution(* com.spring.dao..*.*(..))") //匹配com.spring.dao包及其子包中任意的方法
关于这个表达式的详细写法
https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型、类名、方法名和参数名等 与方法相关的信息,并且在Spring中,大部分需要使用AOP的业务场景中也只需要达到方法级即可,因而execution表达式的使用最广泛的
2、within
该表倒是的最小粒度为类
within与execution相比,粒度更大,仅能实现到包和接口、类级别,而execution可以精确到方法的返回值、参数个数、修饰符、参数类型等
@Pointcut("within(com.spring.dao.*") //匹配com.spring.dao包中任意的方法
@Pointcut("within(com.spring.dao..*") //匹配com.spring.dao包及其子包中任意的方法
3、args
args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
@Pointcut("args(java.lang.Integer, java.lang.String)")
args通execution不同的地方在于:args匹配的是运行时传递给方法的参数类型 execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型
4、this
JDK代理时,指向接口和代理类proxy,cglib代理时,指向接口和子类(不适用proxy)
5、target
target 指向接口和子类
@Pointcut("target(com.spring.dao.ADaoImpl)") // 目标对象,也就是被代理的对象,限制目标对象为com.spring.dao.ADaoImpl类
@Pointcut("this(com.spring.dao.ADaoImpl)") // 当前对象,也就是代理的对象,代理对象是通过代理目标对象的方式获取新的对象,与原值并非是一个
@Pointcut("@target(com.spring.anno.A)") // 具有@A的目标对象中的任意方法
@Pointcut("@within(com.spring.anno.A)") // 等同于target