分享一道面试题:模拟Spring IOC 控制反转实现原理,建议收藏!

前程有光

前言

Spring IOC控制反转是通过反射机制与工厂模式的结合,下面给大家模拟一下

生活案例引入

2000 年,你们家开了一家叫 “笑笑” 的包子铺 ------爸
2002 年,你们城东新开了一家分店 “笑笑” 包子铺------妈
2004 年,你们城西也新开了一家分店"笑笑" 包子铺-----你

多年后,包子铺越开越多,口味难以保证

“笑笑” 包子铺老板:以后我们几个核心班子只在一家做包子,所有分店不在做包子,直接来我们这里提货即可
        带来的好处:
                1、所有包子铺的口味一致
                2、所有包子铺不在关注做包子,只卖,做包子的过程(控制权限)–某一个工厂里面—IOC(Spring的控制反转)------>工厂模式

代码案例

构建纯净类

public class Book {
    private String name;
    private String author;
    private double price;
    
    public Book (){}
    
    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }
}
class AB extends Book{}
class AC extends Book{}

传统方式构建实例

  //1、传统方式
  //弊端:需要自己构建对象(new),若项目中有几百个类,通过该方式构建实例不可取
  Book b1 = new AB();
  Book b2 = new AC();

纯净类的工厂类BookFactory


public class BookFactory {
    //使用简单反射升级
    public static Book getBook1() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //1、先使用反射获取Book的Class对象
        Properties pro = new Properties();
        //2、获取配置文件信息,加载配置文件
        pro.load(new FileInputStream("D://JavaServletProject//ServletDemo//ZhenAiDemoVersionTwo//src//org//soffteem//sources//book//book.properties"));
        //3、得到配置文件里面的key值,根据key值反射得到class对象
        Class<Book> bookClass = (Class<Book>)Class.forName(pro.getProperty("className"));
        //4、获取有参构造器
        Constructor<Book> cons = bookClass.getDeclaredConstructor(String.class,String.class,double.class);
        //5、通过反射构建对象实例
        return cons.newInstance(pro.getProperty("name"),pro.getProperty("author"),Double.parseDouble(pro.getProperty("price")));
    }

    //如果用户过来需要书,会根据类名去构建书对象,原始方法
    public static Book getBook(String name){
        Book book = null;
        if (name.equals("AB")){
            b = new AB();
        }else if (name.equals("AC")){
            b = new AC();
        }else {
            //....如果有很多的书籍多次判定.....
        }
        return book;
    }
}

传统方式上升级

//2、现在有了笑笑包子铺(纯净类工厂)
//弊端:此时不需要构建对象,但是依然需要手动输入对象名称
 System.out.println("请您输入需要的书名");
 //....此处省略部分代码......
 Book book = BookFactory.getBook("你输入的名字");

通过简单反射升级

className=org.soffteem.myspring.Book
author=王大锤
name=倚天屠龙记
price=38.9
//3、不用输入就能构建对象
//弊端:如果不是Book对象而是其他对象呐?调用该工厂的这个方法只能够返回Book对象
System.out.println("获取书籍:" + BookFactory.getBook1());

类似Spring IOC容器的工厂构建

XML文件

<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <!--书籍类-->
    <bean id="book" class="org.soffteem.myspring.Book">
        <property name="name" value="倚天屠龙记"></property>
        <property name="author" value="金庸"></property>
        <property name="price" value="25.3"></property>
    </bean>
    <!--各种其他类-->
    <!--此处的student类没有展示-->
    <bean id="student" class="org.soffteem.test.Student"></bean>
</beans>

类似"Spring IOC容器"类

public class ApplicationContextUtils {
    //文档对象
    Document document;
    //1、初始化文档对象
    public ApplicationContextUtils() throws DocumentException {
        //1.1 通过dom4j的jar读取xml文件,获取文档对象
        SAXReader reader = new SAXReader();
        this.document = reader.read("D://JavaServletProject//ServletDemo//ZhenAiDemoVersionTwo//src//org//soffteem//sources//book//books.xml");
    }

