1. IoC容器概述
IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。
2. 控制反转(IoC)
控制反转是一种思想,可以降低程序耦合度,提高程序扩展力。
控制反转, 就是将对象的创建以及对象和对象之间关系的维护(也就是对象中属性的赋值)交给第三方容器负责。
也就是说,我们可以不需要手动创建Bean对象以及手动对Bean对象中的属性赋值,这些操作都可以由IoC容器完成。
控制反转这种思想是根据依赖注入(Dependency Injection,DI)实现的。
3. 依赖注入
指Spring创建对象的过程中,将对象依赖属性通过配置进行注入。
依赖注入常见的实现方式包括两种:
- 第一种:set注入
- 第二种:构造注入
IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。
4. 代码实现
该代码只是粗略实现,目的是为了更好的展示IoC及依赖注入的实现放视。其中,Bean注解表示将该类交由IoC容器管理(spring中的@component),Di注解表示对该属性进行依赖注入(spring中的@Autowired)。
实现思路:
- 根据用户输入的包名,首先获取到这个包的所在路径。
- 之后,对这个路径下的所有文件及文件夹进行扫描,寻找添加了Bean注解的的类。
- 找到满足条件的类后创建该类的实例对象,并将这个类的实例对象以及其类型放入到map当中。其中,key是类型,value是实例对象。这样。我们就可以根据类型获取到相对应的实例对象。
- 将所有加了Bean注解的类都放入map之后,对map中的所有对象进行遍历。根据反射获取其所有属性,判断是否有属性添加了注解Di。
- 如果存在添加了注解Di的属性,我们就要根据其属性的类型在map中得到相对应类型的实例对象,并调用set函数将该对象赋值给该属性,完成依赖注入操作。
实现代码如下:
public class AnnotationApplicationContext implements ApplicationContext{//存放bean对象private HashMap<Class, Object> beanFactory = new HashMap<>();private static String rootPath;//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化public AnnotationApplicationContext(String basePackage) throws Exception {// 把.替换成\\String packageDir = basePackage.replaceAll("\\.", "\\\\");//得到绝对路径Enumeration<URL> absolutePath = Thread.currentThread().getContextClassLoader().getResources(packageDir);while (absolutePath.hasMoreElements()) {URL url = absolutePath.nextElement();//斜杠'/'可能被编码,因此需要解码String filePath = URLDecoder.decode(url.getFile(),"utf-8");//获得包前面的路劲,以便后续操作rootPath = filePath.substring(0, filePath.length()-packageDir.length());//包扫描scanBean(new File(filePath));//属性注入loadDi();}}private void loadDi() throws Exception {Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();//遍历map集合中的每个对象for(Map.Entry<Class, Object> entry : entries) {Object obj = entry.getValue();//得到每个对象的classClass<?> clazz = obj.getClass();//获得每个对象的所有属性Field[] declaredFields = clazz.getDeclaredFields();for(Field field : declaredFields) {//如果存在属性含有注解Diif(field.getAnnotation(Di.class) != null) {//如果时私有属性,设置可访问field.setAccessible(true);//为属性赋值,按类型获取对应的对象field.set(obj, beanFactory.get(field.getType()));}}}}private void scanBean(File file) throws Exception {// 如果当前是文件夹if(file.isDirectory()) {//获得所有子文件(夹)File[] childFiles = file.listFiles();//如果当前文件夹空if(childFiles == null || childFiles.length == 0) {return;}for(File childFile : childFiles) {//如果当前子文件时文件夹,递归if(childFile.isDirectory()) {scanBean(childFile);}else {//得到包路径+类名称String classPath = childFile.getAbsolutePath().substring(rootPath.length() - 1);//如果当前文件类型时classif(classPath.endsWith(".class")) {//把路径\替换成. 把.class去掉String allName = classPath.replaceAll("\\\\", ".").replace(".class", "");//获取类的classClass clazz = Class.forName(allName);//如果不是接口而且上面有注解Beanif(!clazz.isInterface() && clazz.getAnnotation(Bean.class) != null) {// 实例化对象Object instance = clazz.getConstructor().newInstance();// 如果当前类实现了接口if(clazz.getInterfaces().length > 0) {//把接口的类型作为该实例的类别beanFactory.put(clazz.getInterfaces()[0], instance);}else {//没有接口,就把该类的类型作为keybeanFactory.put(clazz, instance);}}}}}}}@Overridepublic Object getBean(Class clazz) {return beanFactory.get(clazz);}}
使用代码如下:
@Testpublic void testIoc() throws Exception {ApplicationContext context = new AnnotationApplicationContext("org.kkk.spring6");UserService userService = (UserService) context.getBean(UserService.class);userService.print();}