1、Spring IOC的理解
IOC:Inversion Of Control,即控制反转,是一种设计思想。在传统的 Java SE 程序设计中,我们直接在对象内部通过 new 的方式来创建对象,是程序主动创建依赖对象;而在Spring程序设计中,IOC 是有专门的容器去控制对象。
控制反转,也叫依赖注入,他就是不会直接创建对象,只是把对象声明出来,在代码 中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服 务,容器将他们组件起来。在一般的IOC场景中容器创建了所有的对象,并设置了必 要的属性将他们联系在一起,等到需要使用的时候才把他们声明出来,使用注解就跟 方便了,容器会自动根据注解把对象组合起来
所谓控制就是对象的创建、初始化、销毁。
-
创建对象:原来是 new 一个,现在是由 Spring 容器创建。
-
初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。
-
销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。
所谓反转:其实是反转的控制权,前面提到是由 Spring 来控制对象的生命周期,那么对象的控制就完全脱离了我们的控制,控制权交给了 Spring 。这个反转是指:我们由对象的控制者变成了 IOC 的被动控制者。
总结:IOC 解决了繁琐的对象生命周期的操作,解耦了我们的代码。
IOC解决了什么问题
-
对象之间的耦合度或者说依赖程度降低;
-
资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
IOC和DI的区别
IoC(Inverse of Control:控制反转)是一种设计思想或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权交给第三方比如 IoC 容器。 对于我们常用的 Spring 框架来说, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。不过,IoC 在其他语言中也有应用,并非 Spring 特有。
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
IOC思想是一种目标,DI是实现这种思想的目标的手段.
控制反转(IOC)有什么用?
管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要我们来维护的话,那是相当头疼的
解耦,由Spring容器去维护具体的对象
托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
IOC的优点有哪些?
IOC(依赖注入)把应用的代码量降到最低
它使应用容易测试,单元测试不在需要单例和JNDI查找机制
最小的代价和最小的侵入性使松散耦合得以实现
IOC容器支持加载服务时的饿汉式初始化和懒加载
Spring IOC的实现机制
工厂模式+反射机制
工厂模式
Spring使用了三种工厂模式(静态工厂、实例工厂、自定义工厂)来创建和管理Bean对象。
在工厂模式中,对象的创建逻辑对客户端是隐藏的,客户端只需知道接口而无需关心具体的实现细节。
通过工厂模式,Spring可以灵活地创建和管理Bean对象,支持扩展和配置。
Java反射机制
Java反射是Spring实现IOC的关键技术之一。它允许程序在运行时动态地获取类的内部信息(如属性、方法、构造器等),并可以动态地创建对象、调用方法、修改属性等。
在Spring中,反射机制被用于加载类、解析配置文件、创建和管理Bean对象等。
通过反射,Spring可以读取配置文件中的Bean定义,并根据定义动态地创建Bean对象,同时处理Bean之间的依赖关系。
Spring IOC的实现机制可以归纳如下:
定义Bean:通过XML配置文件、Java注解或Java配置类等方式定义应用程序中的各个组件(Bean)。
配置依赖关系:在定义Bean的同时,通过配置文件或注解等方式指定Bean之间的依赖关系。
创建容器:创建一个Spring容器实例(如ApplicationContext),该容器负责管理和创建Bean。
容器初始化:容器读取配置信息并初始化相应的Bean。这涉及到实例化对象、设置属性值、解析依赖关系等操作。
依赖注入:在容器初始化的过程中,Spring容器会自动解析Bean之间的依赖关系,并通过反射机制将依赖的对象注入到需要的地方。这可以通过构造函数、Setter方法或字段注入等方式进行。
使用Bean:容器初始化完成后,可以从容器中获取需要的Bean,并使用它们来执行相应的业务逻辑。
Spring提供了两种主要的IoC容器实现
-
BeanFactory: 是Spring IoC容器的基本实现,它是一个工厂模式的实现,延迟加载对象。
-
ApplicationContext: 是BeanFactory的扩展,提供了更多的功能,如国际化处理、事件传递、应用程序层特定上下文的配置等。
Spring IoC的工作流程
-
配置: 通过XML配置文件、注解或Java配置类配置Bean的信息和依赖关系。
-
实例化: IoC容器根据配置文件中定义的信息实例化Bean。
-
注入: IoC容器将Bean之间的依赖关系通过构造函数注入或Setter注入的方式进行赋值。
-
管理: IoC容器管理Bean的生命周期,包括初始化、使用和销毁。
2、代码实现
1)代码测试
public class SpringIOCTest {@Testpublic void testScan() throws FileNotFoundException {SpringIOC springIOC = new SpringIOC();springIOC.initBeans();TestController instance = (TestController)springIOC.getInstance(TestController.class.getName());instance.test();}
}
测试结果:
zhangsan
18
2)定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
定义两个注解@Autowired和@Component,SpringIoc中注解用来配置和管理Spring应用中的组件和依赖关系
@Target(ElementType.FIELD):这个元注解(meta-annotation)指定了@Autowired注解可以应用的Java元素类型。在这个例子中,它指定了@Autowired和@Component只能应用于字段(FIELD)。但实际上,Spring的@Autowired注解还可以应用于方法(METHOD)和构造器(CONSTRUCTOR)上。这里的描述是为了简化而只提到了字段。
@Retention(RetentionPolicy.RUNTIME):这个元注解指定了@Autowired和@Component注解的保留策略。RetentionPolicy.RUNTIME意味着这个注解不仅被保留在.class文件中,而且在运行时通过反射也是可访问的。这对于Spring框架在运行时通过反射机制读取注解并据此执行自动装配操作是必需的。
3)SpringIOC核心类
接下来由测试代码一步步进行分析SpringIOC的实现
第一步:IOC容器实例化,获取类信息
SpringIOC springIOC = new SpringIOC();
public SpringIOC() {initPath();try {scan();} catch (FileNotFoundException e) {e.printStackTrace();}beanNames= new ArrayList<>();initBeanNames();}
调用SpingIOC进行类实例化时,首先该类声明了五个全局变量如下,
private List<String> beanNames; //用来存储所有包名 private List<String> filePaths; //用来存储所有文件全路径 private String basePath; //用来存储类的路径 private String basePackage; //用来存储类的包路径 private Map<String, Object> beans =new HashMap<>(); //用来存储带有注解的类名->该类里面注入的对象
① initPath():获取当前类的类路径和包路径
private void initPath(){basePath="D:\\daima\\ioc-master\\src\\main\\java\\com\\xxhh\\springioc\\";basePackage="com.xxhh.springioc";}
② scan():扫描所有的文件信息,存到 filePaths
private void scan() throws FileNotFoundException {File file = new File(basePath);filePaths = new ArrayList<>();if(file.exists()){Queue<File> queue = new LinkedList<>();queue.add(file);while(!queue.isEmpty()){File poll = queue.poll();if(poll == null){continue;}if(poll.isDirectory()){File[] files = poll.listFiles();for (File f : files) {queue.add(f);}}else {filePaths.add(poll.getPath());}}}else {throw new FileNotFoundException(basePath+" not found");}}
③ initBeanNames():将所有的.java结尾的文件的全限定名放到 beanNames
public void initBeanNames(){for (String s : filePaths) {String replace = s.replace(basePath, "");if(replace.endsWith(".java")) {replace = replace.substring(0, replace.length()-5);}char[] chars = replace.toCharArray();for (int i = 0; i < chars.length; i++) {if(chars[i]=='\\'){chars[i] = '.';}}beanNames.add(basePackage+"."+new String(chars));}}
第二步:Bean的实例化与注册以及依赖自动注入
springIOC.initBeans();
public void initBeans(){for (String beanName : beanNames) {try {Class<?> aClass = Class.forName(beanName);//获取类注解Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();for (Annotation declaredAnnotation : declaredAnnotations) {if(declaredAnnotation instanceof Component){Object o = aClass.newInstance();beans.put(aClass.getName(),o);}}} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {e.printStackTrace();}}for (Map.Entry<String, Object> entry : beans.entrySet()) {//获取类中的字段Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();for (Field field : declaredFields) {Annotation[] declaredAnnotations = field.getDeclaredAnnotations();for (Annotation annotation : declaredAnnotations) {if(annotation instanceof Autowired ){//field 需要由我们来赋值// 我们所持有的所有对象 在beans中// 根据当前域中的类型的名字String name = field.getType().getName();// 从beans 中获得对应的对象Object o = beans.get(name);//设置字段的访问权限,使其即使是私有的(private)或受保护的(protected),也可以通过反射被访问和修改field.setAccessible(true);try {field.set(entry.getValue(), o);} catch (IllegalAccessException e) {e.printStackTrace();}}}}}}
步骤 1: Bean的实例化与注册
-
遍历所有Bean名称:
for (String beanName : beanNames)
循环遍历在之前步骤(在initBeanNames
方法)中收集的所有Bean的全限定名(即包括包名的类名)。 -
加载类并检查注解:对于每个Bean名称,使用
Class.forName(beanName)
加载类。然后,通过getDeclaredAnnotations()
获取该类上声明的所有注解。 -
实例化并注册Bean:在类的注解数组中,检查是否存在
@Component
(或任何其他表示该类应被注册为Bean的注解,这里假设@Component
是检查的重点)。如果找到,则通过aClass.newInstance()
(注意:在Java 9及更高版本中,推荐使用Class.getDeclaredConstructor().newInstance()
的替代方案,如Class.getDeclaredConstructor().newInstance()
的等价物,因为newInstance()
方法已被标记为过时)实例化该类,并将其添加到beans
映射中,键为类的全限定名,值为类的实例。
步骤 2: 依赖的自动注入
-
遍历已注册的Bean:
for (Map.Entry<String, Object> entry : beans.entrySet())
循环遍历beans
映射中的所有条目,每个条目代表一个已注册的Bean及其实例。 -
获取并遍历Bean的字段:对于每个Bean实例,使用
entry.getValue().getClass().getDeclaredFields()
获取其所有字段。然后,遍历这些字段。 -
检查字段的注解:对于每个字段,通过
getDeclaredAnnotations()
获取其声明的所有注解。 -
自动注入依赖:在字段的注解数组中,检查是否存在
@Autowired
注解。如果存在,表明该字段需要自动注入依赖。a. 获取依赖的Bean名称:通过
field.getType().getName()
获取依赖的类型的全限定名,这里假设该名称直接用作Bean的键(在真实应用中,这可能需要更复杂的逻辑,如支持类型到Bean名称的映射)。b. 从
beans
映射中获取依赖的Bean实例:使用依赖的类型的全限定名作为键,从beans
映射中获取对应的Bean实例。c. 设置字段值:通过反射将依赖的Bean实例设置到当前Bean实例的字段中。首先,通过
field.setAccessible(true)
确保即使字段是私有的或受保护的,也可以被访问。然后,使用field.set(entry.getValue(), o)
将依赖的Bean实例注入到当前Bean实例的字段中。
第三步:获取TestController
类的实例
TestController instance = (TestController)springIOC.getInstance(TestController.class.getName());
public Object getInstance(String beanName) {return beans.get(beanName);}
根据TestController类名找到该类里面注入的对象,即找到userService
TestController实现如下:
import com.xxhh.springioc.stereotype.Autowired;
import com.xxhh.springioc.stereotype.Component;@Component
public class TestController {@Autowiredprivate UserService userService;public void test(){userService.addUser("zhangsan",18);}}
UserService实现如下:
import com.xxhh.springioc.stereotype.Component;@Component
public class UserService {public void addUser(String name, int age) {System.out.println(name);System.out.println(age);}
}
第四步:调用TestController
实例的方法
instance.test();
由于TestController中的test()方法中调用userService中的addUser(String name, int age)方法,最终输出zhangsan 18
以上就是简单实现SpringIOC的全部源码,如有错误,欢迎指正!!!