理解Spring原理 - 手写IOC和DI

本文完整资源包,懒得一步步操作的同学可以移步下载:
CSDN资源-手写IOC和DI完整代码下载

回顾Java反射

我们都知道,Spring框架的IOC是基于Java的反射机制实现的,下面我们先回顾一下Java反射:
Java反射机制是在运行状态中,对于任意类,都能够知道这个类的属性和方法;对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象的方式称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
想要解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关应用程序接口(1)java.lang.Class(2)java.lang.reflect,所以,Class对象是反射的根源。

自定义User类:

User类中包括三个私有属性、set get方法、一个无参构造方法、一个有参构造方法和一个私有普通方法run

package user;
/**
 * @author Sean Zhang
 * @date 2023/9/20 23:37
 */
public class User {
    private int id;
    private String name;
    private int age;
    public User() {
    }
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    private void run() {
        System.out.println("私有方法run...");
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

自定义UserTest类:

UserTest主要测试一下问题:

  1. 获取Class的多种方式
  2. 获取构造方法
  3. 获取属性
  4. 获取方法
package user;
import org.junit.jupiter.api.Test;
/**
 * @author Sean Zhang
 * @date 2023/9/20 23:45
 */
public class UserTest {
    // 1. 获取Class的多种方式
    @Test
    public void testClass() {
    }
    // 2. 获取构造方法
    @Test
    public void testConstructor() {
    }
    // 3. 获取属性
    @Test
    public void testProperty() {
    }
    // 4. 获取方法
    @Test
    public void testMethod() {
    }
}

测试1:获取Class的多种方式testClass

获取Class的三种方式

  1. 类名.class
  2. 对象.getClass()
  3. Class.forName("全路径")
public void testClass() throws ClassNotFoundException {
    // 1. `类名.class`
    Class<User> userClass1 = User.class;
    // 2. `对象.getClass()`
    Class<? extends User> userClass2 = new User().getClass();
    // 3. `Class.forName("全路径")`
    Class<?> userClass3 = Class.forName("user.User");
}

注:获取Class对象之后,对对象实例化:

User user = userClass1.getDeclaredConstructor().newInstance();

测试2:获取构造方法testConstructor

获取构造方法testConstructor步骤

  1. 获取Class
  2. 获取所有构造方法
 public void testConstructor() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<User> userClass = User.class;
        Constructor<?>[] constructors = userClass.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("方法名称:" + constructor.getName() + "参数个数:" + constructor.getParameterCount());
        }
        Constructor<?>[] publicConstructors = userClass.getConstructors();
        //构造创建对象
        Constructor<User> constructor = userClass.getDeclaredConstructor(int.class, String.class, int.class);
        //私有的要设置允许
        constructor.setAccessible(true);
        User user = constructor.newInstance(1, "张三", 18);
    }

注:仅仅获得public构造方法:

Constructor<?>[] publicConstructors = userClass.getConstructors();

测试3:获取属性testProperty

 public void testProperty() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<User> userClass = User.class;
        User user = userClass.getDeclaredConstructor().newInstance();
        Field[] declaredFields = userClass.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.getName().equals("name")) {
                //私有的属性要设置允许
                field.setAccessible(true);
                field.set(user, "李四");
            }
            System.out.println(field.getName());
        }
    }

注:私有的属性要设置允许访问:

field.setAccessible(true);

测试4:获取方法testMethod

  public void testMethod() throws InvocationTargetException, IllegalAccessException {
        User user = new User(2, "王五", 20);
        Class<? extends User> clazz = user.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("run")) {
                method.setAccessible(true);
                Object invoke = method.invoke(user);
                System.out.println("run执行了" + invoke);
            }
        }
    }

注:私有的方法要设置允许访问:

method.setAccessible(true);

实现Spring的IOC

我们知道,IOC和DI是Spring里面最核心的东西,,下面我们一步步写出这两个模块。

步骤:

  1. 创建测试类service和dao
  2. 手写两个注解:@Bean 创建对象 @DI 属性注入
  3. 创建Bean容器接口ApplicationContext定义方法,返回对象
  4. 实现bean容器接口
    a. 返回对象
    b. 根据包规则加载bean

步骤1:手写两个注解:@Bean 创建对象 @DI 属性注入:

package com.hand.anno;
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 Bean {
}
package com.hand.anno;
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 Di {
}

步骤2:创建测试类service和dao:

service和dao类上面标注注解@Bean,具体@Bean实现控制反转后面在ApplicationContext处理
service对dao的依赖要标注注解@DI,具体@DI实现依赖注入后面在ApplicationContext处理

dao:

package com.hand.dao;
public interface UserDao {
    void add();
}
package com.hand.dao.impl;
import com.hand.MyLog;
import com.hand.anno.Bean;
import com.hand.dao.UserDao;
@Bean
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        MyLog.logger.info("dao add ...");
    }
}

service:

package com.hand.service;
public interface UserService {
    void add();
}
package com.hand.service.impl;
import com.hand.MyLog;
import com.hand.anno.Bean;
import com.hand.anno.Di;
import com.hand.dao.UserDao;
import com.hand.service.UserService;
@Bean
public class UserServiceImpl implements UserService {
    @Di
    private UserDao userDao;
    @Override
    public void add() {
        MyLog.logger.info("service add ...");
        userDao.add();
    }
}

步骤3:创建接口类ApplicationContext及其实现类AnnoApplicationContext:

主要是使用反射进行对象创建和依赖注入

package com.hand.bean;
public interface ApplicationContext {
    Object getBean(Class clazz);
}
package com.hand.bean;
import com.hand.MyLog;
import com.hand.anno.Bean;
import com.hand.anno.Di;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class AnnoApplicationContext implements ApplicationContext {
    private String rootPath = null;
    private Map<Class, Object> beanFactory = new HashMap<>();
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }
    public AnnoApplicationContext(String packageStr) {
        try {
            String packagePath = packageStr.replaceAll("\\.", "\\\\");
            Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//                MyLog.logger.info(filePath);
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                loadBean(new File(filePath));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        loadDi();
    }

    /**
     * 对象加入容器
     */
    private void loadBean(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()) {
                    loadBean(childFile);
                    continue;
                }
                String pathWhitClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
                if (!pathWhitClass.contains(".class")) {
                    continue;
                }
                String allName = pathWhitClass.replaceAll("\\\\", ".").replace(".class", "");
                MyLog.logger.info("类:" + allName);
                Class<?> clazz = Class.forName(allName);
                if (clazz.isInterface()) {
                    continue;
                }
                Bean bean = clazz.getAnnotation(Bean.class);
                if (bean != null) {
                    Object instance = clazz.getConstructor().newInstance();
                    if (clazz.getInterfaces().length > 0) {
                        beanFactory.put(clazz.getInterfaces()[0], instance);
                    } else {
                        beanFactory.put(clazz, instance);
                    }
                }
            }
        }
    }

    /**
     * 依赖注入
     */
    private void loadDi() {
        for (Object obj : beanFactory.values()) {
            Class<?> clazz = obj.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                Di di = field.getAnnotation(Di.class);
                if (di == null) {
                    continue;
                }
                field.setAccessible(true);
                try {
                    field.set(obj, beanFactory.get(field.getType()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

步骤4:测试:

package com.hand;
import com.hand.bean.AnnoApplicationContext;
import com.hand.service.UserService;
public class Ioc {
    public static void main(String[] args) {
        AnnoApplicationContext context = new AnnoApplicationContext("com.hand");
        UserService userService = (UserService) context.getBean(UserService.class);
        userService.add();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值