Spring学习01----控制反转和依赖注入

1.初识Spring

在正式开始学习Spring前,先了解下Spring是什么,它做了一些什么事情,它的功能(特性)是什么。

1.1Spring是什么

  • Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

1.2 Spring的发展历程

1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson(spring 之父)
Expert One-to-One J2EE Design and Development(2002)
阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)

1.3Spring的优势

(1)方便解耦,简化开发
Spring就是一个大工厂,可以将所有对象创建和依赖的关系维护,交给Spring管理。
(2)AOP编程的支持
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
(3)声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程。
(4)方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序。
(5)方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
(6)降低JavaEE API的使用难度
Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低

1.4 Spring的体系结构

在这里插入图片描述

2.IOC的概念和作用


2.1 什么是程序的耦合

  • 耦合:耦合指的就是程序间的依赖关系,在开发中我们应该尽量做到编译期不依赖,运行时才依赖。
  • 耦合的弊端:程序的独立性很差。
  • 耦合的分类:
    • 类之间的依赖(比如JDBC中,第一步必须要com.mysql.jdbc.driver这个jar包
    • 方法之间的依赖
  • 我们在开发中,有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。下面举一个耦合的实例:

/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
	private IAccountDao accountDao = new AccountDaoImpl();
}
  • 上面的代码表示:在业务层中来调用持久层,业务层依赖于持久层的接口和实现类如果此时没有持久层的实现类,编译将不能通过。这显然不符合我们低耦合的原则,我们应该在实际开发中避免这种情况的出现。

2.2 解决程序的耦合思路

  • 那么我们解决耦合的问题呢,此时想到了在JDBC操作注册驱动时,我们为什么不使用 DriverManager 的 register方法,而是采用 Class.forName的方式?
@Test
public void testJdbc() throws Exception {
	// 注册驱动
	//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
	Class.forName("com.mysql.jdbc.Driver")
	// 后续操作...
}
  • 对于DriverManager.registerDriver(new com.mysql.jdbc.Driver())来说,它在注册驱动时,依赖于MySQL的驱动ajr包
  • 对于Class.forName("com.mysql.jdbc.Driver")来说,这里的com.mysql.jdbc.Driver只是一串字符串,在编译器并不会真正的依赖,所以即使我们没有这个驱动jar包,程序也不会报错,只有在运行过程中它才会报错,这样就解决了编译器依赖的问题。
  • 上面的方式也产生了一个新的问题:

    • mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码
  • 为了解决这个问题,我们想到使用配置文件的方式来解决。

  • 综上所述,我们通过工厂模式来解决耦合的问题:
    • 1.使用反射来创建对象,而避免使用new关键字
    • 2.通过读取配置文件。来获取要创建的对象的全限定类名。
  • 具体来说,即让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么读取配置文件,创建和获取对象的类就是工厂。

配置文件的内容:

accountService = service.impl.AccountServiceImpl
accountDao = dao.impl.AccountDaoImpl

内容为key、value的形式,即唯一表示=全限定类名

BeanFactory类(即工厂类):


