Spring特性之IoC容器

2019-03-19  本文已影响0人  一只在时光里流浪的大懒猫

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

一、IoC容器概念简介

几个名词概念:

/**
 * 模拟数据库保存用户
 */
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();
    }
}

二、spring中的实现

spring实现DI的两种方式:

<?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 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容器就完成了。

上一篇下一篇

猜你喜欢

热点阅读