谈谈对IOC的理解

(是什么)

IOC也就是控制反转,要谈谈IOC,就要先说一下控制反转是什么。

传统的开发方式就是A依赖于B,那就是A 手动new一个B的对象出来;而IOC的方式是不需要new对象,而是通过IOC容器也就是Spring来帮助我们实例化对象,需要什么对象,就去IOC容器里取出来就行。

所以说控制反转的意思就是“把对象创建管理什么的 权利交给外部环境IOC”。

(为什么)

那为什么要使用IOC?解决了什么问题?

对象之间的耦合度变低,也可以说依赖程度;资源容易管理,比如使用spring容器很容易实现一个单例,而传统模式得自己写。

没有IOC是这样的:

Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。
  • 放图...

有IOC是这样的:

  • 放图...

谈谈对AOP的理解

(是什么)

aop就是面向切面编程,主要的思想就是把横切关注的地方从核心业务逻辑分离出来,

切面Aspect:对关注点封装的类。

通知类型有哪些?

Before前置通知:在方法调用之前触发

After后置通知:在方法调用之后触发

AfterReturning返回通知:在方法调用完成,返回返回值之后触发

AfterThrowing异常通知:在方法调用中抛出异常后触发

Around环绕通知:可以在任意前后触发,还可以直接拿到目标对象,要执行的方法

(为什么)

为什么要用AOP?解决了什么问题?

比如日志记录、事务管理、权限控制、接口限流等,如果一直重复实现这些行为,代码会特别冗余,难以维护。

// 日志注解
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    /**
     * 描述
     */
    String description() default "";
    /**
     * 方法类型 INSERT DELETE UPDATE OTHER
     */
    MethodType methodType() default MethodType.OTHER;
}
// 日志切面
@Component
@Aspect
public class LogAspect {
  // 切入点,所有被 Log 注解标注的方法
  @Pointcut("@annotation(cn.annotation.Log)")
  public void webLog() {
  }
   /**
   * 环绕通知
   */
  @Around("webLog()")
  public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    // 省略具体的处理逻辑
  }
  // 省略其他代码
}
@Log(description = "method1",methodType = MethodType.INSERT)
public CommonResponse<Object> method1() {
      // 业务逻辑
      xxService.method1();
      // 省略具体的业务处理逻辑
      return CommonResponse.success();
}

(怎么实现的?)

spring AOP就是基于动态代理实现的,我详细聊聊Spring AOP 的实现机制

Spring AOP 的实现机制

A.如何实现剥离?

一共有三个对象,真实对象(Target)、代理对象(Proxy)、增强逻辑(Advice)

工作流程:

  1. 接收到方法调用。
  2. 执行增强逻辑(如 @Before 的通知)。
  3. 将调用转发给真实对象。
  4. 执行增强逻辑(如 @After 的通知)。
  5. 返回结果。

B.机制一:JDK动态代理(基于接口)

条件:至少实现了一个接口

为什么至少实现了一个接口?因为JDK动态代理生成的类已经继承了Proxy类,由于单继承,所以它只能通过实现接口的方式实现代理

原理:利用了Java原生的API Proxy InvocationHandler

  1. 运行的时候动态生成一个代理类,这个类和真实对象是兄弟关系,实现同一个接口
  2. 通过实现InvocationHandler插入增强的逻辑
  3. 调用代理对象,走的增强逻辑

C.机制二:CGLIB动态代理(基于继承)

条件:无(没实现接口只能用它)

原理:利用字节码

  1. 运行的时候操作字节码,生成一个代理类,这个类和真实对象是父子关系
  2. 子类会重写目标中所有非final的方法
  3. 重写的方法中加入增强逻辑

为什么不能重写final方法?因为final方法不能重写

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 执行前置增强 (如 @Before)
beforeAdvice();
try {
// 2. 调用真实目标对象的方法
result = method.invoke(targetObject, args);
// 3. 执行后置增强 (如 @AfterReturning)
afterReturningAdvice(result);
} catch (Exception e) {
// 4. 执行异常增强 (如 @AfterThrowing)
afterThrowingAdvice(e);
throw e;
} finally {
// 5. 执行最终增强 (如 @After)
afterFinallyAdvice();
}

D.总结

JDK动态代理限制在于只能代理接口定义的方法,CGLIB动态代理限制在于不能代理final类、方法。

Spring的默认:智能选择,有接口JDK动态代理,没有接口就CGLIB

SpringBoot2.x以上版本:默认CGLIB-->为了避免不必要的报错(比如注入实现类)

什么场景我们会强制使用CGLIB?

一、希望代理没在接口定义的方法

二、希望代理类就是目标类的类型@Autowired直接按照实现类类型注入

所以一般都用@resource按类型

如果使用JDK代理,生成的代理对象只实现了接口,并不是实现类类型,会导致注入失败。而CGLIB代理生成的代理对象是目标类的子类,可以同时匹配接口类型和具体类类型。