01 Spring核心容器配置
1 Spring框架的基础
1-1基本组成
1 spring框架的组成,哪些比较重要?
| Core | IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP. |
|---|---|
| Testing | Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient. |
| Data Access | Transactions, DAO Support, JDBC, R2DBC, O/R Mapping, XML Marshalling. |
| Web Servlet | Spring MVC, WebSocket, SockJS, STOMP Messaging. |
| Web Reactive | Spring WebFlux, WebClient, WebSocket, RSocket. |
| Integration | Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching. |
| Languages | Kotlin, Groovy, Dynamic Languages. |
主要包括:核心容器模块,数据访问模块,web模型,测试模块。
SpringBoot和Spring到底有没有本质的不同?
Spring Boot基本上是Spring框架的扩展,它消除了设置Spring应用程序所需的XML配置,为更快,更高效的开发生态系统铺平了道路。
Spring Cloud基于Spring Boot,为微服务体系开发中的架构问题,提供了一整套的解决方案——服务注册与发现,服务消费,服务保护与熔断,网关,分布式调用追踪,分布式配置管理等。
Spring Boot使用了约定大于配置(PS:很多博客写的是默认大于配置,严格来说,约定更精确)的理念,集成了快速开发的spring多个插件,同时自动过滤不需要配置的多余的插件,简化了项目的开发配置流程,一定程度上取消xml配置,是一套快速配置开发的脚手架,能快速开发单个微服务;
Spring Boot中的一些特征:
创建独立的Spring应用。
嵌入式Tomcat、Jetty、 Undertow容器(无需部署war文件)。
提供的starters 简化构建配置
尽可能自动配置spring应用。
提供生产指标,例如指标、健壮检查和外部化配置
完全没有代码生成和XML配置要求
2 核心容器的使用与配置
1 ApplicationContext接口与BeanFactory接口的关系

关系:BeanFactory是spring核心容器的顶层接口,ApplicationContext接口实现了BeanFactory接口。
特点:
1)BeanFactory接口的实现类:构建核心容器时,采用延迟加载(懒加载)的方式创建对象,也就是说,代码中根据id(类的名称)获取对象时才会在容器中创建对象并注入。
2)ApplicationContext接口的实现类:构建核心容器时,创建对象的策略时采用立即加载的方式,一旦读取完配置文件就马上创建配置文件中的对象即spring框架已启动就创建好相关对象。
2 对于spring中的bean class, 单例与多例class适合哪种加载方式?
| bean class特点 | Ioc容器的加载方式 | |
|---|---|---|
| 单例(只需要创建一次) | 立即加载(立即调用类的构造函数) | |
| 多例 | 延迟加载(没有立即调用类的构造函数) |
3 spring的bean对象创建的三种方式,对象的作用范围以及对象的生命周期?
3-1 xml中创建bean对象的三种方式
| spring核心容器创建bean对象的方式 | 配置方法 | 使用场景 |
|---|---|---|
| 使用对象的默认构造函数(无参构造函数)创建对象 | 在xml配置文件中只设置id以及class属性 | 自定义bean对象并且使用默认构造函数 |
| 采用工厂类实例对象的普通方法创建bean对象 | 需要注入工厂类实例对象,然后配置方法名称将目标实例对象注入 | 其他jar中工厂类对象,使用普通方法创建目标对象 |
| 采用工厂类的静态方法创建bean对象 | 其他jar中工厂类对象,使用静态方法创建目标对象 |
方式1:
1)使用对象的默认构造函数创建对象,在xml配置文件中只设置id以及class属性,并且没有其他属性与标签就是采用默认构造函数对象
注意点:这个要求对象文件中必须有默认构造函数否则会报错。
xml配置
<bean id="accountService" class="springlearn.service.impl.AccountService"></bean>
bean对象定义
public class AccountService implements IAccountService {
public AccountService(String s) { // 没有默认构造函数
System.out.println("AccountService的构造方法执行");
}
public void saveAccount(){
System.out.println("AccountService的saveAccount方法执行");
}
}
错误信息
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'accountService' defined in class path resource [bean.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [springlearn.service.impl.AccountService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: springlearn.service.impl.AccountService.<init>()
方式2:采用工厂类实例对象的普通方法创建bean对象
xml配置
<!--创建bean对象的方式2:采用工厂类的普通方法创建bean对象
step1:将工厂实例对象放入容器
step2:利用工厂实例对象的创建对象的普通方法创建对象放入容器
-->
<bean id="InstanceFactory" class="springlearn.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="InstanceFactory" factory-method="getAccountService"></bean>
工厂对象定义
package springlearn.factory;
import springlearn.service.IAccountService;
import springlearn.service.impl.AccountService;
/*模拟一个工厂类,可能存在于jar包中,,该工厂方法提供普通方法创建目标对象*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountService();
}
}
方式3:采用工厂类的静态方法创建bean对象
<!--创建bean对象的方式3:采用工厂类的静态方法创建bean对象-->
<bean id="accountService" class="springlearn.factory.StaticFactory" factory-method="getAccountService"></bean>
工厂对象定义
package springlearn.factory;
import springlearn.service.IAccountService;
import springlearn.service.impl.AccountService;
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountService();
}
}
3-2 bean的5种范围(scope)
| 作用范围(scope) | 说明 | 备注 |
|---|---|---|
| singleton | Ioc容器中仅有1个实例对象,程序中共享这一个实例对象 | 注入对象时都是同一实例对象 |
| prototype | 每次从容器中获取bean对象时,都会新创建一个实例对象 | 注入对象时是不同的实例对象 |
| request | 每次http请求中都会有个bean | web环境下使用 |
| session | 同一session请求中共享一个bean | web环境下使用 |
| globalsession | 当不是集群环境就是session | web环境下使用 |
singleton : [ˈsɪŋgltən] 一个,独身,单独;
prototype: [ˈprəʊtətaɪp] 原型,雏形,蓝本;
注意点:
- bean对象的作用范围可以通过scope设置(默认是singleton)
<bean id="accountService" class="springlearn.factory.StaticFactory" factory-method="getAccountService" scope="singleton"></bean>
-
singleton vs prototype 和 request vs session 可以对比看。
-
request、session和global session三种作用域仅在基于web的应用中使用(web应用框架比如SpringMVC),这里的scope类似于生命周期
比较单例对象的两次调用的地址
<bean id="accountService" class="springlearn.factory.StaticFactory" factory-method="getAccountService" scope="singleton"></bean>
调用代码
package springlearn.ui;
import springlearn.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Client {
public static void main(String[] args) {
//1.通过类路径确定xml文件位置(IDEA中xml文件要放到resources目录下),这里获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2 2次获取bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountService bs = (IAccountService)ac.getBean("accountService");
System.out.println(as == bs); // true
}
}
- 单例对象在spring容器启动时创建
3-3 bean对象的生命周期
| bean对象类型 | 生命周期 | 创建 | 运行 | 消失(回收) | Ioc容器的加载方式 |
|---|---|---|---|---|---|
| 单例对象(Singleton) | 对象的生命周期与容器绑定 | 容器创建时,对象也创建 | 容器对象存在则该对象存在 | 容器关闭 | 立即加载 |
| 多例对象(prototype) | 对象的生命周期由垃圾管理机制确定 | 从容器中获取对象时创建 | 只要使用就一直活着 | 垃圾回收器判断该对象无用,则回收 | 延迟加载(使用时加载) |
注意:多例对象的生命周期没有与容器类的生命周期绑定
3-3-1 代码演示单例与多例的生命周期
1)bean对象的定义
public class AccountService implements IAccountService {
public AccountService() {
System.out.println("AccountService的构造方法执行");
}
public void saveAccount(){
System.out.println("AccountService的saveAccount方法执行");
}
public void init(){
System.out.println("对象初始化");
}
public void destroy(){
System.out.println("对象销毁");
}
}
2) xml文件配置单例与多例并配置bean对象的init与destroy方法
配置1:单例
<bean id="accountService" class="springlearn.service.impl.AccountService" scope="singleton" init-method="init" destroy-method="destroy"></bean>
配置2:多例
<bean id="accountService" class="springlearn.service.impl.AccountService" scope="prototype" init-method="init" destroy-method="destroy"></bean>
3)测试代码
package springlearn.ui;
import springlearn.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
// 初始化应用上下文
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = (IAccountService)ac.getBean("accountService");
// 关闭应用上下文
ac.close(); // Close this application context, destroying all beans in its bean factory.
}
}
配置1执行结果(单例)
AccountService的构造方法执行
对象初始化
org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4ccabbaa: root of context hierarchy
对象销毁
配置2执行结果(多例)
AccountService的构造方法执行
对象初始化
org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4ccabbaa: root of context hierarchy
总结:单例对象随着容器的销毁而消失,多例对象在application context关闭后,仍然存在。
4 spring的依赖注入
spring中bean对象的管理是基于Ioc的思想,在这一思想的指导下,通过依赖注入机制实现。
依赖注入定义:对象依赖关系的维护就是依赖注入
4-1 注入的数据类型
| 注入数据类型 | |
|---|---|
| 基本数据类型和String | |
| 其他bean对象(在xml文件或者通过注解配置过的类设置) | |
| 复杂类型/集合类型 |
4-2 数据注入到容器中实例对象的方式
| 依赖注入的方式 | 实现方式 | 特点 |
|---|---|---|
| 使用构造函数注入 | 构造函数中参数是基本的数据类型与String类型,则需要在bean标签的value属性中赋值,是对象类型则需要通过xm类文件将该对象也放入到容器中 | 必须配置所有的数据 |
| 使用set方法提供 | 通过class的set方法注入数据 | 可以指定配置数据的数目,更为常用。 |
方式1:构造函数注入实例
xml文件配置
<!--通过构造函数注入数据
constructor-arg标签的属性:
type:用于指定要注入数据的数据类型,该数据类型是构造函数的某个参数的类型,index:参数的索引位置,从0开始,name:参数的名称(通常用这个就行了)
上面三个标签属性都是用于指定参数
value:用于基本数据类型和String类型赋值,ref:指定其他在容器中bean
优势:提供了注入数据的方式
劣势:必须为每个参数赋值
-->
<!--配置日期对象放入到容器中-->
<bean id="now" class="java.util.Date"></bean>
<bean id="accountService" class="springlearn.service.impl.AccountService">
<!-- 参数是String类型,spring自动将字符转换为Inteter-->
<constructor-arg name="string" value="字符串"></constructor-arg>
<!-- 参数是基本数据类型,spring自动将字符转换为Inteter-->
<constructor-arg name="integer" value="123456"></constructor-arg>
<!-- 参数是其他class类型,需要先将对象给注入到容器中,使用ref引用-->
<constructor-arg name="data" ref="now"></constructor-arg>
</bean>
类的定义
package springlearn.service.impl;
import springlearn.service.IAccountService;
import java.util.Date;
public class AccountService implements IAccountService {
/*定义三个属性*/
private Integer integer;
private String string;
private Date data;
/* 通过构造函数中的参数为类注入数据*/
public AccountService(Integer integer, String string, Date data) {
this.integer = integer;
this.string = string;
this.data = data;
}
public void saveAccount(){
System.out.println("AccountService的saveAccount方法执行");
}
}
方式2:使用set方法注入基本数据与简单的类对象
<!--使用set方法注入数据
涉及的标签: property,出现的位置:bean标签的内部
标签的属性: name: 所调用的set方法名称,value:用于基本数据类型和String类型赋值,ref:指定其他在容器中bean
优势:创建对象时没有默认的限制,可以直接使用默认构造函数,弊端:如果某个field必须有,set方式无法保证这个field的数据一定注入
set方式与采用默认构造函数的方式正好时互换的,实际开发中set方式更为常用
-->
<bean id="accountService2" class="springlearn.service.impl.AccountService1">
<property name="string" value="字符串"></property>
<property name="integer" value="123456"></property>
<property name="data" ref="now"></property>
</bean>
类中必须有set方法
package springlearn.service.impl;
import springlearn.service.IAccountService;
import java.util.Date;
public class AccountService1 implements IAccountService {
/*定义三个属性*/
private Integer integer;
private String string;
private Date data;
public AccountService1() {
}
/*通过set方法将数据注入到bean对象中*/
public void setInteger(Integer integer) {
this.integer = integer;
}
public void setString(String string) {
this.string = string;
}
public void setData(Date data) {
this.data = data;
}
public void saveAccount(){
System.out.println("AccountService的saveAccount方法执行"+integer+string+data);
}
}
使用set方法注入map类以及集合类数据
package springlearn.service.impl;
import springlearn.service.IAccountService;
import java.util.*;
public class AccountService implements IAccountService {
/*定义三个属性*/
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
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;
}
public AccountService() {}
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
xml文件配置
<!--复杂/集合类型数据的注入
用于给List结构注入的集合的标签:list,array,set
用于给map结构注入的集合的标签:map,props
结构相同,标签可以互换,不需要严格对应,即list,array,set实际使用时效果相同
-->
<bean id="accountService" class="springlearn.service.impl.AccountService">
<property name="myStrs">
<list>
<value>aa</value>
<value>bb</value>
</list>
</property>
<property name="myMap">
<map>
<entry key="k1" value="v1"></entry>
<entry key="k2" value="v2"></entry>
</map>
</property>
</bean>
总结:spring的依赖注入的方式?
Spring依赖注入三种常用方式:
- 构造器注入:利用构造方法的参数注入依赖
- Setter注入:调用Setter的方法注入依赖
- 字段注入:在字段上使用@Autowired/Resource注解
5 spring中Ioc容器的注解配置的作用分类
5-1 注解配置概述
bean class的xml配置的常用的标签和属性
<bean id="accountService" class="springlearn.service.impl.AccountService" scope="prototype" init-method="" destroy-method="">
<property name="myMap" value="121" | ref="now"></property>
</bean>
| 功能分类 | xml文件 | 相关注解 | 备注 |
|---|---|---|---|
| 容器对象的创建 | bean标签 | @Component与@Controller,@Service,@Repository | |
| 依赖注入 | property标签 | @Autowired,@Qualifier, @Resource,@Value, | |
| 用于配置对象的作用范围(单例,多例) | scope属性 | @Scope | |
| 用于配置对象的生命周期 | init-method和destroy-method属性 | @PreDestory,@PostContrust |
5-2 容器对象创建相关注解
作用:将被注解的class通过反射创建并放入到Ioc容器中。
- 通过value属性确定bean class的Id
注意点:使用注解进行配置时需要在xml文件中指定注解扫描的类路径
使用@Component注解的实例:
- 被注解的类
package springlearn.service.impl;
import org.springframework.stereotype.Component;
import springlearn.service.IAccountService;
/*value属性值就是bean class在Ioc容器中的Id*/
@Component(value="testBean")
public class AccountService implements IAccountService {
public AccountService() {
System.out.println("AccountService的构造方法执行");
}
public void saveAccount(){
System.out.println("AccountService的saveAccount方法执行");
}
}
- 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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean中,而是在名称为context的空间中-->
<context:component-scan base-package="springlearn"></context:component-scan>
</beans>
- 获取容器中的类
package springlearn.ui;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import springlearn.service.IAccountService;
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService bs = (IAccountService)ac.getBean("testBean");
bs.saveAccount();
}
}
问题:@Component与@Controller,@Service,@Repository的关系?
相同点:本质上四个注解的作用相同,都是将bean class交给Ioc容器进行管理。
使用不同点:在开发中要遵循分层思想,不同的层(控制层,服务层,数据层)要使用对应的注解,不属于MVC的层的bean class则使用@Component注解。
5-3 依赖注入相关的注解
相关注解:@AutoWired与@Qualifier与@Resource与@Value
@Autowired作用:从Ioc容器中查找与变量类型匹配的唯一的bean class并注入。相比较xml的property标签不需要set方法。
问题:使用接口IAccountDao定义的变量可以匹配实现该接口的所有类,如果Ioc容器中有多个bean class与变量类型匹配会出现什么问题?
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testBean': Unsatisfied dependency expressed through field 'AccountDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'springlearn.dao.IAccountDao' available: expected single matching bean but found 2: accountDao,accountDao1
针对上述问题,spring容器会无法实现成员变量的注入,上面错误信息可以看到springlearn.dao.IAccountDao接口在Ioc容器中匹配到accountDao,accountDao1两个bean class。
总结: @Autowired中注解的变量,如果变量类型能够在Ioc中容器中匹配到多个bean class,那么无法注入,会报错。
使用实例
package springlearn.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import springlearn.dao.IAccountDao;
import springlearn.service.IAccountService;
/*value属性就是bean class在容器中的id*/
@Component(value="testBean")
public class AccountService implements IAccountService {
// 使用@Autowired为注入当前bean class依赖的 bean class
@Autowired
IAccountDao AccountDao; // 在Ioc容器中寻找与接口类型匹配的bean class对象。
public AccountService() {
System.out.println("AccountService的构造方法执行");
}
public void saveAccount(){
// 调用实体层bean class的方法
AccountDao.saveAccount();
// System.out.println("AccountService的saveAccount方法执行");
}
}
@Qualifier注解:按照类型注入的基础上可以指定注入的bean class的id
属性:valaue用于指定注入的bean的id
使用注意点:
- 给类成员注入的时候不能单独使用,需要配合@Autowird使用
- 给方法参数注入的时候可以单独使用
可以通过@Qualifier配合@Autowired使用从而解决@Autowired的问题
package springlearn.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import springlearn.dao.IAccountDao;
import springlearn.service.IAccountService;
@Component(value="testBean")
public class AccountService implements IAccountService {
// 使用@Autowired注入属性数据到bean class
// IAccountDao如果在容器中匹配到多个bean class,则通过@Qualifier的value的值即bean id确定最终注入的对象
@Autowired
@Qualifier(value = "accountDao")
IAccountDao AccountDao;
public AccountService() {
System.out.println("AccountService的构造方法执行");
}
public void saveAccount(){
AccountDao.saveAccount();
}
}
@Resource作用:通过指定bean的id的实现注入,区别于@Autowird
| 注解名称 | 注入依赖的类型 | 注意点 |
|---|---|---|
| @Autowired | 容器对象 | 属性注入是根据变量类型匹配 |
| @Qualifier | 容器对象 | 属性注入配合@Autowired |
| @Resource | 容器对象 | 属性注入直接通过bean id指定注入对象 |
| @Value | 基本类型和String类型的数据 | 支持EL表达式 |
@Autowired与@Qualifier与@Resource三个注解:
- 只能注入bean类型的数据,而基本类型与String类型无法使用上述注解实现。
@Value注解: 用于注入基本类型和String类型的数据
- 属性:用于指定数据的值,它可以使用spring中EL表达式: ${}
Spring中集合类型的数据只能通过xml配置文件进行注入。无法通过注解配置!!!!!!!!!!。
5-4 作用域与生命周期相关的注解
作用域注解:@Scope
作用:用于指定bean的作用范围,:等同于在<bean>标签中使用scope属性
属性value : 指定范围的取值,同xml中值,常用为singleton, prototype。
生命周期注解:@PreDestory,@PostContrust
@PreDestory
作用:用于指定销毁方法,放在方法上。
@PostContrust
作用:用于指定初始化方法,放在方法上
问题:spring如何找有注解的配置类?
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean中,而是在名称为context的空间中-->
<context:component-scan base-package="springlearn"></context:component-scan>
</beans>
- 需要在配置文件中配置容器创建的时候需要扫描的类路径。
3 Spring的简单案例
3-1 准备工作与pom文件
准备工作
CREATE TABLE `account` (
id int auto_increment,
name varchar(40),
money float,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
POM文件
<?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>springlearn6</groupId>
<artifactId>springlearn6</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
</project>
3-2 各个层次的代码
domain层
package com.springlearn.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getMoney() {
return money;
}
public void setMoney(float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
dao层
- 接口
package com.springlearn.dao;
import com.springlearn.domain.Account;
import java.util.List;
public interface IAccountDao {
List<Account> findAllAccount();
Account findAccountById(Integer accountId);
void saveAccount(Account account);
void updateAccount(Account account);
void deleteAccout(Integer accountId);
}
- 实现类
package com.springlearn.dao.impl;
import com.springlearn.dao.IAccountDao;
import com.springlearn.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
@Repository(value = "accountDao")
public class AccountDao implements IAccountDao {
@Autowired
private QueryRunner queryRunner;
public List<Account> findAllAccount() {
try {
return queryRunner.query("select * from account",new BeanListHandler<Account>(Account.class));
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public Account findAccountById(Integer accountId) {
try {
return queryRunner.query("select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public void saveAccount(Account account) {
try {
queryRunner.update("insert into account(name,money)values(?,?)",
account.getName(),
account.getMoney());
} catch (SQLException e) {
e.printStackTrace();
}
}
public void updateAccount(Account account) {
try {
queryRunner.update("update account set name=?,money=? where id = ?",
account.getName(),
account.getMoney(),
account.getId());
} catch (SQLException e) {
e.printStackTrace();
}
}
public void deleteAccout(Integer accountId) {
try {
queryRunner.update("delete from account where id = ?",accountId);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
service层
- 接口
package com.springlearn.service;
import com.springlearn.domain.Account;
import java.util.List;
public interface IAccountService {
List<Account> findAllAccount();
Account findAccountById(Integer accountId);
void saveAccount(Account account);
void updateAccount(Account account);
void deleteAccout(Integer accountId);
}
- 实现类
package com.springlearn.service.impl;
import com.springlearn.dao.IAccountDao;
import com.springlearn.domain.Account;
import com.springlearn.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service(value = "accountService")
public class AccountService implements IAccountService {
@Autowired
IAccountDao AccountDao;
public List<Account> findAllAccount() {
return AccountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return AccountDao.findAccountById(accountId);
}
public void saveAccount(Account account) {
AccountDao.saveAccount(account);
}
public void updateAccount(Account account) {
AccountDao.updateAccount(account);
}
public void deleteAccout(Integer accountId) {
AccountDao.deleteAccout(accountId);
}
}
3-3 测试类与xml文件配置
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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean中,而是在名称为context的空间中-->
<context:component-scan base-package="com.springlearn"></context:component-scan>
<!-- 数据库的操作对象工作在多线程环境下,为了避免相互干扰,这里采用多例对象,每个新的都是一个新的对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--采用构造函数的方式配置数据库连接参数对象-->
<constructor-arg name="ds" ref="datasource"></constructor-arg>
</bean>
<!-- 为数据库连接对象注入参数-->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springlearn"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
测试类
package springlearn.accountServiceTest;
import com.springlearn.domain.Account;
import com.springlearn.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class AccountTest {
@Test
public void testFindAll(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
List<Account> accounts = as.findAllAccount();
for(Account account:accounts){
System.out.println(account);
}
}
@Test
public void testFindOne(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
System.out.println(as.findAccountById(1));
}
@Test
public void testSave() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
Account account = new Account();
account.setId(10);
account.setMoney(2000);
account.setName("woshishabi");
as.saveAccount(account);
}
@Test
public void testUpdate(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
Account account = new Account();
account.setName("JingJing");
account.setMoney(9999);
as.updateAccount(account);
}
@Test
public void testDelete(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
as.deleteAccout(1);
}
}
3-4 案例中存在的问题
问题1:初始化容器以及从容器中获取实例对象这部分代码过于冗余了。
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
问题2:数据库连接的参数仍然需要通过xml的方式注入到容器中
4 spring中配置的常用注解
4-1 @Configuration,@ComponentScan,@Bean
目标:通过上面的注解配合使用取代上面案例的xml文件的作用
- @Configuration:配置类为AnnotationConfigApplicationContext(SpringConfiguration.class)的参数传入,那么可以不添加这个注解
| 注解 | 作用 | |
|---|---|---|
| @Configuration | 注解的类设置为配置类 | |
| @ComponentScan | 指定类的扫描路径 | |
| @Bean | 注解在方法上,将方法的返回值作为对象放入spring容器中,可以配置数据库连接对象 |
package com.springlearn.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
/*
@Configuration:将该类设置为配置类
注意: 如果该配置类为AnnotationConfigApplicationContext(SpringConfiguration.class)的参数传入,那么可以不添加这个注解!!!
--------------------------------------------------------------------------------------------
@ComponentScan:指定spring在创建Ioc容器要扫描的路径,属性:value(basePackages)用于指定要扫描的类路径
注意:该注解的作用类似于xml文件中<context:component-scan base-package="com.springlearn"></context:component-scan>
---------------------------------------------------------------------------------------------
@Bean:把当前方法的返回值作为bean对象存入到容器中,属性:name用于指定bean在容器中的id,不写的话默认就是方法的名称。
注意:如果注解的方法存在参数,spring会去容器中查找有没有可用的bean对象,查找的方法与@Autowired相似,按照类型匹配的原则进行查找。
配置类扫描的路径,可以取代xml文件中的配置
*/
@Configuration
@ComponentScan(basePackages = {"com.springlearn"})
public class SpringConfiguration{
// 将该方法的返回只作为bean对象存入到容器中
// 数据库连接对象必须采用多例
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
// 将配置类作为返回值返回,配置@Bean使用
@Bean
public DataSource createDataSource() {
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass("com.mysql.jdbc.Driver");
} catch (PropertyVetoException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
ds.setJdbcUrl("jdbc:mysql://localhost:3306/springlearn");
ds.setUser("root");
ds.setPassword("123456");
return ds;
}
}
- 注意@QueryRunner在注解配置中通过@Scope配置为多例
4-2 @Improt
作用:用于导入其他配置类
属性:value用于指定其他配置类的字节码,当使用@Import注解后,有Import注解的类就是父配置类,而导入的是子配置类
主配置类:
package com.springlearn.config;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
@Configuration
@ComponentScan(basePackages = {"com.springlearn"})
@Import(JdbcConfig.class) // 通过@Import注解可以导入定义在其他文件中的配置类
public class SpringConfiguration{
// 将该方法的返回只作为bean对象存入到容器中
// 数据库连接对象必须采用多例
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
}
子配置类:
package com.springlearn.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
// @Configuration
public class JdbcConfig {
@Bean
public DataSource createDataSource() {
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass("com.mysql.jdbc.Driver");
} catch (PropertyVetoException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
ds.setJdbcUrl("jdbc:mysql://localhost:3306/springlearn");
ds.setUser("root");
ds.setPassword("123456");
return ds;
}
}
现有配置类存在的问题?
很明显现有的配置类参数在代码中是写死的,我们通常希望能够在配置文件中统一的配置参数,而不是在代码中配置参数。
4-3 @Properties
目标:从配置文件中读取配置参数,方便维护
主配置类
package com.springlearn.config;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
@Configuration
@ComponentScan(basePackages = {"com.springlearn"})
@Import(JdbcConfig.class) // 通过@Import注解可以导入定义在其他文件中的配置类
@PropertySource("classPath:jdbcConfig.properties")
public class SpringConfiguration{
// 将该方法的返回只作为bean对象存入到容器中
// 数据库连接对象必须采用多例
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
}
子配置类
package com.springlearn.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
//@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource createDataSource() {
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(driver);
} catch (PropertyVetoException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}
}
配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springlearn
jdbc.username=root
jdbc.password=123456
- 在加载其他jar包的类到Ioc容器时可以看到通过纯注解的方式进行配置并没有比xml省事多少
总结:通过Configuration,ComponentScan,Bean和Import注解实现了纯注解的方式配置数据库连接参数,取代了xml,解决了3-4的问题。
4-4 Junit测试时简化容器的初始化过程
Junit测试启动与正常启动的区别:
1)正常启动应用程序流程:main方法
2)junit单元测试中,没有main方法也能执行,原因:junit集成了一个main方法,该方法会判断当前测试类中哪些方法有@Test注解,junit就会有Test注解的方法执行。
3)Junit不会管我们是否采用spring框架,junit不关注测试类是否采用Ioc的方式加载依赖。yu
Junit采用Spring的main方法启动,从而初始化Ioc容器:
1)导入spring-test和junit的jar包
2)使用junit提供的@RunWith注解替换的原本的main方法,替换成Spring提供的。
3)使用@ContextConfiguration告知Spring的运行器,Spring和Ioc创建是基于xml的还是注解的,并且说明位置
属性:1)locations:指定xml的位置,加上classpath的关键字表示在类路径下 2)classes:指定注解类所在位置
实现:借助@RunWith和@ContextConfiguration注解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountTest {
@Autowired
IAccountService as;
@Test
public void testFindAll(){
List<Account> accounts = as.findAllAccount();
for(Account account:accounts){
System.out.println(account);
}
}
@Test
public void testFindOne(){
System.out.println(as.findAccountById(1));
}
@Test
public void testSave() {
Account account = new Account();
account.setId(10);
account.setMoney(2000);
account.setName("woshishabi");
as.saveAccount(account);
}
@Test
public void testUpdate(){
Account account = new Account();
account.setName("JingJing");
account.setMoney(9999);
as.updateAccount(account);
}
@Test
public void testDelete(){
as.deleteAccout(1);
}
}
总结:通过替换main方法成功的去除了测试过程中的冗余代码,解决了3-4中的问题1。
7 Spring如何解决循环依赖问题?

1)Spring核心容器,依赖注入的概念。 2)Spring的bean对象的单例与多例以及对应的生命周期 3)Spring的核心基于xml的配置方法 4)Spring核心容器基于注解的方式的配置
浙公网安备 33010602011771号