关于AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率(百度百科)。
自己的理解:与OOP对比(AOP的出现是相对于OOP来说的),面向切面AOP,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的(也就是说在代码里面把横切性的问题删除,主逻辑不会受到影响),但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的,使代码的重用性和开发效率更高。
例子:
在第一版中,是OOP典型的模型,自顶向下的调用,在其过程中,为了方便系统出现问题时,进行问题的定位和排查,就需要在各层进行输出一些日志,或者是对一些有权限要求的,就需要进行一些权限的验证,但是像这样的日志和权限验证就会掺杂在我们的业务代码里面,导致代码比较混乱,不容易维护,像这种日志和权限验证就是横切性的问题,你说重要吧,把它们删除主业务逻辑仍然是通的,不是不重要吧,没有这些东西还真不行。
在第二版中,将权限验证和记录日志抽离成一个方法,然后在主业务代码中调用权限验证和记录日志的方法就可以了,但是这样做还是有点问题,就是需要在每个业务方法里面都调用一下这些方法,能不能把横切性的问题和主业务逻辑进行解耦,这样能够让代码显的更有扩展性和维护,于是第三版就出来的。
在第三版中,就是把权限验证和记录日志做成一个切面,然后动态的注入到想要的地方就可以了,这就是面向切面编程,让我们只需要去关注具体的业务就行,不需要考虑其他问题。
在学习AOP的时候,要知道AOP的概念,这一点非常重要,下面就来看一下AOP的概念。
AOP概念
Joinpoint(连接点):目标对象中的方法,JoinPoint强调的是关注和增强的方法
Pointcut(切点):切点表示连接点的集合,PointCut是JoinPoint的谓语,这是一个动作,主要是告诉通知连接点在哪里,切点表达式决定 JoinPoint 的数量(可以把pointcut理解成一个表,Joinpoint理解成表中的记录)
Weaving (织入):把代理逻辑加入到目标对象上的过程叫做织入
Target (目标对象): 原始对象,即需要被增强的对象
Aspect(切面):Aspect 是对系统中的横切关注点逻辑进行模块化封装的 AOP 概念实体。类似于 Java 中的类声明,在 Aspect 中可以包含多个 Pointcut 以及相关的 Advice 定义。
Advice(通知):Advice 定义了将会织入到 Joinpoint 的具体逻辑和位置,根据位置不同可以将通过Advice分为下面几种:
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
AOP实现
AOP是一种思想和理念,可以使用这种思想来简化代码开发的耦合性和难度,有句话说的好,天上飞的理念,必有落地的实现,那么实现了AOP这个思想的具体实现由Spring AOP和AspectJ技术,他们都是根据AOP的思想实现的。
Spring AOP和AspectJ的关系?
springAop、AspectJ都是Aop的实现,Spring AOP使用XML的格式进行配置的,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解(记住只是使用到了AspectJ的注解,而进行注解的解析工作是Spring内部实现的,所以Spring AOP只是借助了AspectJ的注解而已)。
spring AOP提供两种编程风格
@AspectJ support ------------>利用aspectj的注解
Schema-based AOP support ----------->xml aop:config 命名空间
证明:spring通过源码分析,我们可以知道spring底层使用的是JDK或者CGLIB来完成的代理,并且在官网上spring给出了aspectj的文档,和springAOP是不同的
springAop的底层技术
JDK动态代理 | CGLIB代理 | |
编译时期的织入还是运行时期的织入? | 运行时期织入 | 运行时期织入 |
初始化时期织入还是获取对象时期织入? | 初始化时期织入 | 初始化时期织入 |
Spring AOP使用案例:
关于Spring AOP的使用,Spring官网是推荐使用AspectJ来进行的,下面使用AspectJ注解来实现一个Spring AOP的例子。
需要被增强的类:
package com.luban.aop.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getName(){
System.out.println("getName");
return "wj";
}
public void sayHello(){
System.out.println("hello");
}
}
1.启用@AspectJ支持
想要在Spring中使用AspectJ的注解,就需要在使用Java @Configuration启用@AspectJ支持,请添加@EnableAspectJAutoProxy(如果你是使用XML aop:config命名空间的形式,而不是使用AspectJ注解可以不这样进行配置)。
package com.luban.aop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
使用XML配置启用@AspectJ支持要使用基于xml的配置启用@AspectJ支持,可以使用aop:aspectj-autoproxy元素
<aop:aspectj-autoproxy/>
2、声明一个Aspect
申明一个@Aspect注释类,并且定义成一个bean交给Spring管理。
package com.luban.aop;
import org.aspectj.lang.annotation.Aspect;//这是aspectj的注解
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect {
}
3、申明一个pointCut
切入点表达式由@Pointcut注释表示。切入点声明由两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,该表达式确定我们对哪个方法执行感兴趣。
@Pointcut("execution(* com.luban.aop.service..*.*(..))")// 切入点表达式
public void pointcut() {// 切入点签名
}
切入点确定感兴趣的 join points(连接点),从而使我们能够控制何时执行通知。Spring AOP只支持Spring bean的方法执行 join points(连接点),所以您可以将切入点看作是匹配Spring bean上方法的执行。
package com.luban.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 申明Aspect,并且交给spring容器管理
*/
@Component
@Aspect
public class MyAspect {
/**
* 申明切入点,匹配UserDao所有方法调用
* execution匹配方法执行连接点
* within:将匹配限制为特定类型中的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* com.luban.aop.service..*.*(..))")
public void pointcut() {
}
}
4、申明一个Advice通知
advice通知与pointcut切入点表达式相关联,并在切入点匹配的方法执行@Before之前、@After之后或前后运行。
package com.luban.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 申明Aspect,并且交给spring容器管理
*/
@Component
@Aspect
public class MyAspect {
/**
* 申明切入点,匹配UserDao所有方法调用
* execution匹配方法执行连接点
* within:将匹配限制为特定类型中的连接点
* args:参数
* target:目标对象
* this:代理对象
*/
@Pointcut("execution(* com.luban.aop.service..*.*(..))")
public void pointcut() {
}
/**
* 申明before通知,在pintCut切入点前执行
* 通知与切入点表达式相关联,
* 并在切入点匹配的方法执行之前、之后或前后运行。
* 切入点表达式可以是对指定切入点的简单引用,也可以是在适当位置声明的切入点表达式。
*/
@Before("pointcut()")
public void before() {
System.out.println("before advice");
}
@After("pointcut()")
public void after() {
System.out.println("after advice");
}
}
5.进行测试
public class Test01 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
UserService bean = context.getBean(UserService.class);
bean.getName();
bean.sayHello();
}
}
//其打印结果:
//before advice
//getName
//after advice
//before advice
//hello
//after advice
对比:
对于上面的例子对照着AOP的概念来说:
- UserService 类中的getName和sayHello方法,每次被调用时所处的程序执行点都是一个 Jointpoint
- Pointcut 就是用于指定Jointpoint集合的表达式,当然这个表达式还可以指定很多其他的接口,表达式常见的格式为:“http://execution(* com.luban.aop.service..*.*(..))”
- Aspect 是定义 Advice、Pointcut 的地方
- Advice 就是我们要在 哪些Jointpoint的哪个位置执行什么逻辑,MyAspect 类上面的before和after方法(加上其方法上面的注解)都是Advice ,拿before来说,@Before("pointcut()")指定了需要在哪些Jointpoint的哪个位置了,而具体的逻辑是使用before方法内部的代码来表示。
- Weaving 就是指将before和after方法逻辑加到getName和sayHello方法的过程
- Target 值得就是UserService这个类
整体图: