SpringIoC和AOP的实现原理解析(整理版)
1.概述
Spring核⼼概念为IoC和AOP。
2.Spring IoC底层原理
要了解控制反转,需要先了解软件设计的⼀个重要思想:依赖倒置原则。
什么是依赖倒置原则?假设我们设计⼀辆汽车:先设计轮⼦,然后根据轮⼦⼤⼩设计底盘,接着根据底盘设计车⾝,最后根据车⾝设计好整个汽车。这⾥就出现了⼀个“依赖”关系:汽车依赖车⾝,车⾝依赖底盘,底盘依赖轮⼦。但这种设计维护性很低。
换⼀种思路:我们先设计汽车的⼤概样⼦,然后根据汽车的样⼦来设计车⾝,根据车⾝来设计底盘,最后根据底盘来设计轮⼦。这时候,依赖关系就倒置过来了:轮⼦依赖底盘,底盘依赖车⾝,车⾝依赖汽车。
这时候,上司再说要改动轮⼦的设计,我们就只需要改动轮⼦的设计,⽽不需要动底盘、车⾝、汽车的设计了。
这就是依赖倒置原则——把原本的⾼层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖⾼层建筑。⾼层建筑决定需要什么,底层去实现这样的需求,但是⾼层并不⽤管底层是怎么实现的。
控制反转就是依赖倒置原则的⼀种代码设计的思路。具体采⽤的⽅法就是所谓的依赖注⼊。这⼏种概念的关系⼤概如下:
为了理解这⼏个概念,我们还是⽤上⾯汽车的例⼦。只不过这次换成代码,我们先定义四个Class,车、车⾝、底盘、轮胎。然后初始化这辆车,最后跑这辆车。代码结构如下:
这样,就相当于上⾯第⼀个例⼦,上层建筑依赖下层建筑——每⼀个类的构造函数都直接调⽤了底层代码的构造函数。假设我们需要改动⼀下轮胎(Tire)类,把它的尺⼨变成动态的,⽽不是⼀直都是30。我们需要像上⾯这样改。
由于我们修改了轮胎的定义,为了让整个程序正常运⾏,我们需要做以下改动:
由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件⼯程中,这样的设计⼏乎是不可维护的——在实际⼯程项⽬中,有的类可能会是⼏千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太⾼了。
所以我们需要进⾏控制反转(IoC),即上层控制下层,⽽不是下层控制着上层。我们⽤依赖注⼊(Dependency Injection)这种⽅式来实现控制反转。
所谓依赖注⼊,就是把底层类作为参数传⼊上层类,实现上层类对下层类的“控制”。这⾥我们⽤构造⽅法传递的依赖注⼊⽅式重新写车类的定义:
这⾥我只需要修改轮胎类就⾏了,不⽤修改其他任何上层类。这显然是更容易维护的代码。
这⾥我们采⽤的构造函数传⼊的⽅式进⾏的依赖注⼊。其实还有另外两种⽅法:Setter传递和接⼝传递,核⼼思路都是⼀样的,都是为了实现控制反转。
那什么是控制反转容器(IoC Container)呢?其实上⾯的例⼦中,对车类进⾏初始化的那段代码发⽣的地⽅,就是控制反转容器。
因为采⽤了依赖注⼊,在初始化的过程中就不可避免的会写⼤量的new。这⾥IoC容器就解决了这个问题。这个容器可以⾃动对你的代码进⾏初始化,你只需要维护⼀个Configuration(可以是xml,也可以是⼀段代码),⽽不⽤每次初始化⼀辆车都要亲⼿去写那⼀⼤段初始化的代码。这是引⼊IoC Container 的第⼀个好处。
IoC Container的第⼆个好处是:我们在创建实例的时候不需要了解其中的细节。在上⾯的例⼦中,我们⾃⼰⼿动创建⼀个车instance时候,是从底层往上层new的:
这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能⼀步⼀步new注⼊。
⽽IoC Container在进⾏这个⼯作的时候是反过来的,它先从最上层开始往下依赖关系,到达最底层之后再往上⼀步⼀步new。
实际项⽬中,有Service Class可能是⼗年前写的,有⼏百个类作为它的底层。假设我们新写⼀个API需要实例化这个Service,我们总不可能回去搞清楚这⼏百个类的构造函数吧。IoC Container的这个特性就很完美的解决了这类问题——因为这个架构要求你在写class的时候需要写相应的Config⽂件,所以你
要初始化很久以前的Service类的时候,前⼈都已经写好了Config⽂件,你直接在需要⽤的地⽅注⼊这个Service就可以了。这⼤⼤增加了项⽬的可维护性且降低了开发难度。
3.Spring AOP底层原理
Spring AOP全称为Spring Aspect-Oriented Programming,即⾯向切⾯编程,是运⾏时织⼊的,那么运⾏时织⼊到底是怎么实现的呢?答案就是代理对象。代理对象⼜可以分为静态代理和动态代理。
静态代理:由程序员创建或特定⼯具⾃动⽣成源代码,再对其编译。在程序运⾏前,代理类的.class⽂件就已经存在了。
动态代理:在程序运⾏时,运⽤反射机制动态创建⽽成。
静态代理的每⼀个代理类只能为⼀个接⼝服务,这样⼀来程序开发中必然会产⽣过多的代理,⽽且,所有的代理操作除了调⽤的⽅法不⼀样之外,其他的操作都⼀样,则此时肯定是重复代码。解决这⼀问题最好的做法是可以通过⼀个代理类完成全部的代理功能,那么此时就必须使⽤动态代理完成。
动态代理与静态代理对照的是动态代理类,动态代理类的字节码在程序运⾏时由Java反射机制动态⽣成,⽆需程序员⼿⼯编写它的源代码。动态代理不仅简化了编程⼯作,⽽且提⾼了软件系统的可扩展性,因为Java反射机制可以⽣成任意类型的动态代理类。flect包中的Proxy类和InvocationH
andler接⼝提供了⽣成动态代理类的能⼒。
Spring AOP使⽤动态代理技术在运⾏期间织⼊增强的代码,主要有两种代理机制:基于JDK的动态代理;基于cglib的动态代理。JDK本⾝只提供接⼝的代理,⽽不⽀持类的代理。
3.1 代理模式
⾸先我们来了解代理模式的基本定义。
代理模式的英⽂叫做Proxy,就是⼀个⼈或者⼀个机构代表另⼀个⼈或者另⼀个机构采取⾏动。在⼀些情况下,⼀个客户不想或者不能够直接引⽤⼀个对象,⽽代理对象可以在客户端和⽬标对象之间起到中介作⽤。
//Subject接⼝
public interface Subject {
void request();
}
//⽬标对象RealSubject
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("real subject execute request");
}
}
//Proxy代理类
public class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
}
@Override
public void request() {
System.out.println("before");
try{
}catch (Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally {
System.out.println("after");
}
}
}
//Client客户端
public class Client {
public static void main(String[] args){
Subject subject = new Proxy(new RealSubject());
}
}
这样就会输出:
before
real subject execute request
after
是不是达到和AOP⼀样的效果了呢?AOP将真正要执⾏的代码交给RealSubject来执⾏,然后通过proxy来对额外的代码进⾏织⼊。
3.2 动态代理
上⾯的案例就是⼀个典型的静态代理的案例,这样有什么缺点呢?
如果当你要代理的⽅法越多时,你需要重复的逻辑就越多,假设你的⽬标类有100个⽅法,那么你的代理类就需要对这100个⽅法进⾏委托,但是⼜可能他们前后需要执⾏的逻辑时⼀样的,这样就会产⽣很多冗余。
这样,就有个更好的动态代理的⽅法出现了。
动态代理也分为两类:基于接⼝的代理和基于继承的代理
两类实现的代表是:JDK代理 与 CGlib代理
3.2.1 JDK代理模式
JDK动态代理主要涉及flect包下的两个类:Proxy类和InvocationHandler接⼝。
JDK代理实现的三个要点:
① 通过flect.Proxy类来动态⽣成代理类;
② 代理类要实现InvocationHandler接⼝;
③ JDK代理只能基于接⼝进⾏动态代理的;
//客户端Client
public class Client {
/*
*newProxyInstance⽅法参数解析
*ClassLoader loader:类加载器
*Class<?>[] interfaces:得到全部的接⼝
*InvocationHandler h:得到InvocationHandler接⼝的⼦类实例
*/
public static void main(String[] args){
Subject subject = (Subject) wProxyInstance(ClassLoader(),
new Class[]{Subject.class},new JdkProxySubject(new RealSubject()));
subject.hello();
}
}
//代理类JdkProxySubject
public class JdkProxySubject implements InvocationHandler{
private RealSubject realSubject;
public JdkProxySubject(RealSubject realSubject) {
}
/*
*invoke⽅法⽅法参数解析
*Object proxy:指被代理的对象。
*Method method:要调⽤的⽅法
*Object[] args:⽅法调⽤时所需要的参数
*InvocationHandler接⼝的⼦类可以看成代理的最终操作类。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = null;
try{
/
/利⽤反射动态的来反射⽅法,这就是动态代理和静态代理的区别
result = method.invoke(realSubject,args);
}catch (Exception e){
System.out.println("ex:"+e.getMessage());
throw e;
}finally {
System.out.println("after");
}
return result;
}
}
/
/⽬标对象RealSubject
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("real subject execute request");
}
@Override
public void hello() {
System.out.println("hello");
}
}
/
/subject接⼝,这个是jdk动态代理必须的前提。spring aop应用场景
public interface Subject {
void request();
void hello();
}
输出:
before
hello
after
before
real subject execute request
after
我们在subject接⼝中新增加了⼀个hello()⽅法,然后在RealSubject中对hello()⽅法进⾏实现,但是在代理类中,我们不需要再去为hello⽅法再去写⼀个代理⽅法,⽽是通过反射调⽤⽬标对象的⽅法,来动态的⽣成代理类。
总结:
因为利⽤JdkProxySubject⽣成的代理类实现了接⼝,所以⽬标类中所有的⽅法在代理类中都有;
⽣成的代理类的所有⽅法都拦截了⽬标类的所有的⽅法。⽽中invoke⽅法的内容正好就是代理类的各个⽅法的组成体;
利⽤JDK代理⽅式必须有接⼝的存在。
3.2.2 CGLib代理模式
CGLib采⽤⾮常底层的字节码技术,可以为⼀个类创建⼦类,并在⼦类中采⽤⽅法去技术拦截所有的⽗类⽅法的调⽤,并顺势织⼊横切逻辑。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论