Spring注解开发和XML开发

时间:2024-04-06 08:36:12

目录

  • Spring
    • 简介
    • 发展史
    • Spring Framework系统架构
    • spring 核心概念
      • IOC、IOC容器、Bean、DI
      • IOC快速入门
      • DI快速入门
    • IOC
      • Bean基础配置
        • id与class属性
        • name属性
        • scope属性
      • Bean的实例化
        • 构造方法
        • 静态工厂
        • 实例工厂
        • FactoryBean的使用(工厂实例的简化)
      • Bean的生命周期
        • 添加初始化和销毁方法
        • close关闭容器
        • 注册钩子关闭容器
        • 关闭方式的区别
        • 通过接口来简化关闭容器
    • DI
      • setter注入
        • 注入引用数据类型
        • 注入简单数据类型
      • 构造器注入
        • 构造器注入多个数据类型
        • 构造器注入多个简单数据类型
        • 解决构造函数中参数名紧耦合
      • 自动装配
        • 什么是依赖自动装配
        • 自动装配的方式有哪些?
        • 按照类型完成自动装配的配置
        • 按照名称完成自动注入装配的配置
        • 注意事项(配置特征)
      • 集合注入
      • 加载 properties 文件
        • 读取单个属性
    • 核心容器
      • 容器
      • Bean 的三种获取方式
      • 容器类层次结构
      • BeanFactory的使用
    • IOC/DI注解开发
      • 注解开发定义bean
      • 纯注解开发模式
      • 注解开发bean作用范围与生命周期管理
        • bean的作用范围
        • bean的生命周期
      • 注解开发依赖注入
        • 注解实现按照类型注入
        • 注解实现按照名称注入
        • 简单数据类型注入
        • 注解读取 properties 配置文件
      • IOC/DI注解开发管理第三方bean
        • 注解开发管理第三方bean
        • 引入外部配置类
          • 使用包扫描引入
          • 使用@Import注解引入
        • 注解开发实现为第三方bean注入资源
          • 简单数据库类型
          • 引用数据类型
        • XML开发和注解的对比

Spring

官网:Spring | Home

简介

随着时代发展,软件规模与功能都呈几何式增长,开发难度也在不断递增,Spring可以简化开发,降低企业级开发的复杂性,使开发变得更简单快捷

随着项目规模与功能的增长,遇到的问题就会增多,为了解决问题会引入更多的框架,Spring可以框架整合,高效整合其他技术,提高企业级应用开发与运行效率

Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能

在这里插入图片描述

  • Spring Framework: Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
  • SpringBoot: Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
  • SpringCloud: 这个是用来做分布式之微服务架构的相关开发。

发展史

在这里插入图片描述

  • IBM(IT公司-国际商业机器公司)在1997年提出了EJB思想,早期的JAVAEE开发大都基于该思想。
  • Rod Johnson(Java和J2EE开发领域的专家)在2002年出版的Expert One-on-One J2EE Design and Development ,书中有阐述在开发中使用EJB该如何做。
  • Rod Johnson在2004年出版的Expert One-on-One J2EE Development without EJB ,书中提 出了比EJB思想更高效的实现方案,并且在同年将方案进行了具体的落地实现,这个实现就是 Spring1.0。
  • 随着时间推移,版本不断更新维护,目前最新的是Spring5
    • Spring1.0是纯配置文件开发
    • Spring2.0为了简化开发引入了注解开发,此时是配置文件加注解的开发方式
    • Spring3.0已经可以进行纯注解开发,使开发效率大幅提升
    • Spring4.0根据JDK的版本升级对个别API进行了调整
    • Spring5.0已经全面支持JDK8

Spring Framework系统架构

Spring Framework 是 Spring 生态圈中最基础的项目,是其他项目的根基

Spring Framework的发展也经历了很多版本的变更,每个版本都有相应的调整

在这里插入图片描述

