spring基础(二)---IoC简介

是什么

IOC全称: Inverse of Control, 控制反转,所以重点就是,什么是控制,怎样反转
* 谁控制谁,控制了什么
举个例子,我们先来看传统的编程
这里写图片描述
client类中代码:

public static void main(String[] args) {
        UserManager userManager=new UserManagerImpl();//依赖写死在这里
        userManager.addUser("张飒","123");
    }

UserManager

public interface UserManager {
    public void addUser(String username,String password);
}

UserManagerImpl

public class UserManagerImpl implements UserManager{
    @Override
    public void addUser(String username,String password){
        //由我们的应用程序负责服务定位
        UserDao userDao=new UserDaoMySqlImpl();
        userDao.addUser(username,password);
    }
}

很明显,UserDao接口有很多不同的实现,有mysql的,也有oracle的.如果我们想读取不同数据源的数据,那么就得有不同的UserManagerImpl类,而UserManagerImpl,即依赖于接口UserDao,有同时依赖实现,所以它需要在编译阶段,就确定使用哪种方式,这样就缺少灵活性了.而且代码量也增多.如果把现在调用哪种类型的userDao,交给client决定呢?
上面代码也写了,是通过应用程序直接负责服务的定位的,就是去找我想要的对象.这个是正转, 反转是什么, 是容器来帮忙创建及注入依赖对象.容器找到之后,就把你要的对象,给你,所以对象就事被动的接收依赖对象,就是所谓的反转了.
上面是不是有点像: 是自己直接找房子, 还是通过中介找房子?

现在我们把上面的代码改造,改造成spring管理的项目
这里写图片描述
这段代码很熟悉吧.

public static void main(String[] args) {
        /*IOC容器中拿接口*/
        BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserManager userManager=(UserManager)factory.getBean("userManager");
        userManager.addUser("张飒","123");
    }

来分析一下它.首先是applicationContext.xml

java自带了多种xml命名空间,通过这些命名空间可以配置Spring容器
beans: 支持声明Bean和装配Bean,是spring最核心也是最原始的命名空间
元素是spring最基本的配置单元,通过该元素spring将创建一个对象,这里创建了一个由spring容器管理的名字叫userDaoMySql的bean.id属性定义了Bean的名字,也作为该Bean在Spring容器中的引用.
当Spring容器加载该Bean时,Spring将使用默认的构造器来实例化userDaoMySql Bean;

<bean id="userDaoMySql" class="com.tgb.kwy.dao.UserDaoMySqlImpl"/>
    <bean id="userDaoOracle" class="com.tgb.kwy.dao.UserDaoOracleImpl"/>

    <bean id="userManager" class="com.tgb.kwy.manager.UserManagerImpl">
        <!--<constructor-arg ref="userDaoMySql"/>&lt;!&ndash;构造函数方式&ndash;&gt;-->
        <!--通常javaBean是私有的,同时拥有一组存取器方法,以setXXX()和getXXX()形式存在.-->
        <property name="userDao" ref="userDaoMySql"></property>
    </bean>

上面看到了一个注释, Spring中,我们可以使用元素配置bean的属性,在许多方面都与类似,只不过一个是通过构造器参数来注入值,另一个是通过调用属性的setter方法来注入值
一旦UserManagerImpl被实例化,Spring就会调用元素所指定属性的setter方法为该属性注入值.

UserManagerImpl

public class UserManagerImpl implements UserManager {
    private UserDao userDao;

    /*构造函数方式*/
    /*public UserManagerImpl(UserDao userDao) {
        this.userDao = userDao;
    }
*/

    /*set方式*/
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void addUser(String username,String password){
        //由我们的应用程序负责服务定位
        // UserDao userDao=new UserDaoMySqlImpl();
        userDao.addUser(username,password);
    }
}

在最上面的配置文件applicationContext.xml中,我们可以看到,如果想变换userDao的实现类,那么就我就改userManager中的ref=”userDaoOracle”即可.

所以控制反转: 对象的创建权,销毁都交给Spring来完成.
依赖注入:Spring主动创建被调用类的对象,然后把这个对象注入到我们自己的类中,使我们能使用它.依赖注入是通过反射实现注入的

附:上面的例子,是把配置全写在xml里面了,但是在实际项目中,更多的是扫描包,用注解的方式注入bean的实现.当需要修改的时候, 就把bean实现路径改了就成.
关于IOC是什么,我觉得这篇博客写的很好:
http://jinnianshilongnian.iteye.com/blog/1413846?page=2

源码分析

下面来了解一下Spring在这个过程中,是怎么做的?(进入源码阅读模式)

SpringIoC两个主要的容器系列,一个是BeanFactory接口的简单容器,实现了基本的功能.另一个就是ApplicationContext应用上下文,是容器的高级形态存在

BeanFactory是一个接口类,
DefaultListTableBeanFactory.XmlBeanFactory.ApplicationContext等都是具体的实现

public interface BeanFactory {  
 //工厂Bean的转义定义,因为如果使用bean的名字检索IOC容器得到的对象是工厂Bean生成的对象,  
 //如果需要得到工厂Bean本身,需要使用转义的名字来向IOC容器检索  
 String FACTORY_BEAN_PREFIX = "&";  
 //根据bean的名字,在IOC容器中得到bean实例,这个IOC容器就象一个大的抽象工厂,用户可以根据名字得到需要的bean  
 //在Spring中,Bean和普通的JAVA对象不同在于:  
 //Bean已经包含了我们在Bean定义信息中的依赖关系的处理,同时Bean是已经被放到IOC容器中进行管理了,有它自己的生命周期  
 Object getBean(String name) throws BeansException;  
 //根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制  
 Object getBean(String name, Class requiredType) throws BeansException;  
 //提供对bean的检索,看看是否在IOC容器有这个名字的bean  
 boolean containsBean(String name);  
 //根据bean名字得到bean实例,并同时判断这个bean是不是单例,在配置的时候,默认的Bean被配置成单例形式,如果不需要单例形式,需要用户在Bean定义信息中标注出来,这样IOC容器在每次接受到用户的getBean要求的时候,会生成一个新的Bean返回给客户使用 - 这就是Prototype形式  
 boolean isSingleton(String name) throws NoSuchBeanDefinitionException;  
 //得到bean实例的Class类型  
 Class getType(String name) throws NoSuchBeanDefinitionException;  
 //得到bean的别名,如果根据别名检索,那么其原名也会被检索出来  
 String[] getAliases(String name);  
}  

我们用的最多的就是getBean方法了. 在BeanFactory中,只对IOC容器的基本行为做了定义,具体的实现,工厂怎么生产对象的,我们得看具体的实现类.如果读取xml,可以使用XmlBeanFactory.使用方式:

ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource res = resolver.getResource("classpath:applicationContext.xml"); 
        BeanFactory bf = new XmlBeanFactory(res);

xmlBeanFactory先通过Resource装载了Spring配置信息,然后启动IOC容器,就能通过getBean方法从IOC容器获取Bean了.但是通过BeanFactory启动IOC容器时,不会初始化配置文件中定义的Bean,而是在首次调用时

不过上面我们也提到了ApplicationContext.我们在上面的例子可以看到. 读取xml的时候, 采用的ClassPathXmlApplicationContext,而这个类就是继承了ApplicationContext
ClassPathXmlApplicationContext:默认从类路径加载配置文件
FileSystemXmlApplicationContext:默认从文件系统中装载配置文件

如果配置文件放置在类路径下,用户可以优先使用 ClassPathXmlApplicationContext 实现类:就像我们上面的例子:

BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");

如果配置文件放置在文件系统的路径下,则可以优先考虑使用 FileSystemXmlApplicationContext 实现类;

小结

  • BeanFactory是Spring框架的基础设施,面向Spring本身
  • ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合我们都直接使用ApplicationContext,而非底层的BeanFatory
  • BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean
  • 而ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean。

IoC初始化过程

Resource定位

  1. 在看源码的时候,我们可以看到ClassPathXmlApplicationContext通过继承AbstractApplicationContext,具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext继承了DefaultResourceLoader;
  2. resourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件的资源.
  3. 常见的resource资源类型如下:
    FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
      ClassPathResourcee:以类路径的方式访问资源,效果类似于this.getClass().getResource(“/”).getPath();
      ServletContextResource:web应用根目录的方式访问资源,效果类似于request.getServletContext().getRealPath(“”);
      UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
      ByteArrayResource: 访问字节数组资源的实现类。

定位好的资源载入BeanDefinition

  1. IOC容器对于bean的管理和依赖注入功能实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的.
  2. BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析这个配置文件,配置文件中每一个就被解析成一个BeanDefinition对象(至于怎么解析的, 我感觉自己理解的不太好, 就不写了)
  3. 在这个时刻,依赖注入还没发生呢

将BeanDefinition注册到容器

  1. 上面提到了,获取所有的BeanDefinition, 那么这些BeanDefinition就存在BeanDefinitionRegistry中, 它通过hashMap来保存所有的BeanDefinition.key就是bean的名字.所以在配置文件中bean id 是不允许重复的.
  2. 完成了注册,就完成IOC容器的初始化过程了.这些BeanDefinition就能被容器使用了.容器作用就是对这些信息进行处理和维护.

依赖注入

上面我们有提到初始化的时候, 依赖注入还没发生. 真正发生的时刻,是用户第一次向IOC容器要Bean时. 这个是默认的配置,但是如果你在配置文件中通过lazy-init属性,让容器完成对Bean的实例化,就是相当于在初始化过程中完成的了.

  1. 依赖注入的入口,就是getBean()方法, 调用者通过getBean(beanName)向容器请求一个Bean时,容器将根据配置情况调用InstantiationStrategy进行bean实例化,使用BeanWrapper完成bean属性设置工作
  2. 简单理解:通过反射,实例化一个类时,通过反射调用set方法把hashmap中类属性注入到类中

IoC装配bean

这里写图片描述
* 关于bean作用域,这里多说一下: spring关于单例,仅限于Spring上下文的范围内,不像真正的单例, 在每个类加载器中保证只有一个实例. Spring的单例Bean只能保证在每个应用上下文中只有一个Bean的实例.还是可以通过传统的方式实例化同一个Bean. 甚至可以定义几个声明来实例化同一个bean
* 关于bean装配, spring容器默认是禁用注解装配的.所以在使用基于注解的自动装配前,我们需要在spring配置中启用它.最简单的启用方式: 这个元素告诉Spring我们打算使用基于注解的自动装配, 但是这种方式,还是需要我们使用元素显示定义Bean. 另一种更常用的是. 这个除了完成一样的工作.还运行spring自动检测Bean和定义Bean. 不再使用元素,Spring应用中的大多数Bean都能够实现定义和装配
* 怎么知道哪些类需要注册为Spring Bean?
* 就是上图中组件注解的几种方式了.Spring扫描到标注了注解的包时,会自动注册为SpringBean

bean生命周期

  • Spring对Bean进行实例化
  • Spring将值和Bean的引用注入进Bean对应的属性中
  • 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给set-BeanName()接口方法
  • 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()接口方法,将BeanFactory容器实例传入
  • 如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()接口方法,将应用上下文的引用传入
  • 如果Bean实现了BeanPostProcessor接口,Spring将调用他们的post-ProcessBeforeInitialization()接口方法
  • 如果Bean实现了InitaialzingBean接口,Spring将调用它们的after-PropertiesSet()接口方法.类似地,如果Bean使用intitle-method声明了初始化方法,该方法也会被调用
  • 如果Bean实现了BeanPostProcessor接口,Spring将调用他们的post-ProcessAfterInitialization()方法
  • 此时此刻,bean已经准备就绪,可以被应用程序使用了.它们将一直驻留在应用上下文中,直到该应用上下文被销毁
  • 如果Bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法,同样,如果Bean使用destroy-method声明了销毁方法,该接口也会被调用.

参考: 众多博客,spring技术内幕书

猜你喜欢

转载自blog.csdn.net/kwy15732621629/article/details/80607233