Spring之依赖注入与 SPEL
一、控制反转与依赖注入
IOC(Inversion of Control) 控制反转
首先想说说 IOC(Inversion of Control)控制反转。这是 spring 的核心,贯穿始终。所谓 IOC,对于 spring 框架来说,就是由 spring 来负责控制对象的生命周期和对象间的关系。程序开发中,在一个对象中如果要使用另外的对象,就必须得到它常见的两种方式是自己 new 一个该对象,或者从 JNDI(Java 命名与目录接口(Java Naming and Directory Interface)中查询一个,使用完之后还要将对象销毁(比如 Connection 等),对象始终会和其他的接口或类藕合起来。
Spring 所倡导的开发方式是这样,所有的类都会在 spring 容器中登记,告诉 spring 该类是做什么用,需要依赖其他什么类,然后 spring 会在系统运行到适当的时候,把需要的类对象主动注入到标记的位置,同时也把该类交给其他需要的地方。所有的类的创建、销毁都由 spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 spring 控制,所以这叫控制反转。
DI(Dependency Injection) 依赖注入
IOC 的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过 DI(Dependency Injection,依赖注入)来实现的。比如对象A 需要操作数据库,以前我们总是要在 A 中自己编写代码来获得一个 Connection 对象,有了 spring 我们就只需要告诉 spring,A 中需要一个 Connection,至于这个 Connection 怎么构造,何时构造,A 不需要知道。在系统运行时,spring 会在适当的时候制造一个 Connection,然后像打针一样,注射到 A 当中,这样就完成了对各个对象之间关系的控制。A 需要依赖 Connection 才能正常运行,而这个 Connection 是由 spring 注入到 A 中的,依赖注入的名字就这么来的。那么 DI 是如何实现的呢? Java 1.3 之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring 就是通过反射来实现注入的。
二、helloworld
使用 maven 创建一个 web 项目,并添加 spring 上下文容器依赖。该依赖 spring 依赖其他几个 spring 常用依赖如:spring-aop、spring-beans、、spring-core、spring-expression 等。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
maven 会自动帮我们解决依赖之间的关联问题,如下为具体依赖关系图:
在项目中创建实体 User
public class User {
private Integer id;
private String name;
}
配置 spring 容器的配置文件,在 resources 目录下点击 new -> XML Configuration File ->Spring Config 创建 spring-context.xml,并在其中配置一个 bean(被 spring 管理的对象)。
其中 bean 的 id 表示该对象在容器中的唯一标识。class 表明该对象所属的类型。property 相当于对象的属性设置,spring 通过调研对象的 set 方法完成属性设置。
<?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="user" class="cn.hxzy.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
</bean>
</beans>
在主函数或测试类中获取 spring 的容器,并从容器中获取 bean 对象。
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-context.xml");
// 从 IOC 容器中获取 bean 的实例
User bean = ctx.getBean(User.class);
System.out.println(bean);
总结:如上操作即使用 spring 的语法在 spring 容器中创建一个被管理的对象。再通过 ApplicationContext(spring 上下文容器)获取该对象,ApplicationContext 是一个接口,常用的实现类有 ClassPathXmlApplicationContext:从类路径下加载 xml 配置文件获取 spring 上下文容器和 AnnotationConfigApplicationContext:从配置类中加载 spring 容器信息。通过容器获取 bean 常用三种方式如下:
Object getBean(String beanId) throws BeansException;
<T> T getBean(String beanId, Class<T> var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
第一种是根据 bean 的 id 获取,由于容器无法确定返回类型,故返回结果为 Object 类型。第二种传入 bean 的类型故返回该类型的 bean。第三种根据类型获取 bean,当该类型的 bean 存在多个是 spring 启动会报错。
三、构造注入
在实际开发中有时需要被管理的对象没有提供无参构造方法,因此我们需要在创建对象时直接传入对应的值。spring 使用 constructor-arg 设置对象的构造方法的参数,通常可以使用 name 指定构造方法的参数名或通过 index 指定构造方法参数的索引。比如: 与下面设置 id 的方式等效。
<bean id="user2" class="cn.hxzy.User">
<constructor-arg name="name" value="李四"/>
<constructor-arg name="id" value="2"/>
</bean>
注意:在 xml 中包含特殊字符,必须使用<![CDATA[*****]]> 包裹,且不能在写在 constructor-arg 节点的属性上。
<constructor-arg name="name" >
<value><![CDATA[<李四]]></value>
</constructor-arg>
练习:
1.创建项目 spring-fruit01,分别使用普通属性注入和构造注入的方式完成如下水果的创建。fruit(id,name,color)
fruit1(1,apple,red)属性注入
fruit2(2,banner,yellow)构造注入
fruit3(3,pear,white)构造加属性注入
fruit4(4,orange,)特殊字符注入
参考代码:
<bean id="fruit1" class="cn.hxzy.Fruit">
<property name="id" value="1"/>
<property name="name" value="apple"/>
<property name="color" value="red"/>
</bean>
<bean id="fruit2" class="cn.hxzy.Fruit">
<constructor-arg index="0" value="2"></constructor-arg>
<constructor-arg index="1" value="banner"></constructor-arg>
<constructor-arg index="2" value="yellow"></constructor-arg>
</bean>
<bean id="fruit3" class="cn.hxzy.Fruit">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="pear"></constructor-arg>
<constructor-arg name="color" value="white"></constructor-arg>
</bean>
<bean id="fruit4" class="cn.hxzy.Fruit">
<constructor-arg name="id" value="4"></constructor-arg>
<constructor-arg name="name" value="orange"></constructor-arg>
<constructor-arg name="color">
<value><![CDATA[<orange>]]></value>
</constructor-arg>
</bean>
四、级联注入
在 Spring 中可以通过一组内置的 xml 标签(例如:<list>,<set> 或 <map>)来配置集合属性。配置 java.util.List 类型的属性,需要指定 <list>标签,在标签里包含一些元素。这些标签可以通过 <value> 指定简单的常量值,通过<ref> 指定对其他 Bean 的引用。通过<bean> 指定内置 Bean 定义。通过 <null/> 指定空元素。甚至可以内嵌其他集合。数组的定义和 List 一样,都使用 <list> 配置 java.util.Set 需要使用 <set> 标签,定义元素的方法与 List 一样。
在用户实体中加入 Car 集合
public class User {
......
private List<Car> cars;
}
汽车实体
public class Car {
private String company;
private Integer maxSpeed;
private Float price;
}
级联时使用 list 表明该处级联类型为 list 集合,里面使用 ref 应用其他 bean 对象,级联 set 与 map 也与此类似。
<bean id="user1" class="cn.hxzy.User">
<property name="id" value="1"/>
<property name="name" value="张三"/>
<property name="cars">
<list>
<ref bean="car1"/>
</list>
</property>
</bean>
<bean id="car1" class="cn.hxzy.Car">
<property name="company" value="大众"/>
<property name="maxSpeed" value="280"/>
<property name="price" value="30"/>
</bean>
注意:设置为空使用下面的语法
<property name="company"><null/></property>
练习:
1.创建项目 spring-fruit02,在基本属性设置的情况下增加特征(feature)和别名(otherName)两个级联属性。
Fruit{id=1, name=‘pear’, color=‘white’, feature={表皮=粗糙, 肉质=甜美}, otherName=[雪梨, 大鸭梨]}
public class Fruit {
private Integer id;
private String name;
private String color;
private Map<String, String> feature;
private Set<String> otherName;
}
参考代码:
<bean id="fruit" class="cn.hxzy.Fruit">
<property name="id" value="1"/>
<property name="name" value="pear"></property>
<property name="color" value="white"></property>
<property name="feature">
<map>
<entry key="表皮" value="粗糙"/>
<entry key="肉质" value="甜美"/>
</map>
</property>
<property name="otherName">
<set>
<value>雪梨</value>
<value>大鸭梨</value>
</set>
</property>
</bean>
五、单例与多例
为了节省内存,spring 中的对象默认使用单例模式。即无论从容器中获取多少次对象,得到的都是同一个。但在实际开发中有时会用到多例,即希望每次从容器中获取对象都是新的对象。spring 提供 scop 属性来指定对象获取是单例还是多例。多例的 bean 又叫原型的。
prototype:原型的。每次调用 getBean 方法都会返回一个新的 bean。且在第一次调用 getBean 方法时才创建实例。
singleton:单例的。每次调用 getBean 方法都会返回同一个 bean。且在 IOC 容器初始化时即创建 bean 的实例。默认值
<bean id="user" class="cn.hxzy.User" scope="prototype">
<property name="id" value="1"/>
<property name="name" value="张三"></property>
</bean>
应用场景:
1.数据库连接 Connection 对象不能设计成单例,否则会出现多个线程使用同一个连接完成数据库的不同操作,也许上一个线程还没有查询完数据就会被下一个线程拿去修改数据库记录,非常容易出现错误。
2.service 层的对象就不需要设计成原型的,因为 service 层没有过多的参数,不容易导致线程安全问题,创建过多的对象反而耗用大量内存意义不大。
六、工厂方法创建 Bean
在项目开发中,有时需要使用工厂方法创建 bean,spring 支持常用的工厂方法如静态工厂方法创建和实例工厂方法创建 bean。
1.静态工厂方法创建 bean
public class CarFactory {
static Map<String, Car> cars = new HashMap<>();
static {
cars.put("car1", new Car("宝马", 120, 122222.0));
cars.put("car2", new Car("奥迪", 120, 122222.0));
}
public static Car getCar(String name) {
return cars.get(name);
}
}
获取 bean
<bean id="car1" class="cn.hxzy.CarFactory" factory-method="getCar">
<constructor-arg value="car2"/>
</bean>
2.实例工厂方法获取 bean
public class CarFactory {
Map<String, Car> cars = new HashMap<>();
public CarFactory() {
cars.put("car1", new Car("宝马", 120, 122222.0));
cars.put("car2", new Car("奥迪", 120, 122222.0));
}
public Car getCar(String name) {
return cars.get(name);
}
}
获取bean
<bean id="carFactory" class="cn.hxzy.CarFactory"></bean>
<bean id="car1" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="car1"></constructor-arg>
</bean>
七、包扫描管理 bean
使用 xml 方式配置 bean 的依赖关系复杂且麻烦,为了解决对象在容器注入麻烦的问题,spring 推出了包扫描与声明类注解配合使用的方式,对于开发人员编写的类只需要在包扫描范围内,使用指定的声明类注解即可将对象加入 spring 容器中。
sping 使用 context:component-scan 将指定包下面的带有声明 bean 的注解的类加入到 spring 容器管理。
<context:component-scan base-package="cn.hxzy"/>
常用的声明类注解有如下几个,它们的功能和作用在 spring 中完全一模一样,都是将自己交给 spring 管理。唯一的区别就是它们所用的业务环节不同。
- @Service 用于标注业务层组件
- @Controller 用于标注控制层组件
- @Repository 用于标注数据访问组件,即 DAO 组件
- @Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
注入类注解:在 spring 容器管理的 bean 对象需要依赖其他 bean 对象时,就可以在对应对象属性或其 set 方法上使用注入类注解完成依赖注入。spring 容器中常用 @Autowired 和 @Resource 两个注解完成依赖注入。@Autowired 属于 Spring 的注解,@Resource 不属于 Spring 的注解,是 JDK1.6 支持的注解。它们的区别是:
@Autowired 默认按类型装配, 依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false @Autowired(required = false) 也可以使用名称装配,配合 @Qualifier 注解
public class TestServiceImpl {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
@Resource 默认按名称进行装配,通过 name 属性进行指定,name 为属性的名字时可以省略。
案例:
dao 层模拟从数据库查出数据,首先需要使用声明类注解将自己注册到容器。
@Repository
public class PetDao {
public List<Pet> getAll() {
List<Pet> pets = new ArrayList<>();
pets.add(new Pet("花花"));
return pets;
}
}
service 层除了将自己注册到容器,还依赖 dao 层的对象,使用注入类注解将容器内的对象注入到对应位置。
@Service
public class PetService {
@Autowired
private PetDao dao;
public List<Pet> getAll() {
return dao.getAll();
}
}
view 层与服务层原理相同。
@Controller
public class PetView {
@Resource
private PetService petService;
public void getAll() {
List<Pet> list = petService.getAll();
System.out.println(list);
}
}
main 从容器中获取 view 层对象即可调用它的方法,得到 dao 层返回的结果。
public class MainTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
PetView bean = context.getBean(PetView.class);
bean.getAll();
}
}
总结:将除了实体以外的其他对象加入 spring 容器管理,并通过彼此的依赖自动注入。使程序开发更加简便灵活,在内存耗用,对象管理方面更加优秀。
八、SPEL与资源调用
内容如下:
<context:property-placeholder location="classpath:db.properties" file-encoding="utf-8"/>
<bean id="car1">
<property name="company" value="${name}"/>
<property name="maxSpeed" value="280"/>
<property name="price" value="30"></property>
</bean>
如果该属性需要的 spring 包扫描的类中使用可以使用 @Value("${name}") 获取配置文件内的属性。
@Service
public class PropertyTest {
@Value("${name}")
private String id;
public String getId() {
return id;
}
}
在 spring 中加载配置文件也可以使用注解的方式 @PropertySource(“classpath:db.properties”) 它与 context:property-placeholder 是等效的。
spring 不仅仅可以获取配置文件里面的内容它还可以做一些简单的运算,如下案例:可以注入其他对象的属性、普通字符串、操作系统的信息、随机数、某个文件的内容等。如下代码在输出 Resource 时用到 commons-io 工具类。
依赖如下:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
案例:
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
@Service
@PropertySource("classpath:db.properties")
public class PropertyTest {
@Value("${id}")
private int id;
//注入配置文件的属性
@Value("I love you")
private String normal;
//字符串原样注入
@Value("#{systemProperties['os.name']}")
private String osName;
//注入操作系统信息
@Value("#{pet.name}")
private String otherName;
//注入容器内其它 bean 的属性
@Value("#{T (java.lang.Math).random()*100}")
private Integer random;
//注入 100 以下的随机数
@Value("classpath:1.txt")
private Resource resource;
//注入classpath下文本文件里面内容
@Value("http://58.42.239.163:8888/")
private Resource resourceUrl;
//注入网站地址的响应内容
public String toString1() throws Exception {
return "[normal=" + normal + ",id="+id+", osName=" + osName + ", otherName=" + otherName
+ ", random=" + random + ", resource=" + IOUtils.toString(resource.getInputStream())
+ ", resourceUrl=" + IOUtils.toString(resourceUrl.getInputStream(), "UTF-8") + "]";
}
}
注意:
1.在 spring 通过注解 @Value 中获取 properties 文件里面的值使用 $ 前缀。
2.获取系统信息、容器类其他对象的信息、方法调用后的结果等使用 # 前缀。
3.获取文件或网页内容不使用前缀,且使用 Resource 接收。
九、对象属性拷贝(自)
BeanUtils 在 spring 工具库中是比较常用的对象属性拷贝工具,BeanUtils.copyProperties(源头,目标);一般使用如下:
User user1 = .....;
User user2 = .....;
BeanUtils.copyProperties(user1,user2);
当在完成两个对象拷贝是需要负略某些属性可以使用对应的重载方法 BeanUtils.copyProperties(源头,目标,“忽略的属性”); 但在实际开发中拷贝非空属性是比较常用的。创建方法将对象的非空属性列出后传入 BeanUtils 即可拷贝非空属性。
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;
public class BeanUtil {
private static String[] getNullPropertyNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for (java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null)
emptyNames.add(pd.getName());
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
public static void copyPropertiesIgnoreNull(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
}
十、单元测试
在实际开发中有时需要对 spring 容器中的大量的类和方法进行测试,使用 main 方法显然不能很好的满足需求,单元测试更好的解决测试方面的注入和运行问题。使用 spring 的单元测试需要导入相应的依赖:spring-test,同时要求 junit 不小于 4.12 版本。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.12.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
引入依赖后在测试源码目录下创建如下类并添加相应的注解
import cn.hxzy.spring.view.PetView;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring-context.xml")
public class AppTest {
@Autowired
private PetView petView;
@Test
public void sendSimpleEmail() throws InterruptedException {
petView.show();
}
}
十一、spring与 mybatis 整合
spring 能够与常见框架整合,如 mybatis,hibernate,redis 等。这使得大部分框架中的对象都可以从 spring 中获取到。而且对象被 spring 管理后可以很方便的使用 spring 的依赖注入和面向切面。
使用 spring 与 mybatis 整合需要如下几步:
1.添加依赖:由于数据库连接相关对象都交给 spring 管理,所以依赖中多出了 spring-jdbc 依赖。同时 mybatis 为了和 spring 整合开发了 mybatis-spring 整合包。
<!--spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.配置数据库连接信息,文件名通常为 db.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mydb
jdbc.username=root
jdbc.password=123456
3.spring 核心配置文件,由于数据库连接等对象都被 spring 管理,所以配置时不再需要 mybatis 的核心配置文件,如果在开发中确实需要用到 mybatis 的核心配置文件也可以配置 sqlsessionfactory 的 configlocation。
<?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" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.hxzy"/>
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据库数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 创建 session 工厂-->
<bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!--告诉spring mybatis接口的位置-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.hxzy.mapper"/>
</bean>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 使用注解的方式完成事务控制-->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
</beans>
注意:
1.mapperScanner 配置主要配置 mybatis 的接口位置。
2.注解式事务是通过 @Transactional 注解即可完成该方法所有数据库操作要么一起成功,要么一起失败的业务逻辑。使用事务时数据库引擎必须使用 innoDB。
@Transactional
public void update(){
User user1 = new User();
user1.setId(2);
user1.setName("O");
userMapper.update(user1);
System.out.println(1 / 0);
User user2 = new User();
user2.setId(3);
user2.setName("Y");
userMapper.update(user2);
}
3.spring5 版本不再支持原来的 mybatis 章节的 log4j 版本,为了显示日志。通常我们使用 logback。导入如下依赖。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
配置文件 logback.xml 放在 classpath 下。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p --- [%t] %-40.40logger{39} : %m%n</pattern>
</encoder>
</appender>
<!--根logger-->
<root level="DEBUG" additivity="false">
<appender-ref ref="console"/>
</root>
</configuration>
章节练习:
1.IOC 综合练习:Boss,Car,Meeting 三个实体存在依赖关系如下:
Car:属性有 brand(品牌)、color(颜色)、parameter (参数 map)
Boss:属性有 name(名字)、company(公司全称)、car(拥有的车)
Meeting:属性 theme(主题)、bosses (会议主题在 meet.properties 文件中)
综合采用所学内容,设计并完成以上综合练习。一次会议包含 张三,李四和王二三个 Boss,他们每个人都有自己的爱车 car,使用 spring ioc 的 xml 方式配置这些对象。并在单元测试中输出结果如下:
Meeting{theme='全国程序员保护协会一次会议', bosses=[
Boss{name='张三', company='ABC', car=Car{brand='轩逸', color='红色', parameter={参考价=9.98-14.30万, 排量=1.6L}}},
Boss{name='李四', company='DEF', car=Car{brand='卡罗拉', color='白色', parameter={参考价=10.98-15.98万, 排量=1.2L,1.5L,1.8L}}},
Boss{name='王二', company='GHI', car=Car{brand='哈弗H6', color='白色', parameter={参考价=9.98-15.48万, 排量=1.5L,2.0L}}}]}
参考代码:
<context:property-placeholder location="classpath:meet.properties"/>
<bean id="car1" class="cn.hxzy.Car">
<property name="color" value="红色"/>
<property name="brand" value="轩逸"/>
<property name="parameter">
<map>
<entry key="参考价" value="9.98-14.30万"/>
<entry key="排量" value="1.6L"/>
</map>
</property>
</bean>
<bean id="car2" class="cn.hxzy.Car">
<property name="color" value="白色"/>
<property name="brand" value="卡罗拉"/>
<property name="parameter">
<map>
<entry key="参考价" value="10.98-15.98万"/>
<entry key="排量" value="1.2L,1.5L,1.8L"/>
</map>
</property>
</bean>
<bean id="car3" class="cn.hxzy.Car">
<property name="color" value="白色"/>
<property name="brand" value="哈弗H6"/>
<property name="parameter">
<map>
<entry key="参考价" value="9.98-15.48万"/>
<entry key="排量" value="1.5L,2.0L"/>
</map>
</property>
</bean>
<bean id="boss1" class="cn.hxzy.Boss">
<property name="name" value="张三"/>
<property name="company" value="ABC"/>
<property name="car" ref="car1"/>
</bean>
<bean id="boss2" class="cn.hxzy.Boss">
<property name="name" value="李四"/>
<property name="company" value="DEF"/>
<property name="car" ref="car2"/>
</bean>
<bean id="boss3" class="cn.hxzy.Boss">
<property name="name" value="王二"/>
<property name="company" value="GHI"/>
<property name="car" ref="car3"/>
</bean>
<bean class="cn.hxzy.Meeting">
<property name="bosses">
<list>
<ref bean="boss1"></ref>
<ref bean="boss2"></ref>
<ref bean="boss3"></ref>
</list>
</property>
<property name="theme" value="${theme}"/>
</bean>