    //2、反射 + 集合 + xml解析 获取Bean实例
    public <T>T getBean(String id,Class<T> tc) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //2.1 解析xml文件,获取根节点 <beans><beans>
        Element element = document.getRootElement();
        //2.2 获取根节点(<beans>)所有的子节点 <bean></bean>
        List<Element> list = element.elements();
        //3、迭代所有的<bean>节点
        for (Element e:list){
            //3.1 得到<bean>子节点中id的值
            String myId = e.attributeValue("id");
            //3.2 得到<bean>子节点中class的数值
            String className = e.attributeValue("class");
            //3.3 判定:根据用户输入的id进行指定的实例化
            //因为此处可能不只只有一个纯净类的节点配置项,要根据用户输入的id值实例化执行的配置节点
            if (myId.equals(id)){
                //3.3.1 反射当前id对应的className的class对象
                Class c = Class.forName(className);
                //3.3.2 再次判定:若是父类呐?此处判断父类我是通过class对象判定的
                if (c == tc){
                    //3.3.3 通过反射构建对象
                    T obj = tc.newInstance();
                    //3.3.4 得到当前<bean>节点中的所有<properties>子节点
                    List<Element> props = e.elements();
                    //3.3.5 得到所有的孙子节点对象<properties>
                    for (Element prop:props){
                        //3.3.5.1 得到孙子节点的每一个属性内容name与value对应的值
                        String name = prop.attributeValue("name");
                        String value = prop.attributeValue("value");
                        //3.3.5.2 将内容赋值到对象属性中
                        setAttribute(tc.getDeclaredFields(),obj,name,value);
                    }
                    //返回对象
                    return obj;
                }
            }
        }
        return  null;
    }

    //封装给属性赋值的方法
    //① 需要得到所有的属性 ② 给哪个对象赋值 ③ 给哪个属性赋值 ④赋值的内容
    public <T>void setAttribute(Field[] fields,T obj,String key,String value) throws IllegalAccessException {
        //1、循环属性,得到具体的属性值
        for (Field field:fields){
            //2、要想给属性赋值,由于属性是私有的,需要设置属性的访问权限
            field.setAccessible(true);
            //3、考虑一下属性的类型:基本属性类型、对象类型
            Class type = field.getType();//获取当前属性字段field的类型
            //此处我只写了几个基本数据类型,其他类型可自行添加......
            if (key.equals(field.getName())){
                if (type == int.class){
                    field.set(obj,Integer.parseInt(value));
                }else if (type == double.class){
                    field.set(obj,Double.parseDouble(value));
                }else if (type == String.class){
                    field.set(obj,value);
                }else {
                    //若是对象类型
                    field.set(obj,value);
                }
            }
        }
    }
}

模拟"Spring容器"构建实例

//1、构建 "Spring容器" 最先实例化可通过on-load-startup属性,后期交给tomcat容器
ApplicationContextUtils application = new ApplicationContextUtils();
//2、通过"Spring容器"结合配置文件的Id属性值获取Book对象
Book book = application.getBean("book",Book.class);
//3、格式化输出对象,进行内容展示
System.out.println(book.toString());

模拟"Spring容器"构建实例步骤总结

  • 构建纯净类,也就是JavaBean类
  • 构建工厂类,便于JavaBean通过工厂创建实例
  • 实例化工厂,调用工厂类的方法获取实例对象
  • Spring IOC获取实例步骤总结
  • 构建纯净类
  • 构建Spring框架的配置文件
  • 将纯净类注入到配置文件中
  • 实例化Spring容器,通过getBean()方法根据Id属性值来获取对应的实例

最后

欢迎关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结! 这些资料的内容都是面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。

阅读 81

810 声望
113 粉丝
0 条评论
810 声望
113 粉丝
宣传栏