Spring概述和IoC配置

spring概述

Spring 是轻量级的 JavaEE 的开源框架,具有 IOC/DI,AOP,MVC,TX,ORM 等功能。
优点是:
+ DI (依赖注入)有效的降低了耦合度。
+ AOP(面向切面)提供了通用的任务的集中管理。
+ ORM(对象关系映射)简化了对数据库的访问。
+ 低侵入式设计,代码污染极低。

spring的组成

spring 4.2.x

IOC的概念和作用

什么是程序的耦合

耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。

在软件工程中,耦合指的就是就是对象之间的依赖性。 对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。

耦合的分类

  1. 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
  2. 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
  3. 外部耦合。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
  4. 控制耦合。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
  5. 标记耦合。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
  6. 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
  7. 非直接耦合。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

内聚与耦合

内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。

内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。

IOC

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

反射工厂

这是一个简单了解IoC思想的小案例

public class BeanFactory {
    private static Properties props;

    //使用静态代码块为Properties赋值
    static{
        try {
            //实例化对象
            props = new Properties();
            //获取properties的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 根据bean的名称获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        Object bean = null;
        try {
            String beanPath = props.getProperty(beanName);
            bean = Class.forName(beanPath).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }
}

问题:每次都会创建新对象,是多例的

单例反射工厂:

public class BeanFactory {
    private static Properties props;

    private static Map<String,Object> beans;

    //使用静态代码块为Properties赋值
    static{
        try {
            //实例化对象
            props = new Properties();
            //获取properties的流对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            beans = new HashMap<String, Object>();
            //取出配置文件中所有的key
            Enumeration keys = props.keys();
            //遍历枚举
            while(keys.hasMoreElements()){
                //取出每个key
                String key = keys.nextElement().toString();
                String beanPath = props.getProperty(key);
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器中
                beans.put(key,value);
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 根据bean的名称获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}

而这种思想就是IOC的思想。

Spring中的IOC

spring的maven配置

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>

spring-container 核心容器

spring的总配置

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 把对象的创建交给spring来管理 -->
    <bean id="accountService" class="com.rhett.service.impl.AccountServiceImpl"/>
    <bean id="accountDao" class="com.rhett.dao.impl.AccountDaoImpl"/>
</beans>

程序中获取bean

//1. 获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2. 根据id获取bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);

ApplicationContext的三个常用实现类:
+ ClassPathXmlApplicationContext:它可以加载类路径下的配置文件
+ FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
+ AnnotationConfigApplicationContext:它是用于读取注解创建容器的

Spring概述和IoC配置

核心容器的两个接口BeanFactoryApplicationContext的区别:

  1. BeanFactory 是不支持国际化功能的,因为 BeanFactory 没有扩展 Spring 中 MessageResource 接口。相反,由于 ApplicationContext 扩展了 MessageResource 接口,因而具有消息处理的能力。
  2. ApplicationContext 的事件机制主要通过 ApplicationEvent 和 ApplicationListener 这两个接口来提供的,和 java swing 中的事件机制一样。即当 ApplicationContext 中发布一个事件的时,所有扩展了 ApplicationListener 的 Bean 都将会接受到这个事件,并进行相应的处理。
  3. ApplicationContext 扩展了 ResourceLoader(资源加载器)接口,从而可以用来加载多个 Resource,而 BeanFactory 是没有扩展 ResourceLoader
  4. BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个Bean 时,才对该 Bean 进行加载实例化,这样,我们就不能发现一些存在的 Spring 的配置问题。而 ApplicationContext 则相反,它是在容器启动
    时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring中存在的配置错误。

创建bean的三种方式

第一种方式:

使用默认构造函数创建。
在spring的配置文件中使用bean标签,如果此类中没有默认构造函数,则对象无法创建

<bean id="accountService" class="com.rhett.service.impl.AccountServiceImpl"/>

第二种方式:

使用某个类中的方法创建对象,并存入spring容器

public class InstanceFactory {
    public IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
<bean id="instanceFactory" class="com.rhett.factory.InstanceFactory"/>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>

第三种方式:

使用某个类中的静态方法创建对象,并存入spring容器

public class StaticFactory {
    public static IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
<bean id="instanceFactory" class="com.rhett.factory.StaticFactory" 
    factory-method="getAccountService"/>

bean的作用范围

bean标签的scope属性用于指定bean的作用范围,取值:
+ singleton:单例的(默认)
+ prototype:多例的
+ request:作用于web应用的请求范围
+ session:作用于web应用的会话范围
+ global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session

bean对象的生命周期

单例对象:
+ 当容器创建时对象出生
+ 只要容器还在,对象一直活着
+ 容器销毁,对象消亡

单例对象的生命周期和容器相同

多例对象:
+ 当我们使用对象时创建
+ 对象只要在使用过程中就一直活着
+ 当对象长时间不用,且没有别的对象引用时,被垃圾回收

有三种方式在 Bean 初始化后和销毁前添加一些操作。
+ Bean 的方法加上@PostConstruct@PreDestroy 注解(必须有 component-scan 才有效)
+ 在Spring配置中定义 init-methoddestory-method 方法
+ Bean 实现 InitializingBeanDisposableBean 接口

除此之外还有

  • BeanNameAware接口设置Bean名称
  • BeanFactoryAware/ApplicationContextAware接口设置IoC容器
  • BeanPostProcessor为所有的Bean添加预初始化方法和后初始化方法

Spring概述和IoC配置

最全概括:

  1. 实例化一个 Bean——也就是我们常说的 new;
  2. 按照 Spring 上下文对实例化的 Bean 进行配置——也就是 IOC 注入;
  3. 如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
  4. 如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的setBeanFactory,传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean,只需在 Spring 配置文件中配置一个普通的 Bean 就可以);
  5. 如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用 setApplicationContext方法,传入 Spring 上下文(同样这个方式也可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更多的实现方法);
  6. 如果Bean中有 @PostConstruct 标注的方法,会调用该方法。
  7. 如果Bean实现了InitializingBean接口,会调用AfterPropertiesSet方法
  8. 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常用作是 Bean内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
  9. 如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初
    始化方法。
  10. 如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)方法
  11. 如果Bean中有 @PreDestroy 标注的方法,会调用该方法。
  12. 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用那个其实现的 destroy方法;
  13. 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。
  14. 如果还要补充的话,容器关闭之后会进行bean的垃圾回收,如果重写了finalize方法也会调用。

spring中的依赖注入

依赖注入:Dependency injection,DI

依赖关系的管理:交给spring来维护

在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明。依赖关系的维护就称之为依赖注入。

能注入的数据有三类:
+ 基本类型和String
+ 其他bean类型(在配置文件中或者注解配置过的bean)
+ 复杂类型/集合类型

注入的方式,有三种:

第一种:使用构造函数提供

需要注入依赖的实体类:

public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了"+name+","+age+","+birthday);
    }
}

spring配置:

<bean id="accountService" class="com.rhett.service.impl.AccountServiceImpl">
    <constructor-arg name="name" value="test"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>

使用标签:\

标签中的属性:
+ type:用于指定要注入的数据类型
+ index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0开始
+ name:用于指定给构造函数中指定名称的参数赋值(通常通过这个就能找到对应的参数)
+ value:用于提供基本类型和String类型的数据
+ ref:用于指定其他的bean类型数据,它指的就是在spring的Ioc容器中出现过的bean对象。

第二种:使用set方法提供

需要注入依赖的实体类:

public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了"+name+","+age+","+birthday);
    }
}

spring配置:

<bean id="accountService" class="com.rhett.service.impl.AccountServiceImpl">
    <property name="name" value="TEST"></property>
    <property name="age" value="21"></property>
    <property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>

使用标签:\

标签中的属性:
+ name:用于指定注入时所调用的set方法名称
+ value:用于提供基本类型和String类型的数据
+ ref:用于指定其他的bean类型数据,它指的就是在spring的Ioc容器中出现过的bean对象。

复杂类型的注入:

需要注入依赖的实体类:

public class AccountServiceImpl implements IAccountService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }
}

spring配置:

<bean id="accountService" class="com.rhett.service.impl.AccountServiceImpl">
    <property name="myStrs">
        <array>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </array>
    </property>
    <property name="myList">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </property>
    <property name="mySet">
        <set>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </set>
    </property>
    <property name="myMap">
        <map>
            <entry key="testA" value="aaa"></entry>
            <entry key="testB">
                <value>bbb</value>
            </entry>
        </map>
    </property>
    <property name="myProps">
        <props>
            <prop key="testC">ccc</prop>
            <prop key="testD">ddd</prop>
        </props>
    </property>
</bean>

property标签中需要搭配array、list、set、map、props使用,其中array、list、set可以互换通用,map、props可以互换通用。

第三种:使用注解提供

Spring之注解配置IoC

(这篇博客的长度已经超出我预期了,一篇博客写那么多太不划算了?)

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/spring%e6%a6%82%e8%bf%b0%e5%92%8cioc%e9%85%8d%e7%bd%ae/

发表评论

电子邮件地址不会被公开。