21-Spring-1(bean)
1. 框架
- 高度抽取可重用代码的一种设计,有高度的通用性。
- 框架是多个「可重用模块」的集合(半成品软件),形成一个某个领域的整体解决方案。
2. Spring 模块
Spring是一个 IOC(DI) 和 AOP 容器框架 // 容器:管理所有的组件(类)
- Test:Spring 的单元测试模块
spring-test-4.0.0.RELEASE.jar
- Core Container:核心容器(IOC)
# 黑框代表功能的组成 jar,若要使用这部分的完整功能,jar 都需要导入 spring-aspects-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar
- AOP + Aspects:面向切面编程模块
spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar
- 数据访问/集成:访问数据库模块
spring-jdbc-4.0.0.RELEASE.jar spring-orm-4.0.0.RELEASE.jar # 对象关系映射 spring-tx-4.0.0.RELEASE.jar # 事务 spring-oxm-4.0.0.RELEASE.jar # x: xml spring-jms-4.0.0.RELEASE.jar
- web:开发 web 应用的模块
spring-websocket-4.0.0.RELEASE.jar spring-web-4.0.0.RELEASE.jar # Servlet spring-webmvc-4.0.0.RELEASE.jar # web spring-webmvc-portlet-4.0.0.RELEASE.jar
3. IOC
IOC:Inversion(反转) Of Control(控制,即资源的获取方式)
- 主动式:要什么资源手动创建即可
- 被动式:由容器(管理所有的组件,即封装功能的类)主动将资源推动给需要的组件,将原来的主动 new 资源变成被动地接受资源。
DI:Dependency Injection,依赖注入。IOC 是一种反转控制的思想,而 DI 是对 IOC 的一种具体实现。
4. HelloWorld
4.1 导包
Spring 运行依赖以上日志包,没有的话运行会报错。
4.2 写配置文件
创建一个源码文件夹 Source Folder → 创建一个 Spring Bean Configuration File,即 Spring 的核心配置文件。// 注意一定是放在源码文件夹,不然根本放不到 bin 目录!
4.3 编写测试类
@Test // 从容器中拿到这个组件
public void test() {
// ApplicationContext 代表 IOC 容器 = 当前应用的配置文件在类路径下
ApplicationContext ioc = new ClassPathXmlApplicationContext("ioc.xml");
// 容器帮我们创建的对象
Person person = (Person) ioc.getBean("person01");
System.out.println(person);
}
4.4 Tips
- src 源码包开始的路径,也称为“类路径的开始”;所有源码包的东西都会被合并放在类路径里面。
- java:/bin/
- web 类路径:/WEB-INF/classes
- java:/bin/
- 需要导入额外的日志包:commons-logging-1.1.3.jar,否则启动将报错
- 一定要先导包,再创建配置文件
- Spring 的容器接管了该类对象的标志
- ServletContext(IOC 容器的接口) 的继承结构
- ClassPathXmlApplicationContext:IOC 容器的配置文件在类路径下
- FileSystemXmlApplicationContext:IOC 容器的配置文件在磁盘路径下
- 给容器中注册一个组件,我们也从容器中按照 id 拿到了这个组件的对象,所有组件的创建默认是在容器启动完成之前,而非是在获取组件时创建
- 同一个组件在 IOC 容器中是单实例的
Person person1 = (Person) ioc.getBean("person01"); Person person2 = (Person) ioc.getBean("person01"); System.out.println(person1 == person2); // true
- 获取一个容器中没有的组件,抛异常:NoSuchBeanDefinitionException
- 容器在对组件初始化的时候,是利用 setter 给属性赋值的 // JavaBean 属性名是由 setXxx 的 xxx 决定的
5. bean 的属性赋值
5.1 简单属性赋值
5.1.1 通过 property
<bean id="person02" class="cn.edu.nuist.bean.Person">
<property name="lastName" value="李四"></property>
<property name="age" value="28"></property>
<property name="email" value="zhangsan@163.com"></property>
<property name="gender" value="男"></property>
</bean>
5.1.2 通过 constructor-org
<!-- 多个重载构造器导致赋值错误情况, 可用 type 指定参数数据类型 -->
<bean id="person05" class="cn.edu.nuist.bean.Person">
<constructor-arg value="小花"></constructor-arg>
<constructor-arg value="18" type="java.lang.Integer"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
</bean>
<!--
可以省略 name 属性,但此时必须严格按照构造器声明的形参顺序赋值,也可用 index 属性指明形参索引
(String lastName, Integer age, String gender, String email)
-->
<bean id="person04" class="cn.edu.nuist.bean.Person">
<constructor-arg value="小花"></constructor-arg>
<constructor-arg value="18"></constructor-arg>
<constructor-arg value="女"></constructor-arg>
<constructor-arg value="xiaohua@hahaha.com"></constructor-arg>
</bean>
<bean id="person03" class="cn.edu.nuist.bean.Person">
<constructor-arg name="lastName" value="小明"></constructor-arg>
<constructor-arg name="gender" value="男"></constructor-arg>
<constructor-arg name="age" value="10"></constructor-arg>
<constructor-arg name="email" value="xiaoming@gmail.com"></constructor-arg>
</bean>
5.1.3 通过 p 名称空间
<!--
通过 p 名称空间为 bean 赋值 // 需要手动导入 p 名称空间
名称空间:在 XML 中名称空间是用来防止标签重复的
<book>
<b:name>西游记</b:name>
<price>19.9</price>
<author>
<a:name>吴承恩</a:name>
<gender>男</gender>
</author>
</book>
带前缀的标签 <c:forEach>, <jsp:forward> ...
-->
<bean id="person06" class="cn.edu.nuist.bean.Person"
p:age="18" p:email="xiaogang@163.com" p:lastName="小强" p:gender="男"
></bean>
5.2 复杂属性赋值
5.2.1 赋值 null
ioc.xml
<!-- 如下写法 或者 什么都不写就是 null -->
<bean id="person01" class="cn.edu.nuist.bean.Person">
<property name="lastName">
<null />
</property>
</bean>
5.2.2 赋值 bean
- 引用其他 bean
<bean id="car01" class="cn.edu.nuist.bean.Car"> <property name="brand" value="宝马"></property> <property name="color" value="黑色"></property> <property name="price" value="100000"></property> </bean> <!-- 测试复杂类型赋值 --> <bean id="person02" class="cn.edu.nuist.bean.Person"> <!-- ref 代表该属性是引用一个外面的值 → car = ioc.getBean("car01") --> <property name="car" ref="car01"></property> </bean> ----------------------------------------------------- @Test public void test() { Person person = ioc.getBean("person02", Person.class); Car car = ioc.getBean("car01", Car.class); System.out.println(person.getCar() == car); // true }
- 引用内部 bean
<!-- 测试引用内部 bean --> <bean id="person03" class="cn.edu.nuist.bean.Person"> <property name="car"> <!-- 对象,我们可以使用 bean 标签创建 → car = new Car()--> <bean class="cn.edu.nuist.bean.Car"></bean> </property> </bean> ----------------------------------------------------- @Test public void test2() { Person person = ioc.getBean("person03", Person.class); Car car = ioc.getBean("car01", Car.class); System.out.println(person.getCar() == car); // false }
5.2.3 赋值 集合bean
- 赋值 list
<bean id="outerBook" class="cn.edu.nuist.bean.Book"> <property name="bookName" value="狐狸在说什么"></property> <property name="author" value="韩国人"></property> </bean> <!-- 测试 list --> <bean id="person04" class="cn.edu.nuist.bean.Person"> <property name="books"> <!-- books = new ArrayList<Book>(); --> <list> <!-- list 标签体中添加每一个元素 --> <bean id="innerBook" class="cn.edu.nuist.bean.Book" p:bookName="cmbyn" p:author="美国人"></bean> <!-- 引用外部一个元素 --> <ref bean="outerBook"></ref> </list> </property> </bean> ----------------------------------------------------- @Test public void test3() { Person person = ioc.getBean("person04", Person.class); System.out.println(person.getBooks()); // NoSuchBeanDefinitionException: No bean named 'innerBook' is defined // 所以,内部 bean 只能内部使用 // Book innerBook = ioc.getBean("innerBook", Book.class); Book outerBook = ioc.getBean("outerBook", Book.class); }
- 赋值 map
<bean id="person05" class="cn.edu.nuist.bean.Person"> <!-- map = new LinkedHashMap<>(); --> <property name="map"> <map> <entry key="key01" value="value01"></entry> <entry key="key02" value="value02"></entry> <entry key="key03" value-ref="outerBook"></entry> <entry key="key04"> <bean class="cn.edu.nuist.bean.Car"> <property name="brand" value="宝马"></property> </bean> </entry> <!-- <entry key="key05"> <map></map> </entry> --> </map> </property> </bean>
- 赋值 properties
<bean id="person06" class="cn.edu.nuist.bean.Person"> <property name="properties"> <!-- properties = new Properties(); --> <props> <!-- 因为 k=v 都是 String --> <prop key="username">root</prop> <prop key="password">123</prop> </props> </property> </bean>
5.2.4 util 名称空间
<bean id="person07" class="cn.edu.nuist.bean.Person">
<!-- 引用 util 名称空间创建的集合类型 bean -->
<property name="map" ref="myMap"></property>
</bean>
<!-- new LinkedHashMap(); -->
<util:map id="myMap">
<entry key="key01" value="value01"></entry>
<entry key="key02" value="value02"></entry>
<entry key="key03" value-ref="outerBook"></entry>
</util:map>
<util:list id="myList">
<list></list>
<bean class="cn.edu.nuist.bean.Person"></bean>
<value>1101</value>
<ref bean="myMap"/>
</util:list>
5.2.5 赋值 级联属性
<!-- 级联属性赋值:属性的属性 -->
<bean id="person08" class="cn.edu.nuist.bean.Person">
<property name="car" ref="car01"></property>
<!-- 改变 ref-car 的价格 -->
<property name="car.price" value="9999"></property>
</bean>
5.3 继承实现配置信息的重用
<!--
abstract: 将 person01 设置为不可被获得实例,只能被用于继承的模板 bean
否则将抛出异常 BeanIsAbstractException: Error creating
bean with name 'person01': Bean definition is abstract
-->
<bean id="person01" class="cn.edu.nuist.bean.Person" abstract="true">
<property name="lastName" value="张三"></property>
<property name="age" value="30"></property>
<property name="email" value="zhangsan@163.com"></property>
<property name="gender" value="男"></property>
</bean>
<!-- parent: 指定当前 bean 的配置信息继承于哪个,类型可省 -->
<bean id="person02" parent="person01">
<property name="lastName" value="李四"></property>
</bean>
6. bean 补充
6.1 bean 之间的依赖
只是改变 bean 的创建顺序。
<!--
默认按照配置顺序创建 bean,如何改变 bean 的创建顺序?
bean 之间的依赖(只是改变创建顺序) → depends-on 属性
-->
<bean id="car01" class="cn.edu.nuist.bean.Car" depends-on="person01, book01"></bean>
<bean id="person01" class="cn.edu.nuist.bean.Person"></bean>
<bean id="book01" class="cn.edu.nuist.bean.Book"></bean>
----------------
Person 被创建了
Book 被创建了
Car 被创建了
6.2 bean 多实例
<!--
测试 bean 的作用域,分别创建单实例和多实例的 bean → 使用 scope 属性
prototype:多实例,容器启动时不会创建,在 getBean 时才创建对象,每次获取都是新的对象
singleton:单实例(默认),在容器启动完成之前就已经创建好对象,保存在容器中了
request:在 web 环境下,同一次请求创建一个 bean 实例(没用)
session:在 web 环境下,同一次会话创建一个 bean 实例(没用)
-->
<bean id="book" class="cn.edu.nuist.bean.Book" scope="prototype"></bean>
6.3 工厂 bean
bean 的创建默认就是框架利用反射 new 出来的 bean 实例。
工厂模式:工厂帮我们创建对象,一个专门创建对象的类,这个类就是工厂。
6.3.1 静态工厂
静态工厂本身不用创建对象,通过静态方法调用。
ioc.xml
<!--
1. 静态工厂
class 指定为静态工厂全类名
factory-method 指定造对象的方法名
constructor-arg 可以为工厂方法传参
-->
<bean id="airplane01" factory-method="getAirplane"
class="cn.edu.nuist.factory.AirplaneStaticFactory">
<!-- 可以为方法指定参数 -->
<constructor-arg value="李四"></constructor-arg>
</bean>
AirplaneStaticFactory
public class AirplaneStaticFactory {
public static Airplane getAirplane(String captain) {
System.out.println("静态工厂正在造飞机...");
Airplane ap = new Airplane();
ap.setCaptain(captain);
ap.setLengthOfWing("50");
ap.setNumber("1101");
return ap;
}
}
6.3.2 实例工厂
工厂本身需要创建对象,然后调用工厂对象的成员方法。
ioc.xml
<!-- 2. 实例工厂 -->
<bean id="airplaneInstanceFactory"
class="cn.edu.nuist.factory.AirplaneInstanceFactory"></bean>
<!--
factory-method: 指定这个实例工厂中哪个方法是工厂方法
factory-bean: 指定创建当前对象是使用的哪个工厂
-->
<bean id="airplane02" class="cn.edu.nuist.bean.Airplane"
factory-bean="airplaneInstanceFactory"
factory-method="getAirplane">
<constructor-arg value="王五"></constructor-arg>
</bean>
AirplaneInstanceFactory:
public class AirplaneInstanceFactory {
public Airplane getAirplane(String captain) {
System.out.println("实例工厂正在造飞机...");
Airplane ap = new Airplane();
ap.setCaptain(captain);
ap.setLengthOfWing("50");
ap.setNumber("1101");
return ap;
}
}
6.3.3 FactoryBean
实现了 FactoryBean 接口的类是 Spring 可以认识的工厂类。
该工厂类返回的 bean 的无论设置的是单实例还是多实例,都是在获取的时候才创建对象。
public class MyFactoryBean implements FactoryBean<Book> {
@Override
public Book getObject() throws Exception {
System.out.println("MyFactoryBean 创建 Book 对象");
Book book = new Book();
book.setBookName(UUID.randomUUID().toString());
return book;
}
// 返回创建的对象的类型
@Override
public Class<?> getObjectType() {
return Book.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
Test
@Test
public void test() {
Object bean = ioc.getBean("myFactoryBean");
Object bean2 = ioc.getBean("myFactoryBean");
System.out.println(bean == bean2); // true
}
6.4 带有生命周期方法的 bean
<!--
自定义初始化方法和销毁方法,方法要求没有参数,但可以抛出异常
init-method, destroy-method
-->
<bean id="book01" class="cn.edu.nuist.bean.Book"
init-method="init" destroy-method="destroy"
<!-- scope="prototype" -->
></bean>
- Test1
- Test2
- Test3
6.5 bean 的后置处理器
/*
* 1. 编写后置处理器实现类
* 2. 将后置处理器注册在配置文件中
* 注:就算没有定义 init-method,后置处理器也会默认其有,然后继续工作
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 初始化方法之前调用
*/
@Override
public Object postProcessBeforeInitialization(Object bean
, String beanName) throws BeansException {
System.out.println(beanName + "BeforeInitialization..." + bean);
// 返回的是什么,容器中保存的就是什么
return bean;
}
/**
* 初始化方法之后调用
*/
@Override
public Object postProcessAfterInitialization(Object bean
, String beanName) throws BeansException {
System.out.println(beanName + "AfterInitialization..." + bean);
return bean;
}
}
ioc.xml
<bean id="book01" class="cn.edu.nuist.bean.Book"
init-method="init" destroy-method="destroy">
<property name="bookName" value="狐狸在说什么"></property>
</bean>
<bean id="car01" class="cn.edu.nuist.bean.Car"></bean>
<bean id="beanPostProcessor" class="cn.edu.nuist.bean.MyBeanPostProcessor"></bean>
7. 引入外部属性文件
- dbconfig.properties
jdbc.user=root jdbc.password=root jdbc.jdbcUrl=jdbc:mysql:///test jdbc.driverClass=com.mysql.jdbc.Driver
- ioc.xml
<!-- 引用外部属性文件;"classpath:" 固定写法,引入类路径下资源 --> <context:property-placeholder location="classpath:dbconfig.properties"/> <!-- 数据库连接池作为单实例是最好的,一个项目就一个连接池,连接池里面管理很多连接 --> <!-- 可以让 Spring 帮我们创建连接池对象(管理连接池) --> <!-- value 属性值中不要放多余的空格,如:"${jdbc.user} " --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> </bean> <!-- username 是 Spring 的 key 中的关键字 → 系统用户名 --> <bean id="car01" class="cn.edu.nuist.bean.Car"> <property name="brand" value="${username}"></property> </bean>
- Test
@Test public void test() { System.out.println(ioc.getBean("car01", Car.class).getBrand()); // 10646 DataSource dataSource = ioc.getBean("dataSource", DataSource.class); System.out.println(dataSource); }
注意!${...}
是取出配置文件中的值,而 #{...}
是 Spring 的表达式语言,别搞错了。