Spring Framework的5版本目前没有最新的架构图,而最新的是4版本,所以接下来主要研究的 是4的架构图

在这里插入图片描述

  1. 核心层
    • Core Container:核心容器,这个模块是 Spring 最核心的模块,其他的都需要依赖该模块
  2. AOP层
    • AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
    • Aspect:AOP思想,Aspects 是对 AOP 思想的具体实现
  3. 数据层
    • Data Access:数据访问,Spring 全家桶中有对数据访问的具体实现技术
    • Data Integration:数据集成,Spring 支持整合其他数据层解决方案,比如MyBatis
    • Transactions:事务,Spring 中事务管理是 Spring AOP 的一个具体实现
  4. Web层
    • Spring MVC 框架
  5. Test层
    • Spring 主要整合了 Junit 来完成单元测试和集成测试

spring 核心概念

IOC、IOC容器、Bean、DI

在这里插入图片描述

在这个代码中,由于业务层代码中需要数据层的对象,导致两层之间的耦合度很高,针对这个问题 Spring 提出了一个解决方案:使用对象时,在程序中不要主动使用 new 产生对象,转换为由外部提供对象

  1. IOC(Inversion of Control)控制反转:控制反转的是对象的创建权

    • 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建的控制权由程序转移到外部,此思想称为控制反转
    • Spring技术对IOC思想进行了实现,提供了一个容器,称为IOC容器,用来充当IOC思想的 “ 外部 ”
    • IOC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为 Bean对象
  2. DI(Dependency Injection)依赖注入:绑定对象与对象之间的依赖关系

    在这里插入图片描述

    但是现在又有一个问题,当 IOC 容器中创建好 service 和 dao 对象后,因为 service 运行需要依赖 dao 对象,但是 service 对象和 dao 对象没有任何关系,导致程序无法正确运行,而在容器中建立对象与对象之间的绑定关系就要用到 DI

    • 在容器中建立 bean 与 bean 之间的依赖关系的整个过程,称为依赖注入

介绍完 Spring 的 IOC 和 DI 的概念后,我们会发现这两个概念的最终目标就是:充分解耦,具体实现靠:

  • 使用 IOC 容器管理 bean(IOC)
  • 在 IOC 容器内将有依赖关系的 bean 进行关系绑定(DI)
  • 最终结果为:使用对象时不仅可以直接从 IOC 容器中获取,并且获取到的 bean 已经绑定了所有的依赖关系.
  1. IOC容器:Spring 创建了一个容器用来存放所创建的对象,这个容器就叫 IOC 容器
  2. Bean:容器中所存放的一个个对象就叫 Bean 或 Bean 对象

IOC快速入门

  1. 创建 Maven 项目

  2. pom.xml引入依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  3. resources下创建 spring 配置文件(applicationContext.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">
        <!--bean标签标示配置bean
        id属性标示给bean起名字
        class属性表示给bean定义类型
        -->
            <bean id="bookDao" class="项目下的Dao实现类路径"/>
            <bean id="bookService" class="项目下的Service实现类路径"/>
        </beans>
    

    注意事项: bean 定义时 id 属性在同一个配置文件中不能重复

  4. 使用 Spring 提供的接口完成 IOC 容器的创建

  5. 从容器中获取对象进行方法调用

    public class App {
        public static void main(String[] args) {
        //获取IOC容器
        ApplicationContext ctx = new
        ClassPathXmlApplicationContext("applicationContext.xml");//这里的这个xml就是刚才创建的Spring的配置文件
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");//通过容器来获取Bean对象
        bookDao.save();
        }
    }
    
    

DI快速入门

实现依赖注入,必须要基于 IOC 管理 Bean

  1. 去除代码中的 new

  2. 为属性提供 setter 方法

    public class BookServiceImpl implements BookService {
        //删除业务层中使用new的方式创建的dao对象
        //private BookDao bookDao = new BookDaoImpl();
        private BookDao bookDao;
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
        //提供对应的set方法
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;	
        }
    }
    
  3. 在配置文件中添加依赖注入的配置

    <?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标签标示配置bean
            id属性表示给bean起名字
            class属性表示给bean定义类型
        -->
        <bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl"/>
            
        <bean id="bookService" class="com.XXX.service.impl.BookServiceImpl">
            <!--配置server与dao的关系-->
            <!--property标签表示配置当前bean的属性
                name属性表示配置哪一个具体的属性
                ref属性表示参照哪一个bean
            -->
            <property name="bookDao" ref="bookDao"/>
        </bean>
    </beans>
    

    注意: 配置中的两个 bookDao 的含义是不一样的

    • name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前 面加set找对应的setBookDao()方法进行对象注入
    • ref="bookDao"中 bookDao 的作用是让 Spring 能在IOC容器中找到id为bookDao的Bean对象给 bookService 进行注入

