浅识IOC原理:手写IOC

本文详细介绍了如何手写一个简单的IoC容器,包括自定义单例对象和依赖注入的注解,以及如何使用这些注解创建并管理对象实例。特别提到了在多实现类情况下可能出现的空指针异常问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前提:用于存储对象的容器我们可以把它想象成一个Map集合,Map<Class, Object>,class为对象类型,object为实例化后的对象。

1.手写IOC需要自定义创建单例对象的注解和依赖注入的注解

项目结构如下:

2. 创建注解

光标移至相应的包下:右键->new->java class

2.1. @Bean注解如下,用于创建单例对象

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}

2.2. 用于依赖注入的注解

@Target({ElementType.FIELD}) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface DI { }

2.3. 用于获取IOC容器中的Bean接口及其实现类

接口:

public interface ApplicationContext { 
Object getBean(Class clazz);
}

实现类:

public class AnnotationApplicationContext implements ApplicationContext {
    private static final Map<Class, Object> beanFactory = new HashMap<>();   
    private static String rootPath;    
    @Override    
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);    }

    /**     
    * 创建有参构造,传递包路径,设置包扫描规则
    * 当前包及其子包,哪个类有@Bean注解,把这个类通过反射机制创建出来,放到beanFactory中   
    * @param basePackage 包路径     
    */    
    public AnnotationApplicationContext(String basePackage) throws IOException {
        String packagePath = basePackage.replace(".", "\\");        
        Enumeration<URL> resources =
                Thread.currentThread().getContextClassLoader().getResources(packagePath);        
                while (resources.hasMoreElements()) {
            URL url = resources.nextElement();            
            String filePath = URLDecoder.decode(url.getFile(), "UTF-8");            
            rootPath = filePath.substring(0, filePath.length() - packagePath.length());            
            // 包扫描            
            loadPackage(new File(filePath));        }
        loadDi();    
        }

    private static void loadPackage(File file) {
        // 1.判断是否是文件夹        
        if (file.isDirectory()) {
            // 2.获取文件夹中的所有内容            
            File[] childrenFiles = file.listFiles();            
            // 3.判断,内容为空直接返回            
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;            }
            // 4.不为空,遍历文件夹中的所有内容            
            Arrays.stream(childrenFiles).forEach(
                    item -> createBean(item)
            ); }
    }

    private static void createBean(File item) {
        boolean directory = item.isDirectory();        
        if (directory) {
            // 4.1 遍历得到每个file对象,继续判断,还是文件夹就遍历            
            loadPackage(item);
            } else {
            // 4.2 遍历得到的file对象不是文件夹,是文件。            // 4.3 包路径+类名称,            
            String pathWithClass = item.getAbsolutePath().substring(rootPath.length() - 1);            
            // 4.4 是否为.class文件的类型            
            if (pathWithClass.contains(".class")) {
                try {
                    // 4.5 .calss类型,把路径\替换成. 把.class去掉                    
                    String allName = pathWithClass.replaceAll("\\\\", ".")
                            .replace(".class", "");                    
                            // 4.6 判断类上是否有注解,有注解进行实例化的过程                    
                            Class<?> clazz = Class.forName(allName);                    
                            if (clazz.isAnnotationPresent(Bean.class)) {
                        Object instance = clazz.getConstructor().newInstance();                        
                        // 4.7 将实例化的对象放到map集合中,beanFactory                        
                        // 4.8 判断当前类如果有接口,让接口class作为map的key                        
                        if (clazz.getInterfaces().length > 0) {
                            beanFactory.put(clazz.getInterfaces()[0], instance);
                            } else {
                            beanFactory.put(clazz, instance);
                            }
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();                } catch (Exception e) {
                    e.printStackTrace();                }
            }
        }
    }

    //属性注入    
    public void loadDi(){
        //实例化对象在beanFactory集合里面        
        //1、遍历beanFactory中的map集合        
        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();        
        for (Map.Entry<Class, Object> entry : entries) {
            //2、获取map集合中的每个对象value 每个对象属性获取到            
            Object obj = entry.getValue();            
            Class<?> clazz = obj.getClass();            
            Field[] declaredFields = clazz.getDeclaredFields();            
            //3、遍历得到每个对象的属性数组 得到每个属性            
            for (Field declaredField : declaredFields) {
                //4、判断属性上是否有注解@Di                
                DI annotation = declaredField.getAnnotation(DI.class);                
                if (annotation != null) {
                    // 如果是私有属性 设置可以设置值                    
                    declaredField.setAccessible(true);                    
                    //5、如果有@Di注解 把对象进行设置(注入)                    
                    try {
                        declaredField.set(obj, beanFactory.get(declaredField.getType()));                    
                        } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);                    
                        }
                }
            }
        }
    }

}

2.4. 创建数据库访问层和业务层

数据访问层dao:

public interface UserDao {
    void addUser();}
@Bean
public class UserDaoImpl implements UserDao{
    @Override    
    public void addUser() {
        System.out.println("add....");    }
}

业务层service:

public interface UserService {
    void add();}
public interface AccountService {
    void addAccount();}
@Bean
public class UserServiceImpl implements UserService, AccountService {

    @DI    
    private UserDao userDao;    
    @Override    
    public void add(){
        System.out.println("adUser...");        userDao.addUser();    }

    @Override    
    public void addAccount() {
        System.out.println("addAccount");        userDao.addUser();    }
}

2.5. 用main方法测试

public class TestAnnotation {
    public static void main(String[] args) throws IOException {
        AnnotationApplicationContext context
                = new AnnotationApplicationContext("com.munal");        
                UserService userService = (UserService) context.getBean(UserService.class);        
                userService.add();        
                //AccountService accountService = (AccountService) context.getBean(AccountService.class);        
                //accountService.addAccount();    }
}

*注意:

运行main测试方法中被注释的代码会报空指针异常;

原因:该手写IOC的流程中,如果一个类中有多个实现类,map中指挥默认存一份,key值为实现的第一个接口的class,值为实例化对象。account接口并没有对应的实例化对象,所以accountService为null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值