手写Spring IOC-简易版

发布于:2024-10-18 ⋅ 阅读:(6) ⋅ 点赞:(0)

项目结构

在这里插入图片描述

entity

  1. User
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User implements Serializable {
    private String username;
    private String password;
    private int age;
}

dao

IUserDao
public interface IUserDao {
    /**
     * Add user
     */
    int addUser(User user);

    /**
     * Get user by id.
     */
    User getUserById(int id);

    /**
     * Get all users.
     */
    List<User> getAllUsers();

    /**
     * Get users by name.
     */
    List<User> getUsersByName(String name);
}
UserDaoImpl
public class UserDaoImpl implements IUserDao {
    @Override
    public int addUser(User user) {
        System.out.println("Dao: addUser()");
        return 0;
    }

    @Override
    public User getUserById(int id) {
        System.out.println("Dao: getUserById()");
        return null;
    }

    @Override
    public List<User> getAllUsers() {
        System.out.println("Dao: getAllUsers()");
        return Collections.emptyList();
    }

    @Override
    public List<User> getUsersByName(String name) {
        System.out.println("Dao: getUsersByName()");
        return Collections.emptyList();
    }
}

service

IUserService
public interface IUserService {
    void login();

    void register();
}
UserServiceImpl
public class UserServiceImpl implements IUserService {

    /**
     * Each time this interface is called, aenw UserDaoImpl object is created,
     * resulting in resource waste or causing OutOfMemoryError in serious cases.
     */
    IUserDao userDao = new UserDaoImpl();

    @Override
    public void login() {
        userDao.getAllUsers();
        System.out.println("This is implementation o login method.");
    }

    @Override
    public void register() {
        userDao.getAllUsers();
        System.out.println("This is implementation o register method.");
    }
}

假设 Web 应用程序现在运行正常。
每次用户调用 IUserService 接口时,都会创建一个 IUserDaoImpl 对象,这会导致资源浪费,在严重情况下可能会引发 OutOfMemoryError

IUserDao userDao = new UserDaoImpl();

如何解决这个问题****❓

我们可以将 IUserDaoImplClass 作为 key,IUserDaoImpl 实例作为 value 存储在一个HashMap<Class, Object>中。

  1. 在 Web 应用程序运行之前,将 ApplicationConext 类中的对象加载并初始化到 IOC 容器中。
  2. 当调用 IUserService 接口时,Java 会获取 IUserDaoImpl 对象,而不再需要创建新的 IUserDaoImpl对象。

ApplicationContext

public class ApplicationContext {
    // Store bean objects in a Hashmap, equivalent to ioc container.
    private HashMap<Class<?>, Object> beanFactory = new HashMap<>();

    /**
     * init ApplicationContext, and put bean objects into IOC container.
     */
    public void initContext() throws IOException, ClassNotFoundException {
        // Load specified objects to IOC container.
        beanFactory.put(IUserService.class,new UserServiceImpl());
        beanFactory.put(IUserDao.class,new UserDaoImpl());
    }

    /**
     * get bean object by class from IOC container.
     */
    public Object getBean(Class<?> clazz) {
        return beanFactory.get(clazz);
    }

    /**
     * return all bean objects in IOC container.
     */
    public HashMap<Class<?>, Object> getAllBeans() {
        return beanFactory;
    }
}

配置文件初始化 IOC 容器

❓****问题:如上述代码所示,我们可以将指定的对象加载到IOC容器中,但如果我们需要添加一些新对象时,就必须修改源代码,这非常麻烦。

public void initContext() throws IOException, ClassNotFoundException {
    // Load specified objects to IOC container.
    beanFactory.put(IUserService.class,new UserServiceImpl());
    beanFactory.put(IUserDao.class,new UserDaoImpl());
    // Add new objects into beanFactory.
    // beanFactory.put(xxxx.class,new xxxxx());
    // ...........
}

为了解决这个问题,我们可以使用 Java 中的反射。

  1. 创建一个application.properties文件,在其中可以添加我们需要的对象信息。
IOC.service.IUserService = IOC.service.Impl.UserServiceImpl
IOC.dao.IUserDao = IOC.dao.Impl.UserDaoImpl
  1. 利用反射机制加载对象,并存储到 IOC 容器。
public void initContext() throws IOException, ClassNotFoundException {
    // Load application.properties file and Get information of bean object.
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/main/resources/application.properties"));

    Set<Object> keys = properties.keySet();
    for (Object key : keys) {
        Class<?> keyClass = Class.forName(key.toString());

        String value = properties.getProperty(key.toString());
        Class<?> valueClass = Class.forName(value);
        Object instance = valueClass.newInstance();

        // put bean object into IOC container.
        beanFactory.put(keyClass, instance);
    }
}

RunApplication

public class RunApplication {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ApplicationContext applicationContext = new ApplicationContext();
        applicationContext.initContext();

        // Get all beans.
        HashMap<Class<?>, Object> allBeans = applicationContext.getAllBeans();
        Set<Class<?>> classes = allBeans.keySet();
        for (Class<?> clazz : classes) {
            System.out.println(clazz.getName() + " -> " + allBeans.get(clazz));
        }
    }
}


注解初始化 IOC 容器

