转载

关于IOC与AOP的一些理解

最近在复习一些旧的知识,随着工作经验的增加,看待问题的眼光也在发生变化,重新谈谈对IOC与AOP新的理解.

IOC

IOC叫做控制反转或者说是依赖注入,IOC提出的目的是解决项目中复杂的依赖关系,使用非硬编码的方式来扩展这些关系,简单来说就是为了解耦,在你需要的地方IOC容器会自动帮你注入对应的服务实例.

对于IOC来说一直存在循环依赖的难题,当A依赖B,B依赖C,C依赖A,彼此的依赖关系构成的是一个环形时,就是循环依赖,解决这种环形的依赖才是IOC的最关键的本质.(系统中出现循环依赖的话一不小心就掉进了死递归,因此尽可能避免循环依赖设计)

处理循环依赖

构造注入

构造注入时利用构造函数在实例化类的时候注入需要参数的一种方式.对于构造注入的循环依赖如下所示:

class A {
  private B b;
  // A的创建依赖B
  public A(B b) {
    this.b = b;
  }
}

class B {
  private A a;
  // B的创建依赖A
  public B(A a) {
    this.a = a;
  }
}

那么结果自然是死锁,A需要B才能实例化,B需要A才能实例化,系统中有没有两个类的实例,互相僵持就是死锁,无法解决循环依赖问题.

属性注入

属性注入是在 类实例化 之后,通过set方法或者反射直接注入到对应的成员变量上的依赖注入方式,如下所示:

class A {
  private B b;

  public A() {
  }
  // 实例化之后set方法注入B
  public A setB(B b) {
    this.b = b;
    return this;
  }
}

class B {
  private A a;

  public B() {
  }
  // 实例化之后set方法注入A
  public B setA(A a) {
    this.a = a;
    return this;
  }
}

与构造函数最大的不同点是去除了类的实例化对外部的强依赖关系,转而用代码逻辑保证这个强依赖的逻辑,比如属性注入失败直接抛异常让系统停止.那么此时的循环依赖解决办法就很简单了.

  • 做法1: 系统初始化时不考虑依赖关系把所有的Bean都实例化出来,然后依次执行属性注入,因为每个Bean都有实例,所以循环依赖不存在死锁.
  • 做法2: 按需实例化,实例化A,然后执行A的属性注入,发现依赖B,接着去实例化B,执行B的属性注入,此时A已经存在,那么B可以注入A,回到A的属性注入,拿到了B的实例,注入B.到此循环依赖解决.

回归本质,概括一下就是转变 强依赖到弱依赖 ,把 实例化与属性注入两个步骤分开 来解决循环依赖的死锁.

AOP

AOP是用动态代理实现了一种无侵入式的代码扩展方式.对于AOP来说有两个很重要的接口:

  • MethodInvocation: AOP需要增强的那个方法的封装
  • MethodInterceptor: AOP的所增强的操作.
    两者混合使用可以构造出如下结构:
    MethodInvocation 是对 HelloService.sayHello(); 的封装,而 MethodInterceptor 持有了 MethodInvocation ,在调用其之前进行了增强处理,这就是AOP的实质.
    关于IOC与AOP的一些理解

那么AOP之后再AOP怎么实现呢?

动态代理之后会产生一个代理类,对于上图就是 HelloServiceProxy ,那么把这个类当成 HelloService ,然后接着AOP,就实现了嵌套.本质上是一样的道理,既然都是实实在在的类,那么就可以一直嵌套下去,这样的嵌套一般会形成一个功能链,Mybatis的Plugin就是利用这种形式来实现的.

处理this

假设 HelloService 被AOP增强,那么调用 sayHello() 时执行 this.sayWorld() 这行代码会走AOP处理吗?

public class HelloService {

  public void sayHello() {
    System.out.println("hello");
    // 这里调用了本类的方法
    this.sayWorld();
  }

  public void sayWorld() {
    System.out.println("world");
  }
}

答案当然是不会,由上图可以得知: 无论AOP怎么增强最终调用 sayHello() 这个方法的实例一定是 HelloService ,那么这里的 this 也一定是 HelloService ,既然这样肯定不会走AOP代理了.

解决办法也很简单,就是获取到代理类,然后再执行这个方法,对于Spring,可以从 ApplicationContext 中获取到当前的 HelloService 实例,这里获取到的自然是代理类,然后利用该实例调用 sayWorld() 就会走AOP代理了,大概形式如下,当然可以更好地封装下.

public class HelloService implements ApplicationContextAware {
  private ApplicationContext applicationContext;

  public void sayHello() {
    System.out.println("hello");
    // 这里拿到代理类后再执行
    applicationContext.getBean("helloService", HelloService.class)
        .sayWorld();
  }

  public void sayWorld() {
    System.out.println("world");
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}
  • 版权声明: 感谢您的阅读,本文由屈定's Blog版权所有。如若转载,请注明出处。
  • 文章标题: 关于IOC与AOP的一些理解
  • 文章链接: https://mrdear.cn/2018/04/14/framework/spring/spring--ioc_aop/

Java学习记录--CAS操作分析

原文  https://mrdear.cn/2018/04/14/framework/spring/spring--ioc_aop/
正文到此结束
Loading...