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添加依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.3.RELEASE</version> </dependency>
|
右键
—>重新加载项目
项目结构
在resource
资源文件夹下新建一个Spring配置文件,新建
—>XML配置文件
—>Spring配置
—>命名为applicationContext
。
tips:文件名可随意,但建议使用applicationContext
。
创建服务类
内容输出测试效果即可
1 2 3 4 5 6 7
| package site.hikki.service;
public class BookService { public void save(){ System.out.println("BookService save..."); } }
|
创建bean
在resource
下applicationContext.xml
配置文件添加以下内容:
1
| <bean id="bookService" class="site.hikki.service.BookService"/>
|
表示创建一个bean对象,对外开放给其他类使用。
创建程序入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
|
项目下载地址:https://rookie1679.lanzoue.com/iMzSZ0jzrb0d
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容器的创建和结束时执行一些业务逻辑,我们需要怎么做呢?
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ├─java │ └─site │ └─hikki │ ├─dao │ │ │ BookDao.java │ │ │ │ │ └─impl │ │ BookDaoImpl.java │ ├─service │ │ │ BookService.java │ │ │ │ │ └─impl │ │ BookServiceImpl.java │ │ App.java └─resources │ applicationContext.xml
|
实现方法
BookDao
在BookDao
接口创建一个save
方法。
1 2 3 4 5
| package site.hikki.dao;
public interface BookDao { void save(); }
|
在BookDaoImpl
实现BookDao
接口的方法,并创建init
和destory
方法,方法名自定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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
方法。
1 2 3 4 5
| package site.hikki.service;
public interface BookService { void save(); }
|
在BookServiceImpl
实现BookService
接口的方法。并定义多个接口InitializingBean
、DisposableBean
,并实现destroy
和afterPropertiesSet
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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
1 2 3 4 5 6 7 8 9 10
| <?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容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext; import site.hikki.dao.BookDao;
public class App { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); ctx.close(); } }
|
从以上结果可以看出,在bean配置init-method和destroy-method属性可以得到spring的声明周期范围。
在虚拟机jvm启动时,Ioc容器会加载bean配置然后启动,如果我们没有手动去关闭Ioc容器,该容器会一直在运行,直到我们手动去关闭它。
需要注意的是,ctx.close();方法一般放在不需要使用到bean之后,不然Ioc提前关闭,后面的bean就没法使用了。
方法二:设置钩子关闭容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ctx.registerShutdownHook(); BookDao bookDao = (BookDao) ctx.getBean("bookDao"); bookDao.save(); } }
|
ctx.registerShutdownHook();
设置了Ioc容器自动化关闭钩子,在程序执行时,该方法可以提醒jvm虚拟机在结束时让Ioc容器先关闭再结束jvm虚拟机,并且ctx.registerShutdownHook();不需要考虑方法的位置,放在任何位置都可以。
bean生命周期
- 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
bean销毁时机
- 容器关闭前触发bean销毁
- 关闭容器方式
- 手动关闭容器
- ConfigurableApplicationContext接口close()操作
- 注册关闭钩子,在虚拟机退出前先关闭Ioc容器再退出虚拟机
- ConfigurableApplicationContext接口registerShutdownHook()操作
项目下载地址:https://rookie1679.lanzoue.com/iLQ810jxkrch
Spring单例模式
spring创建bean对象默认是单例模式的,但我们有时候的业务逻辑需要费单例模式那该怎么办?该案例为你解决这个问题。
单例模式
简单项目介绍
项目沿用上式例子,不需要进行修改,本案例只是用到BookDao
接口和BookDaoImpl
实现类。
BookDao接口:
1 2 3 4 5
| package site.hikki.dao;
public interface BookDao { void save(); }
|
BookDaoImpl实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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配置文件:
1
| <bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
|
程序入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext; import site.hikki.dao.BookDao;
public class App { public static void main(String[] args) { 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);
} }
|
从内容输出可以看出来,两次实例化的bookDao和bookDao2都是同一个对象,也可以说是调用的对象都是同一个地址块的对象。
如果我想每次创建的对象都不一样?或者说是每次创建的对象都重新分配一个地址块该如何处理?这就是非单例模式,每次调用的对象都不是同一个。
这时候我们需要使用到实力工厂实例化了,我们之前使用的都是构造方式实例化bean的,每次实例化bean都会调用一次BookDaoImpl实现类的构造方法。
非单例模式
新建一个工厂Bean,isSingleton()方法默认是单例模式,除非你需要设置非单例模式,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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; }
@Override public boolean isSingleton() { return false; } }
|
spring配置:
1
| <bean id="bookDao4" class="site.hikki.factory.BookDaoFactoryBean"/>
|
程序入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext; import site.hikki.dao.BookDao;
public class App { public static void main(String[] args) { 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);
} }
|
从打印内容可以看到,两个对象的打印的地址不一样,说说两次创建的对象不是同一个。这就是设置非单例的过程了。
依赖注入
我们平时向一个类中传递数据有有几种方式?
在spring中,依赖注入也是用于传递数据的,而依赖注入也有两种方法传递数据:
- setter注入
- 简单类型(int,String)
- 引用类型(实体类)
- 构造器注入
- 简单类型(int,String)
- 引用类型(实体类)
但我们一般都是使用Setter注入较多,下面实验只介绍setter注入方式
项目结构
项目和上面使用的也基本一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ├─java │ └─site │ └─hikki │ ├─dao │ │ │ BookDao.java │ │ │ │ │ └─impl │ │ BookDaoImpl.java │ ├─service │ │ │ BookService.java │ │ │ │ │ └─impl │ │ BookServiceImpl.java │ │ App.java └─resources │ applicationContext.xml
|
setter注入
BookServiceImpl实现类:
我们在BookServiceImpl实现类定义几个对象,引用类型、int、String类型,定义三个对象,并且给他们set属性。
为了验证是否拿到数据,我们在该类下的save方法输出值的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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); }
}
|
构造器注入的关键在于构造器参数的获取,如下:
1 2 3 4 5
| 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对象呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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>
<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()方法。观察打印结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package site.hikki.service;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save(); } }
|
从打印结果可以看到,我们在配置文件定义的值可以成功被接收,并且BookDao也被实例化调用了。
1 2 3 4 5 6 7
| <bean id="bookService" class="site.hikki.service.impl.BookServiceImpl">
<property name="name" value="小码同学"/> <property name="age" value="20"/> </bean>
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>
|
留下个小问题?你试试将bookDao的属性注释掉再重新运行试试,去了解一下为什么会这样
原因解释
当<property name="bookDao" ref="bookDao"/>
没有传递BookDao
的参数时,在BookServiceImpl
的实现类下的save
方法中,存在bookDao.save();
,它表示准备调用BookDao接口的save()方法,但是调用时,bean配置没有给他实例化啊,为什么没有实例化?
原因刚刚把<property name="bookDao" ref="bookDao"/>
注释掉了,这段代码的作用就是给他实例化对象。
项目下载地址:https://rookie1679.lanzoue.com/iIhT10jzljdi
依赖自动装配
使用方法
在上一节,我们讲到依赖注入的问题,我们需要配置一个引用类型的参数才能在bookService调用BookDao的方法,但每次都要配置,感觉好麻烦啊,有没有什么方法可以让他帮我自己识别,自动帮我装配传参过去呢?
1 2 3 4 5
| <bean id="bookService" class="site.hikki.service.impl.BookServiceImpl"> <property name="bookDao" ref="bookDao"/> </bean>
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>
|
答案是:有的,spring有有一个自动装配的方法,添加autowire
属性,该属性有五个值:
- byType(根据类型自动装配)
- byName(根据名称自动装配)
- constructor(使用构造方法自动装配)
- default(使用默认)
- no(不使用自动装配)
这个根据类型和名称是BookServiceImpl
下的setBookDao
方法的那个名称,比如该方法的名称就是bookDao
根据该名称自动装配,根据该类型自动装配。
1 2 3
| <bean id="bookService" class="site.hikki.service.impl.BookServiceImpl" autowire="byType"/>
<bean id="bookDao" class="site.hikki.dao.impl.BookDaoImpl"/>
|
依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单的类型进行操作。
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用。
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名和配置耦合,不推荐使用。
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效。
集合注入
集合注入用的不多,一般用于一些项目的初始化,但也很少用到,用的最多
DataService方法实现
我们在DataService定义几个属性,类型都是集合。
然后在spring配置文件添加初始化的值,进行注入。
定义属性后,记得给他们设置set方法,并且再定义一个save方法打印值的输出,用于检验值是否成功注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?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>
|
程序入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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(); } }
|
由结果可以看到集合已经注入成功。
项目下载:https://rookie1679.lanzoue.com/iJ1xF0k0txra
spring的基础已经学习了差不多啦,继续加油!!!