美文网首页
Spring特性之IoC容器

Spring特性之IoC容器

作者: 一只在时光里流浪的大懒猫 | 来源:发表于2019-03-19 16:09 被阅读0次

    Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。这里,我们先探索一下IoC,下一节探索AOP。

    一、IoC容器概念简介

    几个名词概念:

    • IoC(Inversion Of Control,控制反转)
      在传统的开发中,当我们遇到需要使用一个类时,往往使用 new 关键字来产生一个对象。而“控制反转”的意思是,我们把对象的创建,即new这一步,交给第三方,需要用到时,通过第三方来调取。
    • DI(Dependency Injection,依赖注入)
      IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
      我们以较为常见的service中使用dao为场景来尝试说明:
      依赖注入的几种实现方式:
      1.构造器注入
    /**
     * 模拟数据库保存用户
     */
    public interface UserDao {
        void save();
    }
    
    public class UserDaoImpl implements UserDao{
    
        @Override
        public void save(){
            System.out.println("保存用户数据!");
        }
    
    }
    
    public class UserService {
    
        public UserDao userDao;
    
        /**
         * 通过构造函数,将所要用到的对象注入进来
         * @param userDao
         */
        public UserService(UserDao userDao){
            this.userDao = userDao;
        }
    
        public void addUser(){
            userDao.save();
        }
    
    }
    

    使用

    public class Main {
        // 模拟保存用户场景
        public static void main(String[] args){
            UserDao userDao = new UserDaoImpl();
            // 这里,在service中使用到的userDao,是从构造器传入的
            UserService service = new UserService(userDao);
            service.addUser();
        }
    }
    

    2.属性注入
    userDao不变,将UserService 稍作改变

    public class UserService {
    
        public UserDao userDao;
    
        /**
         * 这里使用set方法,将所用对象传入
         * @param userDao
         */
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void addUser(){
            userDao.save();
        }
    
    
    }
    

    使用

    public class Main {
        public static void main(String[] args){
            // 模拟保存用户场景
            UserDao userDao = new UserDaoImpl();
            UserService service = new UserService();
            // 这里,在service中使用到的userDao,是从构造器传入的
            service.setUserDao(userDao);
            service.addUser();
        }
    }
    
    • IoC容器
      依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。

    二、spring中的实现

    spring实现DI的两种方式:

    • 注解
      分二步走:
      1.xml开启注解
      spring-annotation.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 开启注解 -->
        <context:component-scan base-package="cn.nep"/>
    
    
    </beans>
    

    2.类上加注解
    UserDao.java

    @Component("userDao")
    public class UserDao {
        public void add(){
            System.out.println("UserDao add ... ");
        }
    }
    

    @Component spring注解,标注该类为spring组件,其动态创建、依赖注入、生命周期等交由spring管理。
    ("userDao"),标注该对象在容器中的id,即在容器中的唯一标识。

    UserService.java

    @Component
    public class UserService {
        /**
         * 通过注解注入,不需要set方法
         */
        @Autowired
        private UserDao userDao;
    
        public void add(){
            userDao.add();;
            System.out.println("UserService add...");
        }
    }
    

    @Autowired spring注解,对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过使用来消除 set ,get方法。

    使用:

        public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext("cn/nep/di/annotation/spring-annotation.xml");
            UserService userService = (UserService)context.getBean("userService");
            userService.add();
        }
    
    • xml配置
      分二步走:
      1.xml配置
      首先,我们再xml中配置好所要的bean
      spring-xml.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="userDao" class="cn.nep.di.xml.UserDao">
        </bean>
    
        <!-- 属性注入,pojo中必须要有set方法.且pojo中必须要有无参构造函数 -->
        <bean id="userService"
              class="cn.nep.di.xml.UserService">
            <property name="userDao" ref="userDao"></property>
        </bean>
    
        <!-- 构造器注入,pojo中必须有 对应的构造函数 -->
        <bean id="userService2"
              class="cn.nep.di.xml.UserService2">
            <constructor-arg ref="userDao"/>
        </bean>
    
    </beans>
    

    这里用到的几个类
    UserDao.java

    public class UserDao {
        public void add(){
            System.out.println("UserDao add ... ");
        }
    }
    

    UserService.java

    public class UserService {
    
        private UserDao userDao;
    
        /**
         * 通过xml配置,属性注入,必须要有set方法
         * @param userDao
         */
        public void setUserDao(UserDao userDao){
            this.userDao = userDao;
        }
    
    
        public void add(){
            userDao.add();;
            System.out.println("UserService add...");
        }
    }
    

    UserService2.java

    public class UserService2 {
    
        private UserDao userDao;
    
        /**
         * 通过xml配置,构造器注入
         * @param userDao
         */
        public UserService2(UserDao userDao){
            this.userDao = userDao;
        }
    
        public void add(){
            userDao.add();;
            System.out.println("UserService2 add...");
        }
    }
    

    2.从容器中获取

    public class Main {
    
        public static void main(String[] args){
    
            // 容器解析xml,创建并管理bean
            ApplicationContext context = new ClassPathXmlApplicationContext("cn/nep/di/xml/spring-xml.xml");
    
            // 从容器中获取bean
            UserService u1 = (UserService)context.getBean("userService");
            u1.add();
    
            System.out.println("=========================");
    
            UserService2 u2 = (UserService2)context.getBean("userService2");
            u2.add();
    
        }
    }
    

    三、自定义实现IoC容器

    知道了IoC、DI的概念,我们可以动手自己撸一个简单版的IoC容器。思路:1.定义xml,2.解析xml,反射生成类,3将类放入一个存储介质中,如map,4.从自定义的容器中获取

    下面依次实现:
    1.定义xml
    在xml文件中,为了方便取用,我们一般会设置一个唯一标识id;我们还要设置其属性值,用以注入对象;当然,类的全路径也是不可少的,反射时用以确定哪个类。

    beans.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
        <bean id="nep" class="cn.nep.beans.User">
            <property name="id" value="01"></property>
            <property name="name" value="nep"></property>
        </bean>
    
        <bean id="jj" class="cn.nep.beans.User">
            <property name="id" value="9527"></property>
            <property name="name" value="njj"></property>
        </bean>
    
        <bean id="book" class="cn.nep.beans.Book">
            <property name="id" value="XN002"></property>
            <property name="name" value="删库跑路"></property>
        </bean>
    </beans>
    

    2.解析xml,反射生成类
    当我们有了bean属性的xml文件,接下来就是对文件的解析了。解析我们这里用到dom4j。

       /**
         * dom4j解析
         */
        public static Map<String,Object> createByDom4j(String path) throws Exception{
    
            Map<String,Object> beans = new HashMap<>();
            File file = new File(path);
            if(!file.exists()){
                System.out.println("系统找不到文件!==="+path);
                return null;
            }
    
            SAXReader reader = new SAXReader();
            Document document = reader.read(file);
            Element root = document.getRootElement();
            List<Element> childElements = root.elements();
            for (Element child : childElements) {
                List<Attribute> attributeList = child.attributes();
    //            for (Attribute attr : attributeList) {
    //                System.out.println(attr.getName() + ": " + attr.getValue());
    //            }
                // 利用反射,生成对象
                String cls = child.attributeValue("class");
                // 根据类的全路径 获取class
                Class clz = Class.forName(cls);
                // 生成对象
                Object obj = clz.newInstance();// 需要用到无参构造函数
    
                // 循环获取属性值,并设置
                List<Element> proElements = child.elements();
                for (Element proEl : proElements){
                    Field f = clz.getDeclaredField(proEl.attributeValue("name"));
                    f.setAccessible(true);
                    f.set(obj,proEl.attributeValue("value"));
                }
                beans.put(child.attributeValue("id"),obj);
            }
    
            return beans;
        }
    

    3.将类放入一个存储介质中,如map
    我们设计一个类作为容器。由于map是以键值对形式存储变量的,而变量的类型为Object,这样很符合我们存取对象的需求,所以,在类中我们用到map来存储对象。

        private Map<String,Object> beans;
    

    xml的解析,反射生成对象,存入map,这些步骤都是在容器类内完成的。下面是容器类。

    IContext.java

    public class IContext {
    
        // 用map存储对象
        private Map<String,Object> beans;
    
        /**
         * 根据id获取对象
         * @param id
         * @return
         */
        public Object getBean(String id){
            return beans.get(id);
        }
    
        public IContext(String path){
            path = IContext.class.getResource("/").getPath()+path;
            try {
                // dom4j 解析xml,反射生成对象
                Map<String,Object> beans = createByDom4j(path);
                System.out.println("dom4j 解析xml:");
                if (beans != null&& beans.size()>0){
                    this.beans = beans;
                }
            }catch (Exception e){
                e.printStackTrace();
            }
    
        }
    
        /**
         * dom4j解析
         */
        public static Map<String,Object> createByDom4j(String path) throws Exception{
    
            Map<String,Object> beans = new HashMap<>();
            File file = new File(path);
            if(!file.exists()){
                System.out.println("系统找不到文件!==="+path);
                return null;
            }
    
            SAXReader reader = new SAXReader();
            Document document = reader.read(file);
            Element root = document.getRootElement();
            List<Element> childElements = root.elements();
            for (Element child : childElements) {
                List<Attribute> attributeList = child.attributes();
    //            for (Attribute attr : attributeList) {
    //                System.out.println(attr.getName() + ": " + attr.getValue());
    //            }
                // 利用反射,生成对象
                String cls = child.attributeValue("class");
                // 根据类的全路径 获取class
                Class clz = Class.forName(cls);
                // 生成对象
                Object obj = clz.newInstance();// 需要用到无参构造函数
    
                // 循环获取属性值,并设置
                List<Element> proElements = child.elements();
                for (Element proEl : proElements){
                    Field f = clz.getDeclaredField(proEl.attributeValue("name"));
                    f.setAccessible(true);
                    f.set(obj,proEl.attributeValue("value"));
                }
                beans.put(child.attributeValue("id"),obj);
            }
    
            return beans;
        }
    }
    

    用到的实体类:
    Book.java

    public class Book {
        private String id;
        private String name;
    
        public String getId() {
            return id;
        }
    
        public void setId(String 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 + '\'' +
                    '}';
        }
    }
    

    User.java

    public class User {
    
        private String id;
        private String name;
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    

    测试:

        public static void main(String[] args){
            try {
    
                IContext iContext = new IContext("beans.xml");
    
                User user = (User)iContext.getBean("nep");
                System.out.println(user.toString());
    
                user = (User)iContext.getBean("jj");
                System.out.println(user.toString());
    
                Book book = (Book)iContext.getBean("book");
                System.out.println(book.toString());
    
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    

    至此,一个简单版的IoC容器就完成了。

    相关文章

      网友评论

          本文标题:Spring特性之IoC容器

          本文链接:https://www.haomeiwen.com/subject/zilimqtx.html