轻松手写Spring的IOC

我们知道,ioc(控制反转)和di(依赖注入)是spring里面很重要的东西,那么,我们如何自己手写出这样的代码呢?

什么是IOC

IOC,Inversion of Control,即控制反转,这个不是一种什么技术,而是一种思想,平时我们写代码,都是把对象自己new出来,而现在我们用控制反转这种思想,把对象的创建交给了程序,那么我们就不需要手动创建对象了,这样降低了程序的耦合度。
这是spring的核心,贯穿始终。对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、wx号,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
轻松手写Spring的IOC_第1张图片
轻松手写Spring的IOC_第2张图片

那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。
轻松手写Spring的IOC_第3张图片

简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

什么是DI

DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器;

●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

IOC也叫依赖注入(DI)

2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

实现IOC和DI

首先,我们需要掌握两项必要的知识

  • 反射
  • 注解

如果对注解和反射没有怎么了解的,可以看看我之前的写的博客,java的反射和注解

建立项目

首先,我们先创建基本的项目,并且分好包

  • dao
  • entity
  • service
  • Annoation
  • reflect

轻松手写Spring的IOC_第4张图片

我们模拟的例子是学生借书,所以,我们先创建好每个包下的java文件

实体类下的User,Book

public class Book {
    private int id;
    private String name;

    public Book(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Book(){}

    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;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
public class User implements Serializable {
    private int id;
    private String username;
    private String password;
    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }
    public User(){}
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }



    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

dao层的UserDao,UserDaoImpl,BookDao,BookDaoImpl

public interface BookDao {
    User findBookById();
    List<User> findAllBook();
    List<User>findBookByBookName(String username);
    void save(Book book);
}

public class BookDaoImpl implements BookDao {
    @Override
    public User findBookById() {
        System.out.println("这里是BookDao-findBookById");
        return null;
    }

    @Override
    public List<User> findAllBook() {
        System.out.println("这里是BookDao-findAllBook");
        return null;
    }

    @Override
    public List<User> findBookByBookName(String username) {
        System.out.println("这里是BookDao-findBookByBookName");
        return null;
    }

    @Override
    public void save(Book book) {
        System.out.println("这里是BookDao-save");

    }
}

public interface UserDao {
    User findUserById();
    List<User>findAllUser();
    List<User>findUserByUserName(String username);
    void save(User user);
}
public class UserDaoImpl implements UserDao {
    @Override
    public User findUserById() {
        System.out.println("这里是UserDao-findUserById");
        return null;
    }

    @Override
    public List<User> findAllUser() {
        System.out.println("这里是UserDao-findAllUser");
        return null;
    }

    @Override
    public List<User> findUserByUserName(String findUserByUserName) {
        System.out.println("这里是UserDao-findUserById");
        return null;
    }

    @Override
    public void save(User user) {
        System.out.println("这里是UserDao-save");
    }
}

service层的UserService,UserserviceImpl,BookService,BookServiceImpl

public interface BookService {
    void  borrow(User user, Book book);
}
public class BookServiceImpl implements BookService {
  
    @Override
    public void borrow(User user,Book book) {
        
    }
}

public interface UserService {
    void  login();
    void regist();
}

public class UserServiceImpl implements UserService {

   private UserDao userDao=new UserDaoImpl();
    @Override
    public void login() {
        userDao.findUserByUserName("刘水龙");
        System.out.println("登录业务的实现");
    }

    @Override
    public void regist() {
        userDao.save(new User(1,"刘水龙","222"));
        System.out.println("注册业务的实现");
    }
}

基本思路

通过配置文件把类放入ioc容器

我们知道ioc有一个ioc容器,把全部的对象放入ioc容器之中,这样我们就不需要自己去手动创建对象了,那么我们是怎么把对象放入ioc容器之中的呢?很简单,在这里我们使用了Map

我们在refect包下创建ApplicationContext的java文件

public class ApplicationContext<T> {

    private  HashMap<Class,Object> beanFactory=new HashMap<>();
    private static String filePath;
    public T getBean(Class clazz){
        return (T)beanFactory.get(clazz);
    }

}

这里我们创建了一个bean工厂,在ApplicationContext被实例化后,就会去自动读取配置类文件,把全部的需要放入ioc的容器放入bean工厂,那么如何把配置类文件放入bean工厂呢?我们需要新建议一个类

我们在ApplicationContext下写一个initContext方法,同时创建一个和com文件同级的config文件夹,写好配置文件

轻松手写Spring的IOC_第5张图片

com.znb.service.BookService=com.znb.service.BookServiceImpl
com.znb.dao.BookDao=com.znb.dao.BookDaoImpl

 public void initContext() throws Exception {
        InputStream resource = ApplicationContext.class.getClassLoader().getResourceAsStream("config/bean.config");
        Properties properties=new Properties();
        properties.load(resource);
        Set<Object> keys=properties.keySet();
        System.out.println(properties);
        System.out.println(keys);
        for (Object key : keys) {
//             System.out.println(key.toString());
            beanFactory.put(Class.forName(key.toString()), Class.forName(properties.getProperty(key.toString())).newInstance());
        }
        System.out.println(beanFactory.keySet());
         System.out.println(beanFactory);

    }

在这里我们通过properties类获取配置文件,然后把properties的文件的key放进set集合中,
然后通过反射,将全部的类放进bean工厂,之后需要的时候,就可以直接从bean工厂获取

现在,我们测试一下,写一个BootStrap类

public class BookStrap {
    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ApplicationContext();
        applicationContext.initContext();

    }
}

在这里插入图片描述
可以看到,当我们需要的时候,直接从bean工厂里面拿就可以了,这就是通过配置文件把类放入ioc容器

通过注解把类放入ioc容器

我们通过配置文件把类放入ioc容器,但是这样似乎很麻烦,每次需要放入ioc容器的时候,我们都需要自己去写一次配置文件,那么我们也没有什么方法简化这个步骤呢?

新建注解

我们在Annotation文件下新建一个注解 Bean

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

在ApplicationContext下写两个方法,一个initContextByAnnotation,一个loadOne

 public void initContextByAnnotation() throws Exception {
       //扫描包
        filePath = ApplicationContext.class.getClassLoader().getResource("").getFile();
        loadOne(new File(filePath));

    }
private  void loadOne(File fileParent) {
        if (fileParent.isDirectory()) {
            File[] childrenFiles = fileParent.listFiles();
            if(childrenFiles == null || childrenFiles.length == 0){
                return;
            }
            for (File child : childrenFiles) {
                if (child.isDirectory()) {
                    //如果是个文件夹就继续调用该方法,使用了递归
                    loadOne(child);
                } else {
                    //通过文件路径转变成全类名,第一步把绝对路径部分去掉

                    String pathWithClass = child.getAbsolutePath().substring(filePath.length() - 1);
                    //选中class文件
                    if (pathWithClass.contains(".class")) {
                        //    com.xinzhi.dao.UserDao
                        //去掉.class后缀,并且把 \ 替换成 .
                        String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                        try {
                            Class<?> aClass = Class.forName(fullName);


                            //把非接口的类实例化放在map中
                            if(!aClass.isInterface()){
                                Bean annotation = aClass.getAnnotation(Bean.class);
                                if(annotation != null){
                                    Object instance = aClass.newInstance();
                                    //判断一下有没有接口
                                    if(aClass.getInterfaces().length > 0) {
                                        //如果有接口把接口的class当成key,实例对象当成value
                                        System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass.getInterfaces()[0], instance);
                                    }else{
                                        //如果有接口把自己的class当成key,实例对象当成value
                                        System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName());
                                        beanFactory.put(aClass, instance);
                                    }
                                }
                            }
                        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

然后,我们给全部的接口实现类写上@Bean注解

@Bean
public class BookDaoImpl implements BookDao {
    @Override
    public User findBookById() {
        System.out.println("这里是BookDao-findBookById");
        return null;
    }

    @Override
    public List<User> findAllBook() {
        System.out.println("这里是BookDao-findAllBook");
        return null;
    }

    @Override
    public List<User> findBookByBookName(String username) {
        System.out.println("这里是BookDao-findBookByBookName");
        return null;
    }

    @Override
    public void save(Book book) {
        System.out.println("这里是BookDao-save");

    }
}

都按照这个格式写,其余的我就省略了,然后我们测试一下

 public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ApplicationContext();
        applicationContext.initContextByAnnotation();
    }

结果:
轻松手写Spring的IOC_第6张图片
这个时候,写了@Bean的类都全部进入了IOC容器,当我们需要,只需要自己调用就可以了

依赖注入

我们已经简单的实现了IOC,那么现在来实现一下依赖注入,我们在ApplicationContext里面新写一个assembleObject方法,然后在initContextByAnnotation调用

 private void assembleObject() {
        for(Map.Entry<Class,Object> entry : beanFactory.entrySet()){
            //就是咱们放在容器的对象
            Object obj = entry.getValue();
            Class<?> aClass = obj.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields){
                AutoWired annotation = field.getAnnotation(AutoWired.class);
                if( annotation != null ){
                    field.setAccessible(true);
                    try {
                        System.out.println("正在给【"+obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】");
                        field.set(obj,beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

 public void initContextByAnnotation() throws Exception {
       //扫描包
        filePath = ApplicationContext.class.getClassLoader().getResource("").getFile();
        loadOne(new File(filePath));
        assembleObject();
    }

然后我们在去接口实现类上写上自己需要的接口,以及注释

public class BookServiceImpl implements BookService {
    @AutoWired
    private UserDao userDao;
    @AutoWired
    private BookDao bookDao;
    @Override
    public void borrow(User user,Book book) {
        userDao.save(user);
        System.out.println(user.getUsername()+"借了"+book.getName());
    }
}

测试一下

public class BookStrap {
    public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ApplicationContext();
//        applicationContext.initContext();
//        System.out.println("======================");
//         System.out.println(applicationContext.getBean(BookDao.class));

        applicationContext.initContextByAnnotation();
        BookService bean = (BookService)applicationContext.getBean(BookService.class);
        bean.borrow(new User(1,"刘水龙","00"),new Book(1,"黄书"));
    }
}

结果:
轻松手写Spring的IOC_第7张图片

总结:

在我们写ssm项目,或者springboot项目的时候,我们都会要用到ioc和di,因为这样可以降低系统的耦合性,以后修改迭代起来也很方便,但是由于ioc和di采用了大量的反射,所以性能消耗很大,而且由于ioc容器注入了大量的对象,所以启动起来会比较的慢。
整体的思想,是通过反射,来将对象注入ioc的容器里面,然后直接把接口的实现类创建,这样就不需要自己new对象,就可以需要什么就拿什么了。

你可能感兴趣的:(源码,spring,java,spring,boot)