IOC

IOC 中对象的相关配置以及实例化方式和生命周期

Bean基础配置

id与class属性

在这里插入图片描述

注意: 因为 bookDao 和 bookService 一般在项目中是接口,接口不能实现,所以一般是使用它的实现类来进行对象的创建

name属性

在这里插入图片描述

XML中的配置写法

<!--name属性:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<!-- Ebi全称Enterprise Business Interface,翻译为企业业务接口 --> 
    <bean id="bookService" name="service service4 bookEbi"
        class="BookServiceImpl的类路径">
        <property name="bookDao" ref="bookDao"/>
    </bean>

    <bean id="bookDao" name="dao" class="BookDaoImpl的类路径"/>

注意:

  • 通过 ref 属性来指定 bean ,被指定的 bean 必须在容器中存在,而且 ref 属性值可以是 bean 的 name 属性值
  • 获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常 NoSuchBeanDefinitionException
scope属性

在这里插入图片描述

注意:

  • bean 为单例的意思是在 Spring 的 IOC 容器中只会有该类的一个对象,避免了对象的频繁创建与销毁,达到了 bean 对象的复用,性能高
  • bean在容器中
    • 如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
    • 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
  • 表现层对象、业务层对象、数据层对象、工具对象适合交给容器进行管理,封装实例的域对象,因为会引发线程安全问题,所以不适合交给容器进行管理

Bean的实例化

bean本质上就是对象,对象在new的时候会使用构造方法完成,那创建bean也是使用构造方法完成的,基于此,Bean 有三种创建方式

构造方法

Spring底层使用的是类的无参构造方法,而且是通过反射来访问到类中的方法的

静态工厂

这种方式一般是用来兼容早期的一些老系统,需要在Spring的配置文件中加入以下内容:

<bean id="orderDao" class="OrderDaoFactory类路径名" factory-method="getOrderDao"/>
<!-- class:工厂类的类全名 --> 
<!-- factory-mehod:具体工厂类中创建对象的方法名 --> 

在这里插入图片描述

虽然在工厂类中也是直接new对象,但是在工厂的静态方法中,我们除了new 对象外还可以做一些必不可少的业务操作

实例工厂

在Spring的配置文件中加入以下内容:

<bean id="userFactory" class="UserDaoFactory的类路径"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化工厂运行的顺序是:

  1. 创建实例化的工厂对象,对应的是第一行配置
  2. 调用对象中的方法来创建bean对象,对应的是第二行配置
    • factory-bean:工厂对象
    • factory-method:工厂对象中具体创建 bean 对象的方法名,对应关系如下:
    • 在这里插入图片描述
FactoryBean的使用(工厂实例的简化)

这种方式在Spring去整合其他框架的时候会被用到

让工厂类(这里以UserDaoFactory为例)实现FactoryBean接口,重写接口的方法:

  • 方法一: getObject(),被重写后,在方法中进行对象的创建并返回
  • 方法二: getObjectType(),被重写后,主要返回的是被创建类的Class对象
  • 方法三: 没有被重写,因为它已经给了默认值 true(单例,false为非单例),作用是设置对象是否为单例

在Spring的配置文件中进行配置

 <bean id="userDao" class="UserDaoFactoryBean类路径"/>

Bean的生命周期

bean 的生命周期:bean 对象,从创建到消亡的完整过程

bean 生命周期控制的是 bean 对象从创建后到销毁前做一些事情

关于 Spring 中对 bean 生命周期控制提供了两种方式:

  • 在配置文件中的 bean 标签中添加 init-method属性和destory-method属性
  • 类实现InitializingBean接口和DisposableBean接口
添加初始化和销毁方法
<bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>

init-method 属性是初始化方法,而 destroy-method 属性是销毁的方法

但是执行后只会执行初始化的 init 方法而未执行销毁方法destory,为什么?

原因是 Spring 的 IOC 容器是运行在 JVM 中,运行 main 方法后,JVM 启动,Spring 加载配置文件生成 IOC 容器,从容器获取 bean 对象,然后调方法执行,main 方法执行完后,JVM 退出,这个时候 IOC 容器中的 bean 还未来得及销毁就已经结束了,所以没有调用对应的 destory 方法

close关闭容器

ApplicationContext 中没有 close 方法,所以需要将 ApplicationContext 更换为 ClassPathXmlApplicationContext(ClassPathXmlApplicationContext 是 ApplicationContext 的子类) 来调用 close 方法,这样就可以正常的执行容器的销毁了

注册钩子关闭容器

在容器未关闭之前,提前设置好回调函数,让 JVM 在退出之前回调此函数来关闭容器,调用 registerShutdownHook 方法

注意: RegisterShutdownHook 方法在 ApplicationContext 中也没有,所以依然要使用 ClassPathXmlApplicationContext

关闭方式的区别

close 方法和 RegisterShutdownHook 方法区别:

  • 相同点:两种方法都能用来关闭容器
  • 不同点:close 方法是在执行调用的时候关闭,registerShutdownHook 方法是在 JVM 退出前自动调用关闭
通过接口来简化关闭容器

在实现类中实现 InitializingBean 和 DisposableBean 两个接口之后重写 afterPropertiesSet 方法和 destory 方法

注意: InitializingBean 接口中的 afterPropertiesSet 方法,翻译过来为 属性设置之后,对于 servce 层中的实现类方法来说,Dao 层的实现类为它的一个属性,setxxxDao 方法是 Spring 的 IOC 容器为它注入属性的方法,而setxxxDao方法先执行,afterPropertiesSet 方法后执行

总结:

对于 bean 的生命周期控制在 bean 的整个生命周期中所处的位置

  • 初始化容器
    • 1.创建对象(内存分配)
    • 2.执行构造方法
    • 3.执行属性注入(set操作)
    • 4.执行 bean 初始化方法
  • 使用 bean
    • 执行业务操作
  • 关闭/销毁容器
    • 执行 bean 销毁方法

DI

Spring 中提供了两种注入方式:

  • setter 注入
    • 简单类型(基础数据类型和字符串)
    • 引用类型
  • 构造器注入
    • 简单类型(基础数据类型和字符串)
    • 引用类型

setter注入

  1. 在 bean 中定义引用类型属性,并提供可访问的 set 方法

    public class BookServiceImpl implements BookService {
        private BookDao bookDao;
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    }
    
  2. 配置中使用 property 标签 ref 属性注入引用类型对象

    <bean id="bookService" class="BookServiceImpl类路径">
        <property name="实现类中的bookDao的属性" ref="bean中的bookDao对象"/>
    </bean>
    
    <bean id="bookDao" class="BookDaoImpl类路径"/>
    
注入引用数据类型
  1. 在实现类中声明属性并提供 setter 方法

    public class BookServiceImpl implements BookService{
        private BookDao bookDao;
        private UserDao userDao;//声明属性
    
        public void setUserDao(UserDao userDao) {//提供setter方法
            this.userDao = userDao;
        }
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    
    }
    
  2. 配置文件中进行注入配置

    <bean id="bookDao" class="BookDaoImpl类路径"/>
    <bean id="userDao" class="UserDaoImpl类路径"/>
    
    <bean id="bookService" class="BookServiceImpl类路径">
        <property name="实现类中的bookDao属性" ref="bean中的bookDao对象"/>
        <property name="实现类中的userDao属性" ref="bean中的userDao对象"/>
    </bean>
    
注入简单数据类型
  1. 在实现类中声明对应的简单数据类型的属性,并提供对应的 setter 方法

    public class BookDaoImpl implements BookDao {
        private String databaseName;//声明属性
        private int connectionNum;//声明属性
        
        // 提供 setter 方法
        public void setConnectionNum(int connectionNum) {
        	this.connectionNum = connectionNum;
        }
        public void setDatabaseName(String databaseName) {
        	this.databaseName = databaseName;
        }
        
    }
    
  2. 在配置问文件中进行注入配置

    <bean id="bookDao" class="com.XXX.dao.impl.BookDaoImpl">
        <property name="实现类方法中的参数databaseName(形参)" value="形参中的数据值"/>
        <property name="实现类方法中的参数connectionNum(形参)" value="形参中的数据值"/>
    </bean>
    
    <bean id="userDao" class="com.XXX.dao.impl.UserDaoImpl"/>
    
    <bean id="bookService" class="BookServiceImpl类路径">
        <property name="实现类中的bookDao属性" ref="bean中的bookDao对象"/>
        <property name="实现类中的userDao属性" ref="bean中的userDao对象"/>
    </bean>
    

构造器注入

构造器注入也就是构造方法注入,无非是把实现类中的 setter 方法改为构造器方法

  1. 删除实现类中的 setter 方法并提供构造方法

    public class BookServiceImpl implements BookService{
        private BookDao bookDao;
        
        // 在构造方法中对属性进行设置
        public BookServiceImpl(BookDao bookDao) {
            this.bookDao = bookDao;
        }
        
    }
    
  2. 在配置文件中配置构造方法注入

    <bean id="bookDao" class="BookDaoImpl类路径"/>
    
    <bean id="bookService" class="BookServiceImpl类路径">
        <constructor-arg name="实现类中构造方法的参数bookDao(形参)" ref="bean中的bookDao对象"/>
        <!-- 这里的constructor-arg标签就是通过构造方法注入的标签 -->
    </bean>
    

注意:

  • name 属性对应的值为构造函数中方法形参的参数名,必须保持一致
  • ref 属性指向的是 spring 的 IOC 容器中其他的 bean 对象
构造器注入多个数据类型
  1. 提供多个属性的构造函数

    public class BookServiceImpl implements BookService{
        private BookDao bookDao;// 声明引用类型属性
        private UserDao userDao;// 声明引用类型属性
        
        // 构造方法注入
        public BookServiceImpl(BookDao bookDao,UserDao userDao) {
            this.bookDao = bookDao;
            this.userDao = userDao;
        }
    }
    
  2. 配置文件中配置

    <bean id="bookDao" class="BookDaoImpl类路径"/>
    <bean id="userDao" class="UserDaoImpl类路径"/>
    
    <bean id="bookService" class="BookServiceImpl类路径">
        <constructor-arg name="实现类中构造方法的参数bookDao" ref="bean中的bookDao对象"/>
        <constructor-arg name="实现类中构造方法的参数userDao" ref="bean中的userDao对象"/>
    </bean>
    

    注意: constructor-arg 标签的顺序可以任意

构造器注入多个简单数据类型
  1. 添加多个简单属性并提供构造方法

    public class BookDaoImpl implements BookDao {
        private String databaseName; //声明简单类型属性
        private int connectionNum; //声明简单类型属性
    
        // 通过构造器注入
        public BookDaoImpl(String databaseName