AOP(Aspect Oriented Programming)面向切面编程,它是一种思想,它是对某一类事情的集中处理。比如用户登录权限的效验,没学 AOP 之前,我们所有需要判断用户登录的页面 (中的方法),都要各自实现或调用用户验证的方法,然而有了 AOP 之后,我们只需要在某一处配置一下,所有需要判断用户登录页面 (中的方法)就全部可以实现用户登录验证了,不再需要每个方法中都写相同的用户登录验证了而AOP 是一种思想,而 Spring AOP 是一个框架,提供了一种对 AOP 思想的实现,它们的关系和loC 与 DI 类似。
AOP是一个思想, SpringAOP是AOP的一种实现, AOP是OOP的补充与完善
OOP: 面向对象编程
AOP: 面相切面编程, 对某一类事情的集中处理
想象一个场景,我们在做后台系统时,除了登录和注册等几个功能不需要做用户登录验证之外,其他几乎所有页面调用的前端控制器(Controller) 都需要先验证用户登录的状态,那这个时候我们要怎么处理呢?
我们之前的处理方式是每个 Controller 都要写一遍用户登录验证,然而当你的功能越来越多,那么你要写的登录验证也越来越多,而这些方法又是相同的,这么多的方法就会代码修改和维护的成本。那有没有简单的处理方案呢?答案是有的,对于这种功能统一,且使用的地方较多的功能,就可以考虑 AOP来统一处理了。
除了统一的用户登录判断之外,AOP 还可以实现:
也就是说使用 AOP 可以扩充多个对象的某个能力,所以 AOP 可以说是 OP (Object Oriented Programming,面向对象编程) 的补充和完善
切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包 括了连接点的定义。
_切⾯是包含了:通知、切点和切⾯的类,相当于 AOP 实现的某个功能的集合 _
应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段 时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为。
_连接点相当于需要被增强的某个 AOP 功能的所有⽅法。 _
Pointcut 是匹配 Join Point 的谓词。 Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹配 Join Point,给满⾜规则的 Join Point 添加 Advice。
_切点相当于保存了众多连接点的⼀个集合(如果把切点看成⼀个表,⽽连接点就是表中⼀条⼀条的数据) _
切⾯也是有⽬标的 ——它必须完成的⼯作。在 AOP 术语中,切⾯的⼯作被称之为通知。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
@Slf4j
@Component
@Aspect
public class LoginAspect {
@Pointcut("execution(* springaop.controller.UserController.*(..))")
public void pointcut(){}
//前置通知: 通知方法会在目标方法调用之前执行
@Before("pointcut()")
public void doBefore(){
log.info("do before...");
}
//后置通知:通知方法会在目标方法返回或者抛出异常后调用
@After("pointcut()")
public void doAfter(){
log.info("do after...");
}
//返回之后通知:通知方法会在目标方法返回后调用
@AfterReturning("pointcut()")
public void doAfterReturning(){
log.info("do AfterReturning...");
}
//环绕通知:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){
Object oj;
log.info("环绕通知开始之前");
try {
oj = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("环绕通知开始之后");
return oj;
}
}
各种通知:
执行顺序:
ProceedingJoinPoint常用方法:
在环绕通知里log.info(joinPoint.getSignature().toLongString());
注意点:
**execution(.....)**
来匹配切点方法//环绕通知实现统计方法执行时间
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){
Object oj;
long start = System.currentTimeMillis();
try {
oj = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
long end = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().toLongString()+"耗时: "+(end-start));
return oj;
}
Spring AOP 是构建在动态代理基础上,因此 Spring 对AOP 的支持局限于方法级别的拦截.
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下,实现了接口的类,使用AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类
织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中.
在目标对象的生命周期里有多个点可以进行织入:
**编译期:**
切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。**类加载期:**
切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入 (load-time weaving.LTW) 就支持以这种方式织入切面。**运行期:**
切面在应用运行的某一时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。SpringAOP就是以这种方式织入切面的。为其他对象提供⼀种代理以控制对这个对象的访问。在某些情况下,⼀个对 象不适合或者不能直接引用另⼀个对象,而代理对象可以在客户端和⽬标对象之间 起到中介的作用。 代理模式分为静态代理和动态代理
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改) 且麻烦(需要对每个目标类都单独写一个代理类)。实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景
上面我们是从实现和应用角度来说的静态代理,从JVM 层面来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件
静态代理实现步骤:
这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情
①定义接口
②实现接口
③创建代理类, 丙同样实现支付接口
④实际使用
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB动态代理机制)。
从JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
说到动态代理,不得不提的是Spring AOP,它的实现依赖了动态代理
动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
步骤:
示例:
①定义接口及其实现类
//接口
public interface PayService {
void pay();
}
//实现类
public class AliPayService implements PayService{
@Override
public void pay() {
System.out.println("Alipay...");
}
}
② 定义JDK动态代理类
public class JDKInvocationHandler implements InvocationHandler {
//目标对象即是被代理对象
private final Object target;
//代理对象
public JDKInvocationHandler(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//安全检查
System.out.println("安全检查");
//记录日志
System.out.println("记录日志");
//时间统计开始
System.out.println("时间统计开始");
//通过反射调用被代理类的方法
Object retVal = method.invoke(target,args);
//时间统计结束
System.out.println("时间统计结束");
return retVal;
}
}
③创建代理对象
public static void main(String[] args) {
//动态代理
PayService target = new AliPayService();
//创建一个代理类
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //类加载器
new Class[]{PayService.class}, //被代理类实现的一些接口
new JDKInvocationHandler(target) //实现了InvocationHandler接口的对象
);
proxy.pay();
}
缺点:
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免
步骤:
示例:
〇添加依赖(一般Spring集成了不用添加)
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
dependency>
①定义一个类
②定义MethodInterceptor(方法拦截器)
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {
//被代理对象
private final Object target;
public CGLIBInterceptor(Object target){
this.target = target;
}
@Override
public Object intercept(Object o,
Method method,
Object[] args,
MethodProxy methodProxy) throws Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录⽇志
System.out.println("记录⽇志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过cglib的代理⽅法调⽤
Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
}
③创建代理对象
//动态代理CGLIB
PayService target = new AliPayService();
//创建代理对象
PayService proxy = (PayService) Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
proxy.pay();
proxyTargetClass默认为false, 表示使用JDK动态代理织入增强. true就用CGLIB, false如果未实现接口用CGLIB,实现了接口用JDK.