/**
 * 一个创建Bean对象的工厂
 *
 * Bean:在计算机英语中,有可重用组件的含义
 * JavaBean:用Java语言编写的可重用组件
 *      javabean > 实体类
 *
 *  它就是创建我们的service和dao对象的
 *
 *  第一步:需要一个配置文件来配置我们的service和dao
 *          配置的内容:唯一标识=全限定类名(key=value)
 *  第二部:通过读取配置文件的内容,反射创建对象
 */
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<Object> 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 (IOException e) {
            throw new ExceptionInInitializerError("初始化失败");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

  }
    /**
     * 根据Bean名称的名称来获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}
  • 工厂的类的作用就是读取配置文件的内容,根据配置文件中的全限定类名来反射创建对象,并将创建的对象存储到一个容器中去。容器为一个Map集合,key就是配置文件中的key,以后需要某个对象只需提供某个类的唯一标识即可

2.3 使用IOC来解决程序的耦合

  • 首先我们需要明确一个问题,什么是IOC?
  • 在上面的小节中,我们通过引入一个工厂的概念,把对象的创建和查找都交给了这个工厂来做。我们需要什么直接问工厂来要。是一种被动的方式。这种被动接受获取对象的方式就是控制反转(IOC)的思想
    而在以前,我们需要什么是直接new出来的,是一种主动的方式。

在这里插入图片描述
在这里插入图片描述

  • 由上,我们引出IOC的概念:

    • 即把创建对象的权力交给框架,是框架的重要特性,主要作用就是削减计算机程序间的耦合。
  • 下面我们就使用Spring的IOC来解决程序间的耦合。

  • 首先准备一个maven工程,其pom.xml文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>day01_eesy_03spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>
</project>
  • 创建三层接口类和实现类的结构如下,模拟一个保存账户的服务
    在这里插入图片描述
  • 编写一个配置文件bean.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">

    <!--把对象的创建交给spring来管理-->
    <bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
    <bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean>
    
</beans>
  • 这里bean标签中id就是一个唯一标识,我们使用这个id就可以拿到该类。class就是全限定类名。
  • 上面的内容配置完成后,在Client表现层中,写上如下测试代码:
public class Client {

    public static void main(String[] args) {

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

        System.out.println(as);
        as.saveAccount();

    }
}

  • 这里ApplicationContext就是一个接口,而后面ClassPathXmlApplicationContext是它的一个具体实现类。它就是Spring框架中的工厂,IOC的核心就是使用到了工厂模式

2.4Spring中基于XML的细节

  • Spring中工厂的类结构图:

在这里插入图片描述

  • 在开发中我们通常使用BeanFactory的子接口 ApplicationContext,它有三个常用的实现类,具体如下表所示:
类名作用
ClassPathXmlApplicationContext类的根路径加载配置文件,推荐
FileSystemXmlApplicationContext磁盘任意路径加载配置文件,必须要有访问权限,不推荐
AnnotationConfigApplicationContext使用注解配置容器对象时,需要使用此类来创建 Spring 容器。它用来读取注解
  • BeanFactory 和 ApplicationContext都是容器的接口,它们两个有什么区别呢?

  • BeanFactory才是 Spring 核心容器的顶级接口。

    • 在构建核心容器时,创建对象的默认方式为延迟加载。也就是说,我们向容器获取对象时,容器才会去创建对象。
  • ApplicationContextBeanFactory的子接口。

    • 在构建核心容器时,创建对象的默认方式为立即加载。也就是容器一旦创建完毕,就会创建配置的所有对象(singleton对象)。

3. Spring基于XML的IOC详解


3.1 bean标签

  • 作用:

    • 用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数如果没有无参构造函数则不能创建成功。
  • 属性:

    • id:给对象在容器中提供一个唯一标识。用于获取对象。
    • class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
    • scope:指定对象的作用范围。
      • singleton :默认值,单例的
      • prototype :多例的。
      • request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中。
      • session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中。
      • global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么
        globalSession 相当于 session 。
    • init-method:指定类中的初始化方法名称。
    • destroy-method:指定类中销毁方法名称
    • factory-bean:指定工厂 Bean 对象的唯一标识 (Id),将通过工厂创建对象。
    • factory-method:指定工厂中创建对象的方法,Spring 会通过该方法来创建对象。

3.2 Bean的作用范围与生命周期

  • 单例对象:scope="singleton"

    • 一个应用只有一个对象的实例。它的作用范围就是整个引用。
    • 生命周期:
      • 对象出生:当应用加载,创建容器时,对象就被创建了。
      • 对象活着:只要容器在,对象一直活着。
      • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
  • 多例对象:scope="prototype"

    • 每次访问对象时,都会重新创建对象实例。
    • 生命周期:
      • 对象出生:当使用对象时,创建新的对象实例。
      • 对象活着:只要对象在使用中,就一直活着。
      • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。

3.3 创建Bean的三种方式

  • 第一种方式:使用默认的构造函数创建
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>

如果类中没有默认构造函数,则对象无法创建

  • 第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
public class InstantFactory {
    public AccountServiceImpl service(){
        return new AccountServiceImpl();
    }
}
<bean id="instantFactory" class="factory.InstantFactory"></bean>-->
<bean id="accountService" factory-bean="instantFactory" factory-method="service"></bean>

先把工厂的创建交给spring来管理,然后再使用工厂中的方法来创建对象。

  • 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
public class Staticfactory {
    public static AccountServiceImpl service(){
        return new AccountServiceImpl();
    }
}
 <bean id="accountService" class="factory.Staticfactory" factory-method="service"></bean>

它与第二种方法的区别是:这里的工厂是静态的,无需创建工厂,直接通过静态工厂的方法创建对象。

4.Spring的依赖注入

4.1依赖注入的概念

  • 依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
  • 通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。所以业务层中应包含持久层的实现类对象。
  • 我们等待框架通过配置的方式将持久层对象传入业务层,而不是直接在代码中new某个具体的持久化层实现类,这种方式称为依赖注入.

4.2 构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入

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

    public AccountServiceImpl(String name, Integer age, Date date) {
        this.name = name;
        this.age = age;
        this.date = date;
    }
    @Override
    public void saveAccount() {
        System.out.println(name + ", " + age + ", " + date);
    }
}
    <!-- 使用Date类的无参构造函数创建Date对象 -->
<bean id="now" class="java.util.Date" scope="prototype"></bean>

<bean id="accountService" class="service.impl.AccountServiceImpl">
   <constructor-arg name="name" value="test"></constructor-arg>
   <constructor-arg name="age" value="18"></constructor-arg>
   <!-- birthday字段为已经注册的bean对象,其id为now -->
   <constructor-arg name="birthday" ref="now"></constructor-arg>
 </bean>
  • <constructor-arg>标签用于构造函数注入,属性如下:
    • index: 指定要注入的参数在构造方法参数列表中索引位置,索引从0开始。
    • type: 指定要注入的参数的数据类型,该数据类型也是构造方法中某个或某些参数的类型。
    • name: 指定要注入的参数的名称,这个是最方便也是最常用的。
    • value: 用于提供基本类型和 String 类型的数据。
    • ref: 用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。

4.3使用set方法注入(常用)

  • 在类中提供需要注入成员的 set 方法,创建对象只调用要赋值属性的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("保存成功" + name + "," + age + "," + birthday);;
    }

}
    <bean id="now" class="java.util.Date"></bean>
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <property name="name" value="test"></property>
        <property name="age" value="18"></property>
        <property name="birthday" ref="now"></property>
    </bean>

<property>标签用于 set 方法注入,属性如下:

  • name: 指定注定时所调用的 set 方法名称。
  • value: 用于提供基本类型和 String 类型的数据。
  • ref: 用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。

4.4注入集合字段

  • 集合字段及其对应的标签按照集合的结构分为两类: 相同结构的集合标签之间可以互相替换
  • 只有键的结构:
    • 数组字段: <array>标签表示集合,<value>标签表示集合内的成员.
    • List字段: <list>标签表示集合,<value>标签表示集合内的成员.
    • Set字段: <set>标签表示集合,<value>标签表示集合内的成员.
  • 其中<array>,<list>,<set>标签之间可以互相替换使用.
  • 键值对的结构:
    • Map字段: <map>标签表示集合,<entry>标签表示集合内的键值对,其key属性表示键,value属性表示值。
    • Properties字段: <props>标签表示集合,<prop>标签表示键值对,其key属性表示键,标签内的内容表示值。其中<map>,<props>标签之间,<entry>,<prop>标签之间可以互相替换使用。
public class AccountServiceImpl implements IAccountService {
	// 集合字段
	private String[] myArray;
	private List<String> myList;
	private Set<String> mySet;
	private Map<String,String> myMap;
	private Properties myProps;

	// 集合字段的set方法
	public void setMyStrs(String[] myArray) {
		this.myArray = myArray;
	}
	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;
	}
	
	@Override
	public void saveAccount() {
		System.out.println(Arrays.toString(myArray));
		System.out.println(myList);
		System.out.println(mySet);
		System.out.println(myMap);
		System.out.println(myProps);
	}
}

<bean id="accountService" class="cn.maoritian.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="key1" value="value1"></entry>
			<entry key="key2">
				<value>value2</value>
			</entry>
			
		</map>
	</property>

	<property name="myProps">
		<props>
			<prop key="key1">value1</prop>
			<prop key="key2">value2</prop>
		</props>
	</property>
</bean>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值