Spring简介
spring是一种用于简化复杂的企业开发的轻量级框架(从目的来说),是一种轻量级IOC和AOP的容器框架
IOC(控制反转)
- 定义:是一种设计思想,它将对象的创建和对象之间的依赖关系的管理从应用程序本身转移到了外部容器。在传统的编程中,对象的创建和依赖关系是由程序内部的代码直接控制的,而在 IOC 思想下,这些控制权被反转给了一个外部的容器(如 Spring 容器)。
- 作用:
- 解耦组件之间的依赖关系:使得各个组件之间的耦合度降低,提高了软件的可维护性和可扩展性。比如,一个业务逻辑组件不再需要自己去创建它所依赖的其他组件,而是由外部容器来创建并注入,这样当依赖的组件发生变化时,只需要在容器中进行配置,而不需要修改业务逻辑组件的代码。
- 提高软件的可测试性:由于对象的依赖关系是由外部注入的,在进行单元测试时,可以很方便地替换掉真实的依赖组件,使用模拟的组件来进行测试,从而提高了测试的效率和准确性。
简单来说就是配置bean
DI(依赖注入)
- 定义:是实现 IOC 的一种具体方式。它是指在创建对象时,将对象所依赖的其他对象通过某种方式(如构造函数、方法参数、属性等)注入到该对象中,而不是由对象自身去创建依赖的对象。
- 注入方式:
- 构造函数注入:通过在类的构造函数中声明依赖对象的参数,在创建对象时,由外部传入依赖对象。这种方式可以确保对象在创建时就已经完成了依赖关系的注入,并且依赖关系在对象的整个生命周期内不会改变。
- 方法参数注入:通过类的方法来接收依赖对象作为参数,在需要使用依赖对象时,调用该方法并传入依赖对象。这种方式比较灵活,可以在对象的生命周期内动态地改变依赖关系。
- 属性注入:通过类的属性的 setter 方法来注入依赖对象。这种方式比较直观,并且可以在配置文件或注解中方便地进行配置。
简单来说就是使用propetry标签配置对象之间的关系,让创建对象在IOC容器中实现,在使用时直接定义并使用set方法
IOC 和 DI 的关系
入门使用
将spring的jar包导入项目的方式
一种是原始人方式去官网下载jar包然后导入项目
第二种是在Maven项目的xml配置文件引入依赖,然后idea会自动下载jar包,不成功的原因可能是没有下载spring插件,以及jar包没有自动加载到项目,这时候点开 project structure libaries查看项目里的spring有没有下载到jar包,没有的话根据提示点击下载
在pom.xml中配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.1</version>
</dependency>
创建spring的xml配置文件
右键resoures找到xml然后选择Spring Config创建applicationContext.xml。然后用bean标签配置对象和属性。
当一个类需要创建其他类的对象,如Service对象创建Dao对象,需要在Service的bean中使用propetry标签配置关系。然后就可以在Service当中定义BookDao对象。
name:是指配置的对应setBookDao方法去掉set后的属性名,首字母一般小写
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.example.service.impl.BookServiceImpl">
<!--7.配置service与Dao的关系-->
<!--property标签表示配置当前bean属性-->
<!--name属性是指配置的对应setBookDao方法去掉set后的属性名,首字母一般小写-->
<!--ref属性表示参照哪一个bean(用bean的名字,不再需要具体地址)-->
<property name="bookDao" ref="bookDao"/>
</bean>
文件结构
BookDaoImpl
package com.example.dao.impl;
import com.example.dao.BookDao;
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save....");
}
}
BookServiceImpl
需要对bookDao定义set方法
package com.example.service.impl;
import com.example.service.BookService;
import com.example.dao.BookDao;
public class BookServiceImpl implements BookService {
//private BookDao bookDao = new BookDaoImpl();
// 5.为了解决代码耦合度过高,使用IOC容器将创建对象的过程在容器中实现,不再使用new
private BookDao bookDao;
@Override
public void save() {
System.out.println("book service save...");
bookDao.save();
}
// 6.提供set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
AppTest
package com.example;
import com.example.dao.BookDao;
import com.example.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
public static void main(String[] args) {
// 获取IOC容器,applicationContext.xml为Spring Config文件地址
ApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取BookDao的bean
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
// 获取BookService的Bean
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
Bean的基础配置
Bean别名配置
Bean的作用范围配置
同过将scope属性设置为protopye将id,name指定的bean别名分别生成不同的对象,默认为singletion为单例,所有别名对应一个对象。
但是注意,id是只有一个
// 获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取BookDao的bean
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
<bean id="bookDao" name="bookDao2 bookDao3"
class="com.example.dao.impl.BookDaoImpl" scope="prototype"/>
将scope属性设置为protopye,对象的数量是无穷的,那么Spring将会在使用对象的时候就生成一个,没有优势,所以一般使用单例singletion。通过对象复用,每次都使用同一个对象。
适合交给容器进行管理的bean
-
表现层对象
-
业务层对象
-
数据层对象
-
工具对象
不适合交给容器进行管理的bean
-
封装实体的域对象
Bean的实例化
构造方法(常用)
使用无参构造函数,如果没有抛出异常BeanCreationException。
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("book dao constructor in running.....");
}
public void save() {
System.out.println("book dao save....");
}
}
*静态工厂
在Factory文件目录下创建对应的Factory类
实现静态函数getBookDao创建实例
package com.example.Factory;
import com.example.dao.BookDao;
import com.example.dao.impl.BookDaoImpl;
public class BookDaoFactory {
public static BookDao getBookDao() {
return new BookDaoImpl();
}
}
BookDao bookDao = BookDaoFactory.getBookDao();
Spring配置信息
<bean id="bookDao" class="com.example.Factory.BookDaoFactory" factory-method="getBookDao"/>
class属性的值是工厂Factroy文件地址,factory-method是真正用于创建实例的方法名。
主要是解决早期项目问题
实例工厂(了解)
也是在Factory工厂当中返回一个新创建的对象,但是方法不是静态,因此在使用的时候需要新建工厂对象,然后再调用对应的方法创建需要的对象。
package com.example.Factory;
import com.example.dao.BookDao;
import com.example.dao.impl.BookDaoImpl;
public class BookDaoFactory {
public BookDao getBookDao() {
return new BookDaoImpl();
}
}
package com.example;
import com.example.Factory.BookDaoFactory;
import com.example.dao.BookDao;
import com.example.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
public static void main(String[] args) {
// 实例工厂实例化bean
BookDaoFactory bookDaoFactory = new BookDaoFactory();
BookDao bookDao = bookDaoFactory.getBookDao();
bookDao.save();
}
}
对应的Spring config的xml文件配置
<!--先对实例工厂配置基础bean-->
<bean id="bookDaoFactroy" class="com.example.Factory.BookDaoFactory"/>
<!--再通过工厂bean的id(属性名位factory-bean)来创建需要的对象-->
<!--Factroy-method属性依旧是工厂内创建相应对象的函数(非静态)-->
<bean id="bookDao" factory-method="getBookDao" factory-bean="bookDaoFactroy"/>
FactroyBean(实用)
新建文件BookDaoFactroyBean文件
继承FactroyBean类泛型设置为对应类型
重写方法
- getObject作用:代替原始实例工厂中创建对象的方法
- getObjectType询问返回值类型为对象BookDao.class
- isSingLeton设置是否单例,默认为true
package com.example.Factory;
import com.example.dao.BookDao;
import com.example.dao.impl.BookDaoImpl;
import org.springframework.beans.factory.FactoryBean;
public class BookDaoFactroyBean implements FactoryBean<BookDao> {
@Override
public BookDao getObject() throws Exception {
return new BookDaoImpl();
}
@Override
public Class<?> getObjectType() {
return BookDao.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
Spring配置信息
<!--FactroyBean实例化bean-->
<bean id="bookDao" class="com.example.Factory.BookDaoFactroyBean"/>
配置Spring后的实例化方法均相同
// 获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取BookDao的bean
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
Bean的生命周期
- 生命周期:从创建到消亡的完整过程
- bean生命周期:bean对象从创建到销毁的整体过程
- bean生命周期控制:在bean创建后到销毁前做一些事情
*配置生命周期
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
destroy方法无效?
关闭容器
close关闭容器
ApplicationContext中没有close方法 需要将ApplicationContext更换成ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
//调用ctx的close()方法
ctx.close();
注册钩子关闭容器
在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器 调用ctx的registerShutdownHook()方法。
意思是在关闭虚拟机之前把容器关闭,可以写在任意位置。
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
//调用ctx的registerShutdownHook()方法
ctx.registerShutdownHook()
相同点:这两种都能用来关闭容器
不同点:close()是在调用的时候关闭,registerShutdownHook()是在JVM(虚拟机)退出前调用关闭。 分析上面的实现过程,会发现添加初始化和销毁方法,即需要编码也需要配置,实现起来步骤比较多 也比较乱。
配置生命周期
Spring提供了两个接口来完成生命周期的控制,好处是可以不用再进行配置 destroy-method 接下来在BookServiceImpl完成这两个接口的使用: 修改BookServiceImpl类,添加两个接口 init-method和 InitializingBean, DisposableBean并实现接口中的 两个方法 afterPropertiesSet和 destroy。
对于InitializingBean接口中的afterPropertiesSet方法,翻译过来为属性设置之后,在执行set方法之后执行。
-
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean { //private BookDao bookDao = new BookDaoImpl(); // 5.为了解决代码耦合度过高,使用IOC容器将创建对象的过程在容器中实现,不再使用new private BookDao bookDao; @Override public void save() { System.out.println("book service save..."); bookDao.save(); } // 6.提供set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } @Override public void destroy() throws Exception { System.out.println("service destory"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("service Init"); } }
bean生命周期小结
(1)关于Spring中对bean生命周期控制提供了两种方式:
- 在配置文件中的bean标签中添加 init-method和 destroy-method属性
- 类实现 InitializingBean与 DisposableBean接口,这种方式了解下即可
(2)对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean:执行业务操作
- 关闭/销毁容器:执行bean销毁方法
(3)关闭容器的两种方式:
ConfigurableApplicationContext是ApplicationContext的子类
- close()方法
- registerShutdownHook()方法
依赖注入方式
setter注入(个人推荐)
setter注入引用类型:直接使用set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
setter注入简单类型:
- 在BookDaoImpl类中声明对应的简单数据类型的属性,并提供对应的setter方法
private int id;
private String str;
public void setId(int id) {
this.id = id;
}
public void setStr(String str) {
this.str = str;
}
- 在applicationContext.xml配置文件中使用property标签注入
name:是指配置的对应setBookDao方法去掉set后的属性名,首字母一般小写
value:后面跟的是简单数据类型,对于参数类型,Spring在注入的时候会自动转换,但不可出现不能转换的情况
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl">
<property name="id" value="506"/>
<property name="str" value="埃德加"/>
</bean>
重点
- 在bean中定义数据类型属性并提供可访问的set方法
- 配置中简单数据类型使用<property>标签,使用value属性注入属性
构造器注入方式
构造器注入应用类型
对应的构造方法
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.example.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
- name:对应的值为构造函数中方法形参的参数名,必须要保持一致。
- ref:指向的是spring的IOC容器中其他bean对象。
构造器注入简单数据类型
在BookDaoImpl类当中定义简单数据类型以及构造方法
private int id;
private String str;
public BookDaoImpl(int id, String str) {
this.id = id;
this.str = str;
}
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl">
<!--构造器注入-->
<constructor-arg name="id" value="506"/>
<constructor-arg name="str" value="埃德加"/>
</bean>
但是当形参名发生改变,对应的xml配置也需要改变,代码耦合度过高
方法一:删除name属性,添加type属性,按照类型注入
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="506"/>
<constructor-arg type="java.lang.String" value="埃德加"/>
</bean>
方式二:删除type属性,添加index属性,按照索引下标注入,下标从0开始,可以交换书写顺序
<bean id="bookDao" class="com.example.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="506"/>
<constructor-arg index="1" value="埃德加"/>
</bean>
这三种方式均可以混用,且可以交换书写顺序。但后两个不够直观。
如何选择
1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 强制依赖指对象在创建的过程中必须要注入指定的参数
- 构造器注入方式必须要注入,否则无法运行
2. 可选依赖使用setter注入进行,灵活性强
- 可选依赖指对象在创建过程中注入的参数可有可无
3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相 对严谨
4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选 依赖的注入
5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注 入
依赖注入自动装配
按照类型自动装配
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byType"/>
- 需要注入属性的类中对应属性的setter方法不能省略
- 被注入的对象必须要被Spring的IOC容器管理
- 按照类型在Spring的IOC容器中如果找到多个对象,会报 NoUniqueBeanDefinitionExceptio
一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byName"/>
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
集合等数据类型注入方式
注入数组、list、set类型数据模版
?指数据类型
<property name="?">
<?>
<value>100</value>
<value>200</value>
<value>300</value>
</?>
</property>
注入Map类型数据
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
注入Properties类型数据
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
properties文件
- 对于一些固定的常量如数据库连接四要素,把这些值写在Spring的配置文 件中不利于后期维护
- 需要将这些值提取到一个外部的properties配置文件中
- Spring框架如何从配置文件中读取属性值来配置就是接下来要解决的问题。
实现步骤
步骤一:resources下创建一个jdbc.properties文件,并添加对应的属性键值对
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
步骤2:开启 context 命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
<!-- 要改其他的只需将下面两个网址复制,修改context这个名字即可 -->
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
>
</beans>
步骤3:加载properties配置文件
在配置文件中使用 context命名空间下的标签来加载properties配置文件
<context:property-placeholder location="application.properties"/>
步骤4:完成属性注入
使用 ${key}来读取properties配置文件中的内容并完成属性注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
>
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
读取单个属性
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="name" value="${jdbc.driver}"/>
</bean>
- <context:property-placeholder/>标签会加载系统的环境变量,而且环境变量的值会被优先加载
- system-properties-mode:设置为NEVER,表示不加载系统属性,就可以解决上述问题。或者修改properties文件当中的key名称
加载多个properties配置文件
方式二:可以实现,如果配置文件多的话,每个都需要配置
- 方式三: *.properties代表所有以properties结尾的文件都会被加载,可以解决方式一的问 题,但是不标准
- 方式四:标准的写法, classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根 路径
- 方式五:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的 properties配置文件
总结
容器相关
- BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载 ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类 ClassPathXmlApplicationContext(常用)
- FileSystemXmlApplicationContext
bean相关属性
最常用的就两个属性id和class。