Spring介绍
Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基。
从spring的版本更迭中可以看出,spring的架构在不断的模块化,解耦程度越来越高,尽量实现低耦合高内聚。


目前版本为止spring的架构图如下:
Data Access:数据访问Data Integration:数据集成Web:Web开发AOP:面向切面编程Aspects:AOP思想实现Core Container:核心容器Test:单元测试与集成测试
Spring解决了什么问题?
任何一门技术的出现,肯定不是空穴来风,肯定有他需要解决的业务。
在传统的Java编程中,代码的书写耦合度偏高,我们一般在使用某个类的方法时,我们一般需要先new一个对象,将它实例化,在不那么复杂的业务中,这显得不那么重要,随着业务的迭代和开发的深入,复杂多变的需求开始慢慢侵蚀原本"完美"的架构,我们使用的new方式实例化的对象可能不是那么完美好用了,因为这样的耦合度偏高。
spring的出现,恰好可以解决这个问题,在spring中,我们不要主动去new产生对象,转换为由外部提供对象,并且对象的创建控制权由程序转移到外部,这种思想称为控制反转。

第一个spring项目
新建项目

导入spring的jar包
在pom.xml添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
右键—>重新加载项目
项目结构
在resource资源文件夹下新建一个Spring配置文件,新建—>XML配置文件—>Spring配置—>命名为applicationContext。
tips:文件名可随意,但建议使用applicationContext。

创建服务类
内容输出测试效果即可
package site.hikki.service;
public class BookService {
public void save(){
System.out.println("BookService save...");
}
}
创建bean
在resource下applicationContext.xml配置文件添加以下内容:
<bean id="bookService" class="site.hikki.service.BookService"/>
表示创建一个bean对象,对外开放给其他类使用。
创建程序入口
package site.hikki;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import site.hikki.service.BookService;
public class App {
public static void main(String[] args) {
//创建Ioc容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
// 输出结果:
// BookService save...
bean的基础配置
配置格式
| 类别 | 描述 |
|---|---|
| 名称 | bean |
| 类型 | 标签 |
| 所属 | beans标签 |
| 功能 | 定义Spring核心容器管理的对象 |
| 格式 | <beans> <bean/> <bean></bean> </beans> |
| 属性 列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一 class:bean的类型,即配置的bean的全路径类名 |
| 范例 | <bean id="BookService" class="site.hikki.service.BookService"/> <bean id="bookDao" class="site.hikki.dao.bookDao"> </bean> |
bean别名配置
| 类别 | 描述 |
|---|---|
| 名称 | name |
| 类型 | 属性 |
| 所属 | bean标签 |
| 功能 | 定义bean的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔 |
| 范例 | <bean id="BookService" name="bookservicee servicebook" class="site.hikki.service.BookService"/> <bean id="bookDao" name="bookdao,daobook,db" class="site.hikki.dao.bookDao"> </bean> |
获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常
NoSuchBeanDefinitionExceptionNoSuchBeanDefinitionException: No bean named 'bookDao' available
bean作用范围
| 类别 | 描述 |
|---|---|
| 名称 | scope |
| 类型 | 属性 |
| 所属 | bean标签 |
| 功能 | 定义bean的作用范围,可选范围如下 lsingleton:单例(默认) lprototype:非单例 |
| 范例 | <bean id="BookService" class="site.hikki.service.BookService" scope="prototype"/> |
Spring的生命周期
我们有时候需要在spring的Ioc容器的创建和结束时执行一些业务逻辑,我们需要怎么做呢?
项目结构
├─java
│ └─site
│ └─hikki
│ ├─dao
│ │ │ BookDao.java #持久层实现类
│ │ │
│ │ └─impl
│ │ BookDaoImpl.java # 持久层接口
│ ├─service
│ │ │ BookService.java #业务逻辑实现类
│ │ │
│ │ └─impl
│ │ BookServiceImpl.java # 业务逻辑接口
│ │ App.java
└─resources
│ applicationContext.xml # spring配置文件
实现方法
BookDao
在BookDao接口创建一个save方法。
package site.hikki.dao;
public interface BookDao {
void save();
}
在BookDaoImpl实现BookDao接口的方法,并创建init和destory方法,方法名自定义。
package site.hikki.dao.impl;
import site.hikki.dao.BookDao;
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("BookDaoImpl save ...");
}
public void init(){
System.out.println("init ...");
}
public void destory(){
System.out.println("destory...");
}
}
BookService
在BookService接口创建一个save方法。
package site.hikki.service;
public interface BookService {
void save();
}
在BookServiceImpl实现BookService接口的方法。并定义多个接口InitializingBean、DisposableBean,并实现destroy和afterPropertiesSet方法
package site.hikki.service.impl;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import site.hikki.dao.impl.BookDaoImpl;
import site.hikki.service.BookService;
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
@Override
public void save(){
System.out.println("BookServiceImpl save ...");
}
private BookDaoImpl bookDao;
public void setBookDao(BookDaoImpl bookDao) {
this.bookDao = bookDao;
}
@Override
public void destroy() throws Exception {
System.out.println("destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet...");
}
}
在spring配置文件配置bean
<?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 id="bookDao" class="site.hikki.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
<bean id="bookService" class="site.hikki.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
- bean下的property字段中name属性是bookService该bean的对象类属性值,ref属性是引入上面第一个bean的id值,也就是引入上一个bean的对象。
创建程序入口
方法一:手动关闭Ioc容器
package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import site.hikki.dao.BookDao;
public class App {
public static void main(String[] args) {
//创建Ioc容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//方法一:手动关闭Ioc容器
ctx.close();
}
}
//输出结果:
// init ...
// BookDaoImpl save ...
// destory...
从以上结果可以看出,在bean配置init-method和destroy-method属性可以得到spring的声明周期范围。
在虚拟机jvm启动时,Ioc容器会加载bean配置然后启动,如果我们没有手动去关闭Ioc容器,该容器会一直在运行,直到我们手动去关闭它。
需要注意的是,ctx.close();方法一般放在不需要使用到bean之后,不然Ioc提前关闭,后面的bean就没法使用了。
方法二:设置钩子关闭容器
package site.hikki.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import site.hikki.dao.BookDao;
public class App {
public static void main(String[] args) {
//创建Ioc容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//设置自动关闭Ioc容器钩子
ctx.registerShutdownHook();
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
// 输出结果:
// init...
// afterPropertiesSet...
// BookDaoImpl save ...
// destroy
// destory...
ctx.registerShutdownHook();设置了Ioc容器自动化关闭钩子,在程序执行时,该方法可以提醒jvm虚拟机在结束时让Ioc容器先关闭再结束jvm虚拟机,并且ctx.registerShutdownHook();不需要考虑方法的位置,放在任何位置都可以。
bean生命周期
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
bean销毁时机
- 容器关闭前触发bean销毁
- 关闭容器方式
- 手动关闭容器
- ConfigurableApplicationContext接口close()操作
- 注册关闭钩子,在虚拟机退出前先关闭Ioc容器再退出虚拟机
- ConfigurableApplicationContext接口registerShutdownHook()操作
- 手动关闭容器
Spring单例模式
spring创建bean对象默认是单例模式的,但我们有时候的业务逻辑需要费单例模式那该怎么办?该案例为你解决这个问题。
单例模式
简单项目介绍
项目沿用上式例子,不需要进行修改,本案例只是用到BookDao接口和BookDaoImpl实现类。
BookDao接口:
package site.hikki.dao;
public interface BookDao {
void save();
}
BookDaoImpl实现类:
package site.hikki.dao.impl;
import site.hikki.dao.BookDao;
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("这是构造方法");
}
@Override
public void save() {
System.out.println("BookDaoImpl save ...");
}
public void init(){
System.out.println("init...");
}
public void destory(){
System.out.println("destory...");
}
}
spring配置文件:
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
程序入口
package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import site.hikki.dao.BookDao;
public class App {
public static void main(String[] args) {
//创建Ioc容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
System.out.println(bookDao2);
}
}
// 内容输出:
// 这是构造方法
// init...
// 这是构造方法
// site.hikki.dao.impl.BookDaoImpl@69453e37
// site.hikki.dao.impl.BookDaoImpl@69453e37
从内容输出可以看出来,两次实例化的bookDao和bookDao2都是同一个对象,也可以说是调用的对象都是同一个地址块的对象。
如果我想每次创建的对象都不一样?或者说是每次创建的对象都重新分配一个地址块该如何处理?这就是非单例模式,每次调用的对象都不是同一个。
这时候我们需要使用到实力工厂实例化了,我们之前使用的都是构造方式实例化bean的,每次实例化bean都会调用一次BookDaoImpl实现类的构造方法。
非单例模式
新建一个工厂Bean,isSingleton()方法默认是单例模式,除非你需要设置非单例模式,
package site.hikki.factory;
import org.springframework.beans.factory.FactoryBean;
import site.hikki.dao.BookDao;
import site.hikki.dao.impl.BookDaoImpl;
public class BookDaoFactoryBean implements FactoryBean<BookDao> {
//代替原始实例工厂创建对象的方法
@Override
public BookDao getObject() throws Exception {
return new BookDaoImpl();
}
@Override
public Class<?> getObjectType() {
return BookDao.class;
}
//创建的对象默认是单例模式的(true)
@Override
public boolean isSingleton() {
return false; //true:单例模式 ,false:非单例模式
}
}
spring配置:
<bean id="bookDao4" class="site.hikki.factory.BookDaoFactoryBean"/>
程序入口
package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import site.hikki.dao.BookDao;
public class App {
public static void main(String[] args) {
//创建Ioc容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao4");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao4");
System.out.println(bookDao);
System.out.println(bookDao2);
}
}
// 输出内容:
// 这是构造方法
// 这是构造方法
// site.hikki.dao.impl.BookDaoImpl@65e98b1c
// site.hikki.dao.impl.BookDaoImpl@61322f9d
从打印内容可以看到,两个对象的打印的地址不一样,说说两次创建的对象不是同一个。这就是设置非单例的过程了。
依赖注入
我们平时向一个类中传递数据有有几种方式?
- 普通方法(set方法)
- 构造方法
在spring中,依赖注入也是用于传递数据的,而依赖注入也有两种方法传递数据:
- setter注入
- 简单类型(int,String)
- 引用类型(实体类)
- 构造器注入
- 简单类型(int,String)
- 引用类型(实体类)
但我们一般都是使用Setter注入较多,下面实验只介绍setter注入方式
项目结构
项目和上面使用的也基本一样
├─java
│ └─site
│ └─hikki
│ ├─dao
│ │ │ BookDao.java
│ │ │
│ │ └─impl
│ │ BookDaoImpl.java
│ ├─service
│ │ │ BookService.java
│ │ │
│ │ └─impl
│ │ BookServiceImpl.java
│ │ App.java <!-- 程序入口 -->
└─resources
│ applicationContext.xml <!-- spring配置文件-->
setter注入
BookServiceImpl实现类:
我们在BookServiceImpl实现类定义几个对象,引用类型、int、String类型,定义三个对象,并且给他们set属性。
为了验证是否拿到数据,我们在该类下的save方法输出值的内容。
package site.hikki.service.impl;
import site.hikki.dao.BookDao;
import site.hikki.service.BookService;
public class BookServiceImpl implements BookService {
private BookDao bookDao;
private int age;
private String name;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public void save() {
System.out.println("book service save ..."+name+"年龄:"+ age);
bookDao.save();
}
}
构造器注入
BookDaoImpl实现类:
package site.hikki.dao.impl;
import site.hikki.dao.BookDao;
import site.hikki.service.BookService;
public class BookDaoImpl implements BookDao {
private BookService bookService;
private int age;
private String name;
public BookDaoImpl() {
System.out.println("BookDaoImpl无参构造器");
}
public BookDaoImpl(BookService bookService, int age, String name) {
this.bookService = bookService;
this.age = age;
this.name = name;
}
@Override
public void save() {
System.out.println("BookDaoImpl save ..."+name+"年龄:"+ age);
}
}
构造器注入的关键在于构造器参数的获取,如下:
public BookDaoImpl(BookService bookService, int age, String name) {
this.bookService = bookService;
this.age = age;
this.name = name;
}
配置bean
你可能会疑问,我们为什么要定义 <bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>???
其实是我们刚刚在BookServiceImpl实现类中定义了一个BookDao的引用类型,我们需要用到bookDao的对象,我们就必须在bean配置他的对象。不然你怎么实例化bookDao对象呢?
<?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 id="bookService" class="site.hikki.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="name" value="小码同学"/> <!-- 定义值 -->
<property name="age" value="20"/> <!-- 定义值 -->
</bean>
<!-- ref将参数传到BookDaoImpl中-->
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl">
<constructor-arg name="bookService" ref="bookService"/>
<constructor-arg name="age" value="22"/><!-- 定义值 -->
<constructor-arg name="name" value="吴小白"/><!-- 定义值 -->
</bean>
</beans>
程序入口
我们实例化用BookService来接收,并且调用save()方法。观察打印结果
package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
//创建Ioc容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
// 打印结果:
// book service save ...小码同学年龄:20
// BookDaoImpl save ...吴小白年龄:22
从打印结果可以看到,我们在配置文件定义的值可以成功被接收,并且BookDao也被实例化调用了。
<bean id="bookService" class="site.hikki.service.impl.BookServiceImpl">
<!-- <property name="bookDao" ref="bookDao"/>-->
<property name="name" value="小码同学"/>
<property name="age" value="20"/>
</bean>
<!-- ref将参数传到BookDaoImpl中-->
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>
留下个小问题?你试试将bookDao的属性注释掉再重新运行试试,去了解一下为什么会这样
{% hideToggle 原因解释 %}
当<property name="bookDao" ref="bookDao"/>没有传递BookDao的参数时,在BookServiceImpl的实现类下的save方法中,存在bookDao.save();,它表示准备调用BookDao接口的save()方法,但是调用时,bean配置没有给他实例化啊,为什么没有实例化?
原因刚刚把<property name="bookDao" ref="bookDao"/>注释掉了,这段代码的作用就是给他实例化对象。
{% endhideToggle %}
依赖自动装配
使用方法
在上一节,我们讲到依赖注入的问题,我们需要配置一个引用类型的参数才能在bookService调用BookDao的方法,但每次都要配置,感觉好麻烦啊,有没有什么方法可以让他帮我自己识别,自动帮我装配传参过去呢?
<bean id="bookService" class="site.hikki.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/> <!-- 配置该引用类型 -->
</bean>
<!-- ref将参数传到BookDaoImpl中-->
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>
答案是:有的,spring有有一个自动装配的方法,添加autowire属性,该属性有五个值:
- byType(根据类型自动装配)
- byName(根据名称自动装配)
- constructor(使用构造方法自动装配)
- default(使用默认)
- no(不使用自动装配)
这个根据类型和名称是BookServiceImpl下的setBookDao方法的那个名称,比如该方法的名称就是bookDao根据该名称自动装配,根据该类型自动装配。
<bean id="bookService" class="site.hikki.service.impl.BookServiceImpl" autowire="byType"/>
<!-- ref将参数传到BookDaoImpl中-->
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>
依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单的类型进行操作。
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用。
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名和配置耦合,不推荐使用。
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效。
集合注入
集合注入用的不多,一般用于一些项目的初始化,但也很少用到,用的最多
DataService方法实现
我们在DataService定义几个属性,类型都是集合。
然后在spring配置文件添加初始化的值,进行注入。
定义属性后,记得给他们设置set方法,并且再定义一个save方法打印值的输出,用于检验值是否成功注入。
import java.util.*;
public class DataService {
private int[] arrayInt;
private List<String> listStr;
private Set<String> setStr;
private Map<String,String> mapStr;
private Properties propertiesStr;
public void setArrayInt(int[] arrayInt) {
this.arrayInt = arrayInt;
}
public void setListStr(List<String> listStr) {
this.listStr = listStr;
}
public void setSetStr(Set<String> setStr) {
this.setStr = setStr;
}
public void setMapStr(Map<String, String> mapStr) {
this.mapStr = mapStr;
}
public void setPropertiesStr(Properties propertiesStr) {
this.propertiesStr = propertiesStr;
}
public void save(){
System.out.println("DataService...");
System.out.println("遍历数组arrayInt:"+ Arrays.toString(arrayInt));
System.out.println("遍历数组listStr:"+listStr.toString());
System.out.println("遍历数组setStr:"+setStr.toString());
System.out.println("遍历数组mapStr:"+mapStr.toString());
System.out.println("遍历数组propertiesStr:"+propertiesStr.toString());
}
}
spring配置
定义一个bean,该bean指定DataService类,添加子标签property,定义集合数据,其中property标签的name的值应该为注入DataService对象的属性。
重点学习:
- List
- Map
<?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 id="dataService" class="DataService">
<!-- 数据初始化 注入-->
<property name="arrayInt">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<property name="listStr">
<!-- 重点-->
<list>
<value>小码同学</value>
<value>小码博客</value>
<value>小码公众号</value>
</list>
</property>
<property name="mapStr">
<!-- 重点-->
<map>
<entry key="name" value="小码同学"/>
<entry key="blog" value="https://blog.hikki.site"/>
<entry key="weixin" value="小码同学"/>
</map>
</property>
<property name="setStr">
<set>
<value>小码同学</value>
<value>小码博客</value>
<value>小码公众号</value>
</set>
</property>
<property name="propertiesStr">
<props>
<prop key="name">小码同学</prop>
<prop key="blog">小码博客</prop>
<prop key="weixin">小码公众号</prop>
</props>
</property>
</bean>
</beans>
程序入口
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.ApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataService dataService = (DataService) ctx.getBean("dataService");
dataService.save();
}
}
// 结果输出:
// DataService...
// 遍历数组arrayInt:[100, 200, 300]
// 遍历数组listStr:[小码同学, 小码博客, 小码公众号]
// 遍历数组setStr:[小码同学, 小码博客, 小码公众号]
// 遍历数组mapStr:{name=小码同学, blog=https://blog.hikki.site, weixin=小码同学}
// 遍历数组propertiesStr:{weixin=小码公众号, name=小码同学, blog=小码博客}
由结果可以看到集合已经注入成功。
spring的基础已经学习了差不多啦,继续加油!!!

