IoC
概念
所谓控制反转,指的是获取对象的方式发生了反转。在传统面向对象编程中,我们都是在要使用某一个对象实例时创建一个对象实例,对象的控制权在我们自己手里,如果对于一个接口的多个实现类,我们要自己选择判断使用具体的实现类,使得我们进行软件开发耦合度高,维护起来不方便;spring的IOC则是将某一接口的具体实现类的选择控制权从调用者中移除,转由spring容器进行控制。在spring初始化对象的关联关系的过程是通过“依赖注入”实现的。
依赖注入类型
1、 构造器注入;
2、 属性注入;
3、 接口注入。
实现原理
我们一般是通过ApplicationContext来完成spring中bean的初始化,如下图所示,这看似简单的一个创建对象操作,实践上里面涉及的技术非常复杂。
在实例化context对象时,spring容器做了如下图所示的操作。
1、 首先通过解析XML配置文件取得bean的配置信息,并将bean注册到bean定义注册表里;
2、 将注册表里的bean根据注册信息实例化(通过工厂模式+反射),并进行依赖注入,如果是bean依赖,先初始化依赖的bean。
3、 将实例化的bean放入spring容器(bean存放的数据结构本质为map)。
ApplicationContext接口为对BeanFactory接口的扩展。ApplicationContext 在初始化时就把 xml 的配置信息读入内存,对 XML 文件进行检验,如果配置文件没有错误,就创建所有的Bean ,直接为应用程序服务;而BeanFactory是延迟加载,是在调用getBean方法时才进行bean的实例化,如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
详细的源码讲解请参考 https://javadoop.com/post/spring-ioc#toc18
设计思想
1、 单例模式
2、 工厂模式
3、 反射思想
使用IoC的好处
1、不用自己组装,拿来就用。
2、享受单例的好处,效率高,不浪费空间。
3、便于单元测试,方便切换mock组件。
4、便于进行AOP操作,对于使用者是透明的。
5、统一配置,便于修改。
AOP
概念
面向切面编程(AOP),适合那些具有横切逻辑的应用场合,如性能监测、访问控制、事务管理及日志记录。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
AOP到底是什么?先看看下图
观察上图,可以发现createForum方法和removeTopic方法除了①②处代码不一样外,其他代码一样。我们知道,我们开发软件讲究的是“懒”,重复代码一般都会抽出来形成一个类或方法,但上面示例的我们无法抽出,两个不同的业务代码被相同非业务代码包围,所以我们无法通过抽象父类进行抽取。这时候,AOP闪亮登场,AOP通过横向切取(底层通过代理模式实现)将那些重复的代码抽取出来,让业务逻辑代码不掺杂其他非业务逻辑代码并且可以不改变原来的业务流程,这就是AOP要做的事。这种将分散在各个逻辑代码的相同代码通过横向切取的方式抽取到一个独立模块的编程方式就叫AOP。
实现原理
1、 基于JDK的动态代理;下面几张图是样例程序
接口:
实现类
代理类
使用样例
JDK代理的底层实现过程:
主要在调用Proxy.newProxyInstance过程中会根据传入的类加载器、接口动态生成一个叫$proxy0类的字节码,编译后加入到jvm中。动态生成的过程就是利用java的反射机制获取的到类加载器的名称,接口名称,接口方法等然后拼接成字符串存入磁盘后进行编译这样大致的过程。看看下面的反编译字节码的结果,在构造方法处使用到了传入的InvocationHadler实现类MyAOP,然后在doSomething方法是调用MyAOP的invoke方法。

package first_maven; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements TestService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void doSomething() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("first_maven.TestService").getMethod("doSomething"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
参考链接 https://www.jianshu.com/p/84ffb8d0a338
jdk动态代理是通过继承proxy类然后实现接口动态创建代理类,所以不能创建没有实现接口的类的代理类。
与jdk静态代理比较:jdk静态代理只能代理一个接口(代理类内部需存在该接口方法的调用),如果其他接口的实现类也需要类似的增强,只能在创建一个代理类;而动态代理可以代理多个接口(因为代理类内部并不存在对接口实现类的实例方法的直接调用)。
Java静态代理
2、 基于CGlib的动态代理,下面是样例程序
代理类
被代理类及使用样例
底层实现原理请参考
https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0
cglib采用的是动态创建子类生成代理对象,所以被代理类不能是final 或 private,但cglib代理的对象并不需要实现接口。
使用aop的好处
- 通知自定义
- 解耦和
- 统一管理权限,统一管理异常抛出
- 不需要我们自己编写复杂的代理类
相关设计模式
一、工厂模式
1、简单工厂模式
就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
运算类接口
运算类实现类
工厂类
使用样例
当我们需要增加其他运算如乘法或除法时,只需获取到operateServive就可以进行编写,而与AddOperateImpl及SubOperateImpl无关,并且这些实现类都有很好的复用性。
优点:
工厂类是整个模式的关键。包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的。明确了各自的职责和权利,有利于整个软件体系结构的优化。
缺点:
由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利。
(摘抄自 https://www.cnblogs.com/yueguanguanyun/p/9584501.html)
也正是因为当我们增加新的运算时,工厂方法需要增加case分支语句,并且如果有很多“产品”工厂方法里就有大量的代码,虽然工厂对扩展开放,当同时也开放了修改,这就违反了面向对象的“开放-封闭原则”,于是就有了工厂方法模式。
2、工厂方法模式
定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的加载延迟到子类。
先看看使用:
抽象工厂
具体工厂:
工厂方法使用样例
工厂方法优点:
一方面解决了简单工厂违反“开放-封闭原则”,另一方面某一工厂生产具体某一产品,减轻了工厂类负担,使得程序维护性更高。
工厂方法缺点:
每加一个产品类,就要相应增加一个产品工厂类,增加了额外开发量;
一个工厂只能生产一个版本的产品,而不能生产多个产品,这时抽象工厂的出现就为解决这个问题。
3、 抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
抽象工厂接口,用户只需到该工厂取到具体产品而不需要去了解其他方面
Department表类似,这里不再贴出
Mysql生产车间
Oracle生产车间
客户端使用样例
抽象工厂优点:
1、 便于交换产品系列,可以方便的在不同产品系列间切换;
2、 具体的创建实例过程与客户端分离。
抽象工厂缺点:
当我们增加某一类产品时,就要增加每一个生产车间对该类产品的具体实现,同时要把该产品发布到我们的工厂(即在工厂接口提供取得该产品的抽象方法)
二、单例模式
保证一个类只有一个实例,并暴露出一个获取该实例的渠道(公共静态方法)
饿汉式
懒汉式
单例模式优点:
1、 由于单例模式在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,单例模式就非常明显了;
2、 由于单例模式只生成一个实例,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决;
3、 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作;
4、 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
单例模式缺点:
1、 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现;
2、 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。
(摘抄自 https://blog.csdn.net/lijizhi19950123/article/details/78150213/ )
主要应用场景
1、 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置;
2、 控制资源的情况下,方便资源之间的互相通信。如线程池等
三、 代理模式
为其他对象提供一种代理以控制对这个对象的访问。
Java中的三种代理模式样例代码见上面AOP实现原理模块。
应用场景:
1、 远程代理:为一个对象在不同地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。例如Java的RMI。
2、 虚拟代理:是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象。例如当我们打开一个网页,考虑到有些网页图片比较多,而且某些图片文件比较大,因此将先以图标的方式显示图片,不同类型的图片使用不同的图标,并且在图标下面标注该图片的文件名,用户单击图标后可查看真正的图片,此时代理存放的是图片的真实路径,但它并没有把要代理的图片存在自己内存中,而是在用户需要是去被代理里“取货“。
3、 安全代理:用来控制真实对象访问时的权限。例如,对某一对象,不同的用户有不同的访问权限,那么我们就可以创建拥有不同权限的代理类来进行代理。
4、 智能代理:是指当调用真实的对象时,代理去处理另外一些事情。我对这种代理的理解就是spring中的aop,增强被代理类。