总目录
(一)spring框架概述以及spring中基于XML的IOC配置
(二)spring中基于注解的IOC和IOC案例
(三)spring中的aop和基于XML以及注解的AOP配置
(四)spring的JdbcTemplate以及Spring事务控制
1 spring的概述
- spring是什么东西
- spring的两大核心
- spring的发展历程和优势
- spring的体系结构
2 程序的耦合及解耦
- 曾经案例中问题
- 工厂模式解耦
3 IOC概念和spring中的IOC
- spring中基于XML的IOC环境搭建
4 依赖注入(Dependency Injection)
1 spring概述
IOC 反转控制 AOP面向切面编程 两大核心
spring 优势
- 方便解耦,简化开发
- AOP编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低JavaEE API的使用难度
- 学习spring源码(以后)
spring的体系结构
2 程序的耦合及解耦
编写jdbc的工程代码用于分析程序的耦合
public class JdbcDemo01 {
public static void main(String[] args) throws Exception {
//1注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2获取连接
Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/spring","root","xgh961120");
//3获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4执行SQL,得到结果集
ResultSet rs =pstm.executeQuery();
//5 遍历结果集
while (rs.next()){
System.out.println(rs.getString("name"));
}
//6释放资源
rs.close();
pstm.close();
conn.close();
}
}
程序的耦合
耦合:简单的认为时程序间的依赖关系
包括
- 类之间的依赖
- 方法之间的依赖
解耦:降低程序间的依赖关系
实际开发中应该做到,编译器不依赖,运行时才依赖
比如
注册驱动编译器有依赖,如果去除mysql的驱动,则在编译期间,就报错,因为依赖具体的驱动类
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
解耦的方法:
只依赖一个字符串
Class.forName(“com.mysql.jdbc.Driver”);
出去mysql的驱动,在编译的时候可以通过,到运行的时候才报异常
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字
第二步:通过读取配置文件来获取要创建的对象全限定类名
曾经代码中的问题
对于一个MVC三层架构,原来的思路在调用过程中也犯了跟JDBC耦合同样的错误new了下一层的对象
持久层
/*
账户的持久层接口
*/
public interface IAcountDao {
/**
* 模拟保存接口
*/
void saveAccount();
}
账户的持久层实现类
public class AccountDaoImpl implements IAcountDao{
public void saveAccount() {
System.out.println("保存了账户");
}
}
业务层调用调用持久层就犯了错误
public interface IAcountService{
/*
模拟保存账户
*/
void saveAccount();
}
账户的业务层实现类,业务层调用持久层
注意:业务层在调用持久层的时候犯了同数据库一样的问题,new了持久层的具体实现类
public class AccountServiceImpl implements IAcountService {
private IAcountDao accountDao=new AccountDaoImpl();
public void saveAccount() {
accountDao.saveAccount();
}
}
表现层也同样耦合
- 模拟一个表现层,用于调用业务层,在实际开发中这应该是一个servlet
- 表现层在调用业务层的时候也犯了同样的错误new了业务层的具体实现类
public class Client {
public static void main(String[] args) {
IAcountService as = new AccountServiceImpl();
as.saveAccount();
}
}
只要AccountsDaoimpl,AccountServiceImpl实现类去掉,整个工程在编译阶段就报错
怎么解除依赖关系呢?
使用工厂
一个创建Bean对象的工厂
Bean在计算机英语里,有可重用组件的含义(软件开发中,一个项目也有很多部分组成)
JavaBean:用Java语言编写的可重用组件。
javabean >实体类
一个创建Bean对象的工厂,它就是创建我们的service和dao的对象。
如何创建?
1. 需要一个配置文件来配置我们的service和dao
配置的内容:唯一标志(用什么来去全限定类名)=全限定类名(Key=Value)
2. 通过读取配置文件的配置内容,利用反射创建bean对象
配置文件可以是XML,可以是properties
创建配置文件,存放唯一标志和全限定类名
bean.properties
accountService =com.tju.service.impl.AccountServiceImpl
accountDao = com.tju.dao.impl.AccountDaoImpl
一个创建Bean对象的工厂类
利用从配置文件读取全限定类名创建我们的service和dao对象的。
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败");
}
}
/**
* 根据Bean名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean=null;
try {
String beanPath = props.getProperty(beanName);
//用反射的方式创建对象,需要从创建的类里得到全限定类名
bean=Class.forName(beanPath).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
首先改造表现层中的耦合语句
public class Client {
public static void main(String[] args) {
//原来的耦合语句IAcountService as = new AccountServiceImpl();
//解耦的方法
IAcountService as = (IAcountService)BeanFactory.getBean("accountService");
as.saveAccount();
}
}
改造业务层的耦合结果
public class AccountServiceImpl implements IAcountService {
//原来的耦合语句 private IAcountDao accountDao=new AccountDaoImpl();
//解耦
private IAcountDao accountDao= (IAcountDao)BeanFactory.getBean("accountDao");
public void saveAccount() {
accountDao.saveAccount();
}
}
这样在没有实体类AccountServiceImpl,AccountDaoImpl的话不会编译报错,只会运行时发出异常,一定程度解耦
分析工厂模式中的问题并改造
问题:
在client中进行改造:
public class Client {
public static void main(String[] args) {
for(int i=0;i<5;i++){
IAcountService as = (IAcountService)BeanFactory.getBean("accountService");
System.out.println(as);
}
}
}
结果:
com.tju.service.impl.AccountServiceImpl@880ec60
com.tju.service.impl.AccountServiceImpl@3f3afe78
com.tju.service.impl.AccountServiceImpl@7f63425a
com.tju.service.impl.AccountServiceImpl@36d64342
com.tju.service.impl.AccountServiceImpl@39ba5a14
证明是多例的
在刚才的基础上
Client.java
public class Client {
public static void main(String[] args) {
for(int i=0;i<5;i++){
IAcountService as = (IAcountService)BeanFactory.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
}
AccountServiceImpl.java
public class AccountServiceImpl implements IAcountService {
private IAcountDao accountDao= (IAcountDao)BeanFactory.getBean("accountDao");
private int i=1;
public void saveAccount() {
accountDao.saveAccount();
System.out.println(i);
i++;
}
}
执行结果
com.tju.service.impl.AccountServiceImpl@880ec60
保存了账户
1
com.tju.service.impl.AccountServiceImpl@3f3afe78
保存了账户
1
com.tju.service.impl.AccountServiceImpl@7f63425a
保存了账户
1
com.tju.service.impl.AccountServiceImpl@36d64342
保存了账户
1
com.tju.service.impl.AccountServiceImpl@39ba5a14
保存了账户
1
每个对象都是单独的示例,Service的实例对象都是新创建的,所以一直重新初始化i,i始终为1,所以是多例模式
多例多项被创建多次,执行效率低
如果创建对象是单例的,对象只被创建一次,类中的成员也就只会初始化一次,单例对象有线程问题
在service层和dao层没有在单例对象可以改变的类成员
我们还是把变量定义到方法中来
每次方法都会初始化,所以得到的i还是1
public class AccountServiceImpl implements IAcountService {
private IAcountDao accountDao= (IAcountDao)BeanFactory.getBean("accountDao");
public void saveAccount() {
int i=1;
accountDao.saveAccount();
System.out.println(i);
i++;
}
}
在BeanFactory类中
bean=Class.forName(beanPath).newInstance(); //默认每次都会调用默认构造函数创建对象
调整的角度就是不需要每次都创建对象,只需要创建对象一次
只能用一次newInstance()创建对象
创建好立即存起来,用什么存?
用map存
重新写类BeanFactory
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象,我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
//静态代码块只在类加载的时候执行一次
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化我们的容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的key
Enumeration keys = props.keys();
//遍历这个枚举
while(keys.hasMoreElements()){
//取出每个Key
String key =keys.nextElement().toString();
//根据key获取value
String beanPath =props.getProperty(key);
//反射创建对象
Object value =Class.forName(beanPath).newInstance();
//把key和value存入容器之中
beans.put(key,value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败");
}
}
/**
* 对原来的getBean(String beanName)方法进行改造
* 根据bean的名称获取单例对象
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
打印结果
com.tju.service.impl.AccountServiceImpl@30c7da1e
com.tju.dao.impl.AccountDaoImpl@5b464ce8
保存了账户
1
com.tju.service.impl.AccountServiceImpl@30c7da1e
com.tju.dao.impl.AccountDaoImpl@5b464ce8
保存了账户
2
com.tju.service.impl.AccountServiceImpl@30c7da1e
com.tju.dao.impl.AccountDaoImpl@5b464ce8
保存了账户
3
com.tju.service.impl.AccountServiceImpl@30c7da1e
com.tju.dao.impl.AccountDaoImpl@5b464ce8
保存了账户
4
com.tju.service.impl.AccountServiceImpl@30c7da1e
com.tju.dao.impl.AccountDaoImpl@5b464ce8
保存了账户
5
此时变成单例模式,同一个类反复操作类成员变量,不需要反复创建对象
2 IOC的概念和作用
两种创建对象的方式
主动情况下,直接new,和资源联系,明显依赖
依赖工厂,不依赖资源,这就是IOC
为什么叫控制反转
原来在Service层
我想new就new,主动权在我
private IAcountDao accountDao=new AccountDaoImpl();
现在必须依赖于工厂,创建对象必须给全限定类名,控制权在工厂,叫控制反转
private IAcountDao accountDao=new AccountDaoImpl();
控制反转:
把创建对象的权利交给框架,是框架的重要特征。它包括依赖注入和依赖查找
spring的IOC解决程序耦合
spring基于XML的IOC环境搭建和入门
spring的jar包
整个工程的依赖
在配置文件中从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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 把对象的创建交给spring来管理 id是唯一标志,class是全限定类名-->
<bean id="accountService" class="com.tju.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.tju.dao.impl.AccountDaoImpl"></bean>
</beans>
接下来获取spring创建核心容器的map,并根据id获取对象
spring的类视图
//1.获取核心容器对象,配置文件在根路径下直接写文件名字
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象,两种方式一种是直接获得类的对象自己强转,一种是传字节码已经强转
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
结果
com.tju.service.impl.AccountServiceImpl@1dac5ef
com.tju.dao.impl.AccountDaoImpl@5c90e579
我们要做的就是配置文件和获取核心容器对象
ApplicationContext的三个实现类
ApplicationContext的三个常用实现类:
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");
- AnnotationConfigApplicationContext:它是用于读取注解创建容器的。
BeanFactory和ApplicationContext的区别
核心容器的两个接口引发出的问题:
ApplicationContext: 单例对象适用 实际开发种采用此接口
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);
一执行完这句话,配置文件被读完,同时Service和Dao的实现类被创建
BeanFactory: 多例对象使用
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
//--------BeanFactory----------
//从类路径下读取配置文件
Resource resource = new ClassPathResource("bean.xml");
//根据配置文件创造工厂
BeanFactory factory = new XmlBeanFactory(resource);
//什么时候用,什么时候创建对象,只要在调用的时候,Servic实现类或者Dao的实现类才会被创建
IAccountService as1 = (IAccountService)factory.getBean("accountService");
System.out.println(as1);
spring中的bean细节之三种创建Bean对象的方式
创建Bean的三种方式
第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.tju.service.impl.AccountServiceImpl"></bean>
第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
- 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
这样的话取得是工厂类对象
<bean id="instanceFactory" class="com.tju.factory.InstanceFactory"></bean>
我们创建实例化对象accountService,需要调用工厂的方法
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
- 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
<bean id="accountService" class="com.tju.factory.StaticFactory" factory-method="getAccountService"></bean>
spring中的bean中的细节之作用范围
spring的默认对象是单例的
<bean id="accountService" class="com.tju.service.impl.AccountServiceImpl" scope="prototype"></bean>
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as1 = (IAccountService)ac.getBean("accountService");
IAccountService as2 = (IAccountService)ac.getBean("accountService");
System.out.println(as1==as2);
}
结果是True Bean对象只创建了一次,两个对象是同一个
bean的作用范围调整
bean标签的scope属性:
- 作用:用于指定bean的作用范围
- 取值: 常用的就是单例的和多例的
- singleton:单例的(默认值)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
全局Session图
更改成多例:
<bean id="accountService" class="com.tju.service.impl.AccountServiceImpl" scope="prototype"></bean>
结果False,Bean对象多次创建,两个对象不同
Spring bean中的生命周期
bean对象的生命周期
- 单例对象
- 出生:当容器创建时对象出生,立即
- 活着:只要容器还在,对象一直活着
- 死亡:容器销毁,对象消亡
- 总结:单例对象的生命周期和容器相同
- 多例对象
- 出生:当我们使用对象时spring框架为我们创建,延迟
- 活着:对象只要是在使用过程中就一直活着。
- 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
单例对象:
<bean id="accountService" class="com.tju.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy"></bean>
测试单例对象
public static void main(String[] args) {
//1.获取核心容器对象
// 不使用这种多态形式,无法调用子类的特有关闭方法 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//解析完配置文件就立马创建bean对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
//3 手动关闭容器,荣去关闭的同时,bean对象销毁
ac.close();
}
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl(){
System.out.println("对象创建了");
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。");
}
public void init(){
System.out.println("对象初始化了。。。");
}
public void destroy(){
System.out.println("对象销毁了。。。");
}
}
结果(生命周期和容器相同)
对象初始化了。。。
service中的saveAccount方法执行了。。。
对象销毁了
多例对象
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
4 spring的依赖注入
spring中的依赖注入
依赖注入:
Dependency Injection
IOC的作用:
降低程序间的耦合(依赖关系)
依赖关系的管理:
以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:
就称之为依赖注入。
依赖注入:
能注入的数据:有三类
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
注入的方式:有三种
- 第一种:使用构造函数提供
- 第二种:使用set方法提供
- 第三种:使用注解提供
第一种:使用构造函数注入
构造函数注入:
- 使用的标签:constructor-arg
- 标签出现的位置:bean标签的内部
- 标签中的属性
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
- name:用于指定给构造函数中指定名称的参数赋值 常用的
以上三个用于指定给构造函数中哪个参数赋值 - value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功,实现类对象就一个三个参数的构造方法,必须赋值
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
bean.xml
<bean id="accountService" class="com.tju.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="泰斯特"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
实现类对象
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name,Integer age,Date birthday){
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
}
}
测试获取bean对象
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
结果
service中的saveAccount方法执行了。。。test,18,Apr 26 15:53:14 cst 2019
第二种set方法注入 更常用的方式
- 涉及的标签:property
- 出现的位置:bean标签的内部
- 标签的属性
- name:用于指定注入时所调用的set方法名称***
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
bean.xml
<bean id="accountService2" class="com.tju.service.impl.AccountServiceImpl2">
<property name="name" value="TEST" ></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
实现类对象
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
}
测试bean对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService2");
as.saveAccount();
结果
service中的saveAccount方法执行了。。。test,18,Apr 26 15:53:14 cst 2019
关于复杂类型注入,注入集合数据
使用第二种种set方法注入(property使用类的set方法注入)
bean.xml
复杂类型的注入/集合类型的注入
记两组就行
- 用于给List结构集合注入的标签:
list array set - 用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>
实现类
public class AccountServiceImpl3 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 void saveAccount(){
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
测试类
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = (IAccountService)ac.getBean("accountService3");
as.saveAccount();
结果
[AAA, BBB, CCC]
[AAA, BBB, CCC]
[AAA, BBB, CCC]
{testD=ddd, testC=ccc}
{testB=BBB, testA=aaa}