最近在复习一些旧的知识,随着工作经验的增加,看待问题的眼光也在发生变化,重新谈谈对IOC与AOP新的理解.
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; } }
与构造函数最大的不同点是去除了类的实例化对外部的强依赖关系,转而用代码逻辑保证这个强依赖的逻辑,比如属性注入失败直接抛异常让系统停止.那么此时的循环依赖解决办法就很简单了.
回归本质,概括一下就是转变 强依赖到弱依赖 ,把 实例化与属性注入两个步骤分开 来解决循环依赖的死锁.
AOP是用动态代理实现了一种无侵入式的代码扩展方式.对于AOP来说有两个很重要的接口:
MethodInvocation
是对 HelloService.sayHello();
的封装,而 MethodInterceptor
持有了 MethodInvocation
,在调用其之前进行了增强处理,这就是AOP的实质. 那么AOP之后再AOP怎么实现呢?
动态代理之后会产生一个代理类,对于上图就是 HelloServiceProxy
,那么把这个类当成 HelloService
,然后接着AOP,就实现了嵌套.本质上是一样的道理,既然都是实实在在的类,那么就可以一直嵌套下去,这样的嵌套一般会形成一个功能链,Mybatis的Plugin就是利用这种形式来实现的.
假设 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; } }
Java学习记录--CAS操作分析