前言
Spring作为java应用中最强大的框架,拥有着无比替代的地位,很多人只知道它好,听说过IOC、DI 解耦等。这篇文章会以最通俗的说法分析、理解Spring的意义
Spring的意义
先抛开Spring,我们假设一个业务场景:我们team1有一个UserService.java,它有一个ListUser方法,返回所有的User,我们如何使用呢:
import com.unis.team1.UserService;public UserAction{ UserService userService = new UserService(); userService.listUser(); ......}复制代码
领导有一天突然发现,我们的UserService中的listUsers写的不够好,没有另外一个小组编写的UserService中实现的listUsers方法好,因为另外一个小组在listUsers中加了一个小判断,在列举出来的用户中,领导永远排在第一个。
import com.unis.team1.UserService;复制代码
import com.unis.team2.UserService;复制代码
这一切的一切的罪恶的根源,源于如下的代码:
import com.unis.team1.UserService;public UserAction{ UserService userService = new UserService(); userService.listUser(); ......}复制代码
在我们的日常开发中,创建对象的操作随处可见以至于对其十分熟悉的同时又感觉十分繁琐,每次需要对象都需要亲手将其new出来,甚至某些情况下由于坏编程习惯还会造成对象无法被回收,这是相当糟糕的。但更为严重的是,我们一直倡导的松耦合,少入侵原则,这种情况下变得一无是处。于是前辈们开始谋求改变这种编程陋习,考虑如何使用编码更加解耦合,由此而来的解决方案是面向接口的编程,于是便有了如下写法:
public interface IUserService { ListlistUsers();}复制代码
同时,无论是我们编写的com.unis.team1.UserService还是别的小组编写的com.unis.team2.UserService,都实现了此IUSerService接口。
于是乎,罪恶之源代码被修改成:
import com.unis.team2.UserService;public Class UserAction{ public void listUser(){ IUserService userServiceInterface = new UserService(); userServiceInterface.listUser(); ...... }}复制代码
这样的做的好处是显而易见的,所有调用都通过接口IUserService来完成,而接口的真正的实现者和最终的执行者就是UserService,当替换UserService类,也只需修改userServiceInterface指向新的实现类。
虽然上述的代码在很大程度上降低了代码的耦合度,但是代码依旧存在入侵性和一定程度的耦合性,比如在修改IUserService的实现类时,仍然需要修改UserAction的内部代码,当依赖的类多起来时,查找和修改的过程也会显得相当糟糕,因此我们仍需要寻找一种方式,它可以令开发者在无需触及UserService内容代码的情况下实现修改IUserService的实现类,以便达到最低的耦合度和最少入侵的目的。
实际上存在一种称为反射的编程技术可以协助解决上述问题,反射是一种根据给出的完整类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才决定到底是哪一种对象,因此可以这样假设,在某个配置文件,通过读取该文件而获取到IUserService的真正实现类的名称,然后通过反射技术在运行时动态生成该类,最终赋值给IUserService接口,也就解决了刚才的存在问题,这里为简单演示,使用properties文件作为配置文件,className.properties如下:
IUserService.name=com.unis.team2.UserService;复制代码
修改后的UserAction如下:
public Class UserAction{ //读取配置文件的工具类 PropertiesUtil propertiesUtil=new PropertiesUtil("conf/className.properties"); public IUserService DynamicObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException { //获取完全限定名称 String className=propertiesUtil.get("IUserService.name"); //通过反射 Class c=Class.forName(className); //动态生成实例对象 return (IUserService) c.newInstance(); } public void listUser(){ IUserService userServiceInterface = DynamicObject(); userServiceInterface.listUser(); ...... }}复制代码
的确如我们所愿生成了IUserService的实例,这样做的好处是在替换IUserService实现类的情况只需修改配置文件的内容而无需触及UserAction的内部代码,从而把代码修改的过程转到配置文件中,这样UserAction与IUserService的实现类间也就实现了解耦合。
这个时候,我们无须修改代码,只需修改配置文件即可完成领导的任务。
总结
了解了上述的问题再来理解IOC就显得简单多了。Spring IOC 也是一个java对象,在某些特定的时间被创建后,可以进行对其他对象的控制,包括初始化、创建、销毁等。简单地理解,在上述过程中,我们通过配置文件配置了IUserService实现类的名称,然后利用反射在运行时为IUserService创建实际实现类,包括UserService的创建,Spring的IOC容器都会帮我们完成,而我们唯一要做的就是把需要创建的类和其他类依赖的类以配置文件或者注解的方式告诉IOC容器需要创建那些类和注入哪些类即可。
我们可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在配置文件(XML)或者注解中给出定义的,然后利用Java的反射技术,根据XML或注解中给出的类名生成相应的对象。从某种程度上来说,IoC相当于把在工厂方法里通过硬编码创建对象的代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性,更是达到最低的耦合度,因此我们要明白所谓为的IOC就将对象的创建权,交由Spring完成,从此解放手动创建对象的过程,同时让类与类间的关系到达最低耦合度。
最后,如果有读者想详细了解IOC和DI的话,建议去读一读:
https://juejin.im/post/5be976a76fb9a049fd0f5f31
这篇文章会从基本的问题入手,从源码的角度分析Spring