@Bean

  1. 新增 annotation 包,创建 Bean 注解类。
@Target(ElementType.TYPE)    // 只能修饰类型元素
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {}
  1. UserDaoImplUserServiceImpl 类上加上 **@Bean **注解。
@Bean
public class UserServiceImpl implements IUserService {}

@Bean
public class UserDaoImpl implements IUserDao {}
  1. ApplicationContext 类中添加使用注解初始化 bean 对象。
// root path of project.
private String filePath;

/**
* Load all bean objects with annotation @Bean from the project path.
*/
public void initContextByAnnotation() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    // Get the project path.
    filePath = Objects.requireNonNull(ApplicationContext.class.getClassLoader().getResource("")).getFile();

    // Load all bean objects into IOC container.
    loadOne(new File(filePath));
}

/**
* Load all class file with @Bean.
*/
public void loadOne(File fileParent) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    if(!fileParent.isDirectory()){
        return;
    }

    File[] childrenFiles = fileParent.listFiles();
    if (childrenFiles == null) {
        return;
    }
    for (File child : childrenFiles) {
        if (child.isDirectory()) {
            loadOne(child);
        } else {
            String pathWithClass = child.getAbsolutePath().substring(filePath.length() - 1);
            
             // Get file name like UserServiceImpl.class
            if (pathWithClass.contains(".class")) {
                String fullName = pathWithClass
                .replaceAll("\\\\", ".")
                .replace(".class", "");

                Class<?> clazz = Class.forName(fullName);
                if (!clazz.isInterface()) {
                    Bean annotation = clazz.getAnnotation(Bean.class);
                    if (annotation != null) {
                        Object instance = clazz.newInstance();

                        Class<?>[] interfacesList = clazz.getInterfaces();
                        // interface as key, if object has no interface.
                        if (interfacesList.length > 0) {
                            Class<?> interfaceClass = interfacesList[0];
                            beanFactory.put(interfaceClass, instance);
                        }
                        // clazz as key, if object has interface.
                        else {
                            beanFactory.put(clazz, instance);
                        }
                    }
                }
            }
        }
    }
}
  1. 修改 RunApplication 启动类中使用注解初始化 IOC 容器。
public class RunApplication {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new ApplicationContext();
        //applicationContext.initContext();
        // Load beans by @Bean.
        applicationContext.initContextByAnnotation();
        
        // Get all beans.
        HashMap<Class<?>, Object> allBeans = applicationContext.getAllBeans();
        Set<Class<?>> classes = allBeans.keySet();
        for (Class<?> clazz : classes) {
            System.out.println(clazz.getName() + " -> " + allBeans.get(clazz));
        }
    }
}

输出:

@Autowired

  1. 在 annotation 包中添加 Aurowired 注解类。
@Target(ElementType.FIELD)    // 只能修饰成员变量
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {}
  1. UserServiceImpl 成员变量添加 @Autowired 注解。
@Bean
public class UserServiceImpl implements IUserService {
    @Autowired
    private IUserDao userDao;
}
  1. ApplicationContext 类中添加使用注解初始化 bean 对象后,初始化各个 bean 对象的成员变量。
    1. 这里默认只实现了根据类型初始化成员变量(在 springboot 中支持类型名称)。
/**
* Load all bean objects with annotation @Bean from the project path.
*/
public void initContextByAnnotation() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    // Get the project path.
    filePath = Objects.requireNonNull(ApplicationContext.class.getClassLoader().getResource("")).getFile();

    // Load all bean objects into IOC container.
    loadOne(new File(filePath));

    // initiate fields of object.
    assembleObject();
}

/**
* @Autowired annotation to initiate fields of bean objects.
*/
public void assembleObject() throws IllegalAccessException {
    Set<Class<?>> keys = beanFactory.keySet();
    for (Class<?> key : keys) {
        Object value = beanFactory.get(key);
        Class<?> clazz = value.getClass();
    
        // Set value of field by @Autowired.
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field filed : declaredFields) {
            Autowired annotation = filed.getAnnotation(Autowired.class);
            if (annotation != null) {
                filed.setAccessible(true);
    
                // Get bean object by type from IOC container.
                Object object = beanFactory.get(filed.getType());
                filed.set(value, object);
            }
        }
    }
}
  1. 答复
public class RunApplication {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ApplicationContext applicationContext = new ApplicationContext();
        //applicationContext.initContext();
        applicationContext.initContextByAnnotation();
        
        // Get all beans.
        HashMap<Class<?>, Object> allBeans = applicationContext.getAllBeans();
        Set<Class<?>> classes = allBeans.keySet();
        for (Class<?> clazz : classes) {
            System.out.println(clazz.getName() + " -> " + allBeans.get(clazz));
        }

        // Get bean object by class type.
        IUserService bean = (IUserService) applicationContext.getBean(IUserService.class);
        // call login method.
        bean.login();
    }
}

输出:

Dao 层调用了 getAllUsers() 方法,说明依赖注入成功。

源码https://github.com/RainbowJier/HandWrittenSpring

Reference

  1. 一堂课深挖java反射机制,手撸一个ioc,实现自动装配,顺便spring也了解了_哔哩哔哩_bilibili