Spring AOP的实现方式与原理
作者:mmseoamin日期:2024-04-29

目录

认识IOC与AOP

AOP的实现方式

@Aspect注解实现AOP

自定义注解实现AOP

Spring AOP原理

代理模式

静态代理和动态代理

JDK动态代理

CGLIB动态代理

Spring AOP实现的哪种代理


认识IOC与AOP

IOC又称为控制反转,也就是控制权发生了反转.在传统的程序中,我们是需要自己来手动创建对象的,但是在Spring实现IOC思想后,就可以直接交给Spring来管理创建.所以控制反转就是将对对象的创建销毁权利交给SPring来处理,我们就不需要关心. 举个例子: 原来,我们需要在A类中调用B类的方法,传统的方式就是我们直接在A中new出B类的对象,然后再调用B类的方法.这里虽然可以实现效果.但是会存在一个很大的问题:如果你的需求发生了变化,你就需要对源代码进行修改,这样不仅是导致代码工作量巨大,还容易发生错误.这种代码属于高耦合.但当实现IOC思想后,我们创建B对象就交给了Spring来管理.在Spring中,B对象被看成一个Bean对象. Bean对象都由spring来创建和管理..这样的话,我们需要获取对象,就从主动new变成了被动等Spring来创建. 从主动变成被动,这就可以理解为控制反转.这样就可以大大降低代码的耦合性. 所以IOC也就是依赖类不由程序猿实例化,而是通过Spring容器来帮助我们new好指定的实例,且将实例注入到需要的类中.

而AOP称为面向切面编程,这里的切面就是特指一些特定的问题.向我们的拦截器,统一结果返回,统一异常处理,这也是AOP思想的一种体现.简单来说,AOP就是对一类事情的集中处理.AOP可以说是对OOP的补充. 面向对象就是将食物的特性和行为抽象成一个对象,将它们的特征和行为封装成一个类,统一调用. 且面向切面就是将其中特定的问题给提取出来,等需要用的时候才切入.就比如有一个people类,它们都有身高,体重,年龄等属性和吃饭,睡觉等行为.但是生病去医院看病这个行为是只有一部分人才会发生的.AOP就是将看病这个行为给提取出来,然后等到需要这个功能的时候再给切入到需要的方法中.这样就可以减少系统的重复代码和模块之间降低耦合度.

AOP的实现方式

Spring AOP有四种实现方式,第一种是通过@Aspect注解来实现的,第二种则是通过自定义注解来实现的,第三种是通过SPring的API来实现的(也就是xml的方式). 第四种就是基于动态代理来实现的,可以说上面是三种都是基于动态代理在实现的.

@Aspect注解实现AOP

@Aspect表示一个切面类,而和它配合使用的还有多个注解:

通知类型:

@Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏    @Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏ @After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏ @AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏ @AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏
@Slf4j
@Aspect
public class TimeAspect {
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint joinPoint) {
        long start = System.currentTimeMillis();
        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info(joinPoint.getSignature() + "执行时间: " + (System.currentTimeMillis()-start)+ "ms");
        return result;
    }
}

Spring AOP的实现方式与原理,第1张

自定义注解实现AOP

这里是使用@annotatoin注解来实现的.我们还需要先实现一个自定义注解再将自定义注解放到我们需要增强的方法上.

自定义注解:

package com.example.demo.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

 切面类:

@Aspect
@Slf4j
public class AspectDemo1 {
    @Before("@annotation(com.example.demo.aspect.MyAspect)")
    public void before1() {
        log.info("before1方法执行");
    }
    @After("@annotation(com.example.demo.aspect.MyAspect)")
    public void after1() {
        log.info("after1方法执行");
    }
}

需要增强的方法:

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public void t1() {
        log.info("t1方法执行");
    }
    @MyAspect
    @RequestMapping("/t2")
    public void t2() {
        int a = 10 / 0;
        log.info("t2方法执行");
    }
}

Spring AOP原理

我们的Spring AOP是基于动态代理来实现的,这里我们分两部分来进行说明.

代理模式

代理模式就是为其他对象提供一种代理来控制对这个对象的访问. 它的作用就是通过提供一个类,让我们在调用目标方法的是,不再是直接调用,而是通过这个代理类来间接调用.因为再一些情况先,不适合直接引用另一个对象. 代理模式就好比我们的房屋中介一样: 房屋出租时,房东就将房屋授权给中介,由中介来代理看房,房屋咨询等.

静态代理和动态代理

我们可以根据代理的创建时期,代理分为静态代理和动态代理.

静态代理就是有程序猿来创建代理类,再对其进行编译,在程序运行前代理类就是class文件存在了.

动态代理则是在程序运行的时候,运用反射机制来创建.相比于静态代理来说,动态代理更加的灵活.我们不需要针对每一个目标对象单独创建一个代理对象,而是将这个工作推到程序运行再让JVM来执行.

Java中也对动态代理进行了实现,常见的动态代理有JDK动态代理和CGLIB动态代理.

JDK动态代理

它的实现步骤主要是:

1. 定义一个接口和它的实现类.

2.自定义一个类来实现InvocationHandler接口,且重写invoke方法,在invoke方法中我们会调用目标方法且自定义一些逻辑.

3. 通过proxy.newProxyInstance(ClassLoder loader, Class[] interfaces, InvocationHandler h) 方法来创建.

实现InvocationHandler接口:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
 //⽬标对象即就是被代理对象
 private Object target;
 public JDKInvocationHandler(Object target) {
 this.target = target;
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
 // 代理增强内容
 System.out.println("我是中介, 开始代理");
 //通过反射调⽤被代理类的⽅法
 Object retVal = method.invoke(target, args);
 //代理增强内容
 System.out.println("我是中介, 代理结束");
 return retVal;
 }
}

创建一个代理对象并使用:

public class DynamicMain {
 public static void main(String[] args) {
 HouseSubject target= new RealHouseSubject();
 //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
 HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
 target.getClass().getClassLoader(),
 new Class[]{HouseSubject.class},
 new JDKInvocationHandler(target)
 );
 proxy.rentHouse();
 }
}

通过这里,我们知道JDK动态代理在使用Proxy类的newProxyInstance方式创建代理类,它方法的第二个参数是interfaces,也就是被代理类实现的一些接口(这里就决定了JDK动态代理只能实现接口的类)

CGLIB动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类. 有些场景下, 我们的业务代码是直接实现的, 并没有接⼝定义. 为了解决这个问题, 我们可以⽤ CGLIB 动态代理机制来解决    

CGLIB实现步骤:

1. 定义一个被代理类

 2. 自定义MethodInterceptor,且重写intercept方法,intercept方式用来增强目标方法,和JDK的invoke方法类似.

2. 通过Enhancer类的create()来创建代理类.

自定义一个类实现MethodInerceptor接口:

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {
 //⽬标对象, 即被代理对象
 private Object target;
 public CGLIBInterceptor(Object target){
 this.target = target;
 }
 @Override
 public Object intercept(Object o, Method method, Object[] objects, 
MethodProxy methodProxy) throws Throwable {
 // 代理增强内容
 System.out.println("我是中介, 开始代理");
 //通过反射调⽤被代理类的⽅法
 Object retVal = methodProxy.invoke(target, objects);
 //代理增强内容
 System.out.println("我是中介, 代理结束");
 return retVal;
 }
}

创建代理类,并使用:

public class DynamicMain {
 public static void main(String[] args) {
 HouseSubject target= new RealHouseSubject();
 HouseSubject proxy= (HouseSubject) 
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
 proxy.rentHouse();
 }
}

这里Enhancer.create()生成对象的方法参数:

type: 被代理类的类型

callback: 自定义的被代理类MethodInterceptor

因为这里被代理类的类型参数为任意参数,所以CGLIB可以代理任意类.

Spring AOP实现的哪种代理

Spring Framework 和Spring Boot它们底层实现的都是JDK和CGLib动态代理. 但是它们不同之处在于:

Spring Framework如果代理的目标类是实现了接口的类,那就是使用JDK,如果代理的是没有实现接口的类,则会使用CGlib(而是,这里的实现的接口里面至少有一个自定义的方法,不然还是会CGLIB代理,因为这里就算我们将proxyTargetClass配置为了false,经过一些判断还是会为true)

Spring boot2.0 之后的版本默认配置的使用CGlib代理.代理的类无论是实现了接口还是没有实现都用CGlib代理,如果需要使用JDK代理,则需要去配置. 而Spring boot2.0之前的版本和Spring Framework一样.

它们之所以不同,就是因为代理工厂里面proxyTargetClass属性的默认配置不同. 如果为false 则实现了接口的类使用JDK,没有则使用CGLIB.如果为true,不管实现没实现都使用CGLIB.  也就是因为SpringFramework和Springboot2.0之前,ProxyTargetClass默认是false,而Springboot2.0之后默认是true.如果需要使用其他的代理,我们修改proxyTargetClass配置即可.