Spring-framework复习笔记一(IOC,bean以及DI)

1.耦合概念以及解耦

耦合: 简单来说就是指程序间的依赖关系。一般包括:类之间的关系,方法间的关系。
解耦: 目的是为了降低程序间的依赖关系。实际开发中应该做到:编译时不依赖,运行时才依赖。

1.1.两个例子:JDBC连接以及持久层业务层调用关系

创建一个demo类,在不导入mysql-connector-java.jar的情况下,执行程序。
(在导入jar包后,都可正常运行)
在这里插入图片描述
尝试通过Class.forName(“驱动类全类名”)(反射机制)获取驱动类:此时即使驱动类不存在,也不会在编译的时候报错
在这里插入图片描述
持久层和业务层的调用关系:此时若不存在持久层类,将无法通过编译,这就是编译期依赖
在这里插入图片描述

1.2.解耦的思路:通过工厂模式解耦(手动实现)

用工厂模式,通过反射机制,创建对象
第一步: 创建一个配置文件来配置我们的service和dao。内容为:唯一标识 = 全限定类名(key - value)
第二步: 通过读取配置文件中配置的内容,通过反射创建对象。

配置文件:
在这里插入图片描述
手写的BeanFactory工厂类(实现单例模式):
PS:单例模式与多例模式:
单例模式: 多次创建(new)同一对象时,为相同的对象,拥有相同的地址。
多例模式: 多次创建(new)同一对象时,为不同的对象,拥有的地址不同。
本例实现单例模式的思路: 在类中新建一个Map用于存储<String,Object>,在第一次运行的时候,将配置文件中的多个对象一次性创建并且加到Map中,之后在调用getBean方法的时候通过传入的类名从Map中取出一开始就创建好的对象即可。

public class BeanFactory {
    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
    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();
                //根据key获取value
                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的名称获取对象
    * 且从Map中获取对象,实现了单例模式
    * */
    public static Object getBean(String beanName) {
        return beans.get(beanName);//通过类名找到对应的对象地址
    }
}

业务层使用getBean()方法从工厂中获取对应类名的对象。
在这里插入图片描述工厂模式优点:
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
3、屏蔽产品的具体实现,调用者只关心产品的接口。
工厂模式缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

2.使用SpringIOC解决耦合(将对象的创建交给spring)

使用spring的方法获取类,不再需要自己写工厂。

2.1.基本实现

1.创建一个maven工程。pom.xml导入spring相关jar包
在这里插入图片描述
2.创建xml配置文件
导入spring约束,配置唯一标识id与相应类的对应关系。使得可以根据id获得对应的对象。
在这里插入图片描述
3.创建测试类:从上到下代表了持久层,业务层,表现层的调用关系。
包和类的结构如下:
在这里插入图片描述
持久层:

/**
 * 账户的持久层实现类
 **/
public class AccountDaoImpl implements IAccountDao {
    public void saveAccount() {
        System.out.println("保存了账户");
    }
}
/**
 * 持久层接口
 **/
public interface IAccountDao {
    /*
    * 模拟保存账户
    * */
    void saveAccount();
}

业务层:

/**
 * 业务层实现类
 **/
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = new AccountDaoImpl();
    public void saveAccount() {
        accountDao.saveAccount();
    }
}
/**
 * 业务层的接口
 **/
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

表现层:

/**
 * 表现层调用业务层,业务层调用持久层
 * 模拟一个表现层,用于调用业务层
 **/
public class Client {
    /**
     * 获取spring的Ioc核心容器,并根据id获取对象
     * **/
    public static void main(String[] args) {
        //1,获取核心容器对象。通过容器创建对象.通过核心容器的getBean()方法获取具体对象.
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");//根据唯一标识获得对象
        System.out.println(as);//尝试输出对象,默认情况为单例模式
    }
}

2.1.1.ApplicationContext的三个实现类

ClassPathXmlApplicationContext: 它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了。
FileSystemXmlApplicationContest: 它可以加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContest: 读取注解创建容器。

2.1.2.核心容器BeanFactory和ApplicationContext有什么区别

ApplicationContext: 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建文件中配置的对象。(单例对象适用)

BeanFactory: 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。(多例对象适用)

PS:没有特殊要求的情况下,应该使用ApplicationContext完成。因为BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近现在开发的功能。

3.XML文件的配置

使用配置文件实现IOC,要将托管给spring的类写进bean.xml配置文件中。

3.1.bean标签

作用: 配置托管给spring的对象,默认情况下调用类的无参构造函数,若果没有无参构造函数则不能创建成功。
属性:
1.id: 指定对象在容器中的标识,将其作为参数传入getBean()方法可以获取获取对应对象.
2.class: 指定类的全类名,默认情况下调用无参构造函数。
3.scope: 指定对象的作用范围,可选值如下:
3.1.singleton:单例模式。(默认)
3.2.prototype: 多例模式。
3.3.request: 将对象存入到web项目的request域中。
3.4.session: 将对象存入到web项目的session域中。
3.5.global session: 作用于集群环境的会话范围(全局会话范围),当不是集群环境时,他就是session
4.init-method: 指定类中的初始化方法名称,在对象创建成功之后执行
5.destroy-method: 指定类中销毁方法名称,对prototype多例对象没有作用,因为多利对象的销毁时机不受容器控制。

3.2.bean的作用范围和生命周期

1.单例对象: scope="singleton"
作用范围: 每个应用只有一个该对象的实例,它的作用范围就是整个应用。
生命周期: 单例对象的创建与销毁 和 容器的创建与销毁时机一致。
-------------出生:当容器创建时,对象出生。
-------------活着:只要容器存在,对象一直活着。
-------------死亡:容器销毁,对象消亡
2.多例对象: scope="prototype"
作用范围: 每次访问对象时,都会重新创建对象实例。
生命周期: 多例对象的创建与销毁时机不受容器控制。
-------------出生:当我们使用对象时,spring框架为我们创建。
-------------活着:对象只要是在使用过程中就一直活着。
-------------死亡:当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收。

3.3.实例化bean的三种方式

1.构造器实例化: 用默认构造函数创建。
在spring的配合文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数的bean对象,此时如果类没有默认构造函数,则对象无法创建。(类没有被重载的情况)

 <bean id="accountService" class="service.impl.AccountServiceImpl"></bean>//xml文件 
 				//id为标识符,class为要通过getBean()创建的类

2.静态工厂实例化: 使用静态工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)(静态方法可以直接用类获取。)

//			标识符				指定类									直接指定类中的静态方法
<bean id="accountService" class="factory.StaticFactory" factory-method="getAccountService"></bean>
						

PS:类的构造函数也是静态方法,因此默认无参构造函数也可以看作一种静态工厂方法
3.实例工厂实例化: 使用普通工厂中的方法创建对象(使用某个类中的非静态方法创建对象,并存入spring容器)。

/**
 * 模拟一个实例工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
 **/
public class InstanceFactory {
    public IAccountService getAccountService(){//不是静态方法
        return new AccountServiceImpl();
    }
}

目的:先创建实例工厂instanceFactory,然后调用getAccountService()方法创建对象。

		//标识符						指定类
<bean id="instanceFactory" class="factory.InstanceFactory"></bean>
		//标识符							指定实例工厂的id					指定实例工厂中生产对象的方法
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

4.Spring的依赖注入(DI)

4.1.依赖注入(Dependency Injection)

在通过Spring创建对象的时候,因为使用的时反射机制而不是使用new关键字,因此我们需要在配置文件指定创建出对象各字段的取值。
能注入的数据有三类:
第一种:基本数据类型和String。
第二种:其他bean类型(在配置文件中或者注解配置过的bean)
第三种:复杂/集合类型
注入的方式有三种:
第一种:使用构造函数提供。
第二种:使用set方法提供(最常用)
第三种:使用注解提供(笔记二整理)

4.2.使用构造函数进行注入

首先是一个类:目的是在类创建的时候,指定字段name,age和birthday的值

public class AccountServiceImpl{
    //如果是经常变化的数据,并不适用于注入的方式
    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;
    }
}

使用构造器注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:用于指定要注入数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
name:用于指定给构造函数中指定名称的参数赋值 (常用!这里用的也是name)
上面三个用来指定给构造函数中的哪个参数赋值
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:在获取bean对象时,必须注入操作,否则对象无法创建成功。
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

 <bean id="accountService" class="service.impl.AccountServiceImpl">
        //		name设置对应字段,下面同理  value为设置的初始值   ref为引用的是bean标签(为了调用Date类生成日期)
        <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>
    <!--配置一个日期对象,可以使用ref引用关联的bean对象-->
    <bean id="now" class="java.util.Date"></bean>

4.3.使用set方法注入

首先是一个类:要生成对应的set方法

public class AccountServiceImpl2{
    //如果是经常变化的数据,并不适用于注入的方式
    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;}
}

使用set方法注入:
使用的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定主时所调用的set方法名称。
value:用于提供基本类型和String类型的数据。
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象。
优势:创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:如果有某个成员必须有值,则获取对象时,有可能set方法没有执行。

<bean id="accountService2" class="service.impl.AccountServiceImpl2">
        <property name="age"  value="21"></property>//相当方便
        <property name="name" value="TEST"></property>
        <property name="birthday" ref="now"></property>
    </bean>
    <bean id="now" class="java.util.Date"></bean>

4.4.复杂类型的注入/集合类型的注入

准备一个演示类:

public class AccountServiceImpl3{
   private String[] myStrs;//数组类型
   private List<String> myList;//集合类型
   private Set<String> mySet;//set类型
   private Map<String,String> myMap;//hash表类型
   private Properties myPros;//也是类似hash表类型
   //各自生成set方法
   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 setMyPros(Properties myPros) { this.myPros = myPros; }
    }

各自的标签以及方法:

<bean id="accountService3" class="service.impl.AccountServiceImpl3">
        <property name="myStrs">
            <array>
                <value>value1</value>
                <value>value2</value>
                <value>value3</value>
            </array>
        </property>
        <property name="myList">
            <list>
                <value>value1</value>
                <value>value2</value>
                <value>value3</value>
            </list>
        </property>
        <property name="mySet">
            <set>
                <value>value1</value>
                <value>value2</value>
                <value>value3</value>
            </set>
        </property>
        <property name="myMap">
            <map>
                <entry key="testA" value="aaa"></entry>
                <entry key="testB">
                    <value>BBB</value>
                </entry>
            </map>
        </property>
        
        <property name="myPros">
            <props>
                <prop key="testC"></prop>
                <prop key="testD"></prop>
            </props>
        </property>
    </bean>

注意:总体来说分为两大结构,List和Map,相同结构的标签内容可以替换使用。
用于给List结构集合注入的标签:list,array,set
用于给Map结构集合注入的标签:map,props

4.5.使用注解进行注入

待续。

猜你喜欢

转载自blog.csdn.net/qq374461948/article/details/106018502