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注解

不推荐使用@Autowired进行Field注入的原因

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,那么无法注入,会报错

不推荐使用@Autowired进行Field注入的原因


使用实例

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配置文件进行注入。无法通过注解配置!!!!!!!!!!

springboot 多类型数据的注入

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如何解决循环依赖问题?

参考资料

01 Spring中bean的作用域与生命周期

02 Spring基础

03 不推荐使用@Autowired进行Field注入的原因

04 Spring整理比较好的笔记

05 逐行解读Spring(五)- 没人比我更懂循环依赖!

06 bean对象实现Serializable接口的必要性

posted @ 2021-06-25 09:23  狗星  阅读(256)  评论(0)    收藏  举报
/* 返回顶部代码 */ TOP