1 Spring 配置/管理 bean 介绍
Bean 管理包括两方面 :创建 bean 对象;给 bean 注入属性
Bean 配置方式:基于 xml 文件配置方式;基于注解方式
2 基于 XML 配置 bean
2.1 通过类型来获取 bean
方法:给getBean传入一个Class对象
示例:
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster = ioc.getBean(Monster.class);
说明:
2.2 通过构造器配置 bean
(1)constructor-arg标签可以指定使用构造器的参数
(2)index表示构造器的第几个参数 从0开始计算的
(3)除了可以通过index 还可以通过 name / type 来指定参数方式,name表示参数的名字,type表示参数的类型
(4)类的构造器,不能有完全相同类型和顺序的构造器,所以可以通过type来指定
<bean id="monster03" class="com.spring.bean.Monster">
<constructor-arg value="200" index="0"/>
<constructor-arg value="白骨精" index="1"/>
<constructor-arg value="吸人血" index="2"/>
</bean>
<bean id="monster04" class="com.spring.bean.Monster">
<constructor-arg value="200" name="monsterId"/>
<constructor-arg value="白骨精" name="name"/>
<constructor-arg value="吸人血" name="skill"/>
</bean>
<bean id="monster05" class="com.spring.bean.Monster">
<constructor-arg value="300" type="java.lang.Integer"/>
<constructor-arg value="白骨精~" type="java.lang.String"/>
<constructor-arg value="吸人血~" type="java.lang.String"/>
</bean>
说明:
- 通过 index 属性来区分是第几个参数
- 通过 type 属性来区分是什么类型(按照参数顺序)
2.3 通过 p 名称空间配置 bean
在 xml 文件配置, 增加命名空间配置
将光标放在p , 输入alt+enter , 就会自动的添加xmlns
示例:
<!--通过p名称空间来配置bean
将光标放在p , 输入alt+enter , 就会自动的添加xmlns
-->
<bean id="monster06" class="com.spring.bean.Monster"
p:monsterId="500"
p:name="红孩儿"
p:skill="吐火"
/>
2.4 引用/注入外部 bean 对象
在 spring 的 ioc 容器, 可以通过 ref 来实现 bean 对象的相互引用
应用案例:
public class MemberDAOImpl {
public MemberDAOImpl() {
System.out.println("MemberDAOImpl 构造器...");
}
public void add() {
System.out.println("MemberDAOImpl add()方法");
}
}
(2)创 建 MemberServiceImpl.java
public class MemberServiceImpl {
private MemberDAOImpl memberDAO;
public MemberServiceImpl() {
System.out.println("MemberServiceImpl 构造器~");
}
public void add() {
System.out.println("MemberServiceImpl add()...");
memberDAO.add();
}
public void setMemberDAO(MemberDAOImpl memberDAO) {
this.memberDAO = memberDAO;
}
public MemberDAOImpl getMemberDAO() {
return memberDAO;
}
}
(3)在 beans.xml 配置
<!-- bean 对象的相互引用
1. 其它含义和前面一样
2. ref 表示 memberDAO 这个属性将引用/指向 id = memberDAOImpl 对象
-->
<bean id="memberServiceImpl" class="com.spring.service.MemberServiceImpl">
<property name="memberDAO" ref="memberDAOImpl"/>
</bean>
<bean id="memberDAOImpl" class="com.spring.dao.MemberDAOImpl"/>
(5)测试
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans03.xml");
MemberServiceImpl bean = (MemberServiceImpl)ioc.getBean("memberServiceImpl", MemberServiceImpl.class);
System.out.println(bean);
细节说明:
- 这里就体现出spring容器的依赖注入
- 注意在spring容器中, xml是作为一个整体来执行的, 即如果你引用到一个bean对象, 对你配置的顺序没有要求,即bean的顺序没有要求
- 建议还是按顺序,好处是阅读的时候,比较方便
2.5 引用/注入内部 bean 对象
在 spring 的 ioc 容器中, 可以直接在 bean 内部配置内部 依赖 bean 对象
<!--配置MemberServiceImpl对象-使用内部bean-->
<bean class="com.spring.service.MemberServiceImpl" id="memberService2">
<!--自己配置一个内部bean-->
<property name="memberDAO">
<bean class="com.spring.dao.MemberDAOImpl"/>
</property>
</bean>
2.6 引用/注入集合/数组类型
在 spring 的 ioc 容器, 给 bean 对象的集合/数组类型属性赋值
应用案例:
(1)创建 Master.java
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Master {
private String name;
private List<Monster> monsterList;
private Map<String, Monster> monsterMap;
private Set<Monster> monsterSet;
private String[] monsterName;
//这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式
//这里 Properties key 和 value 都是 String
private Properties pros;
public Master() {
}
public Master(String name) {
this.name = name;
}
public Set<Monster> getMonsterSet() {
return monsterSet;
}
public void setMonsterSet(Set<Monster> monsterSet) {
this.monsterSet = monsterSet;
}
public String[] getMonsterName() {
return monsterName;
}
public void setMonsterName(String[] monsterName) {
this.monsterName = monsterName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Monster> getMonsterList() {
return monsterList;
}
public void setMonsterList(List<Monster> monsterList) {
this.monsterList = monsterList;
}
public Map<String, Monster> getMonsterMap() {
return monsterMap;
}
public void setMonsterMap(Map<String, Monster> monsterMap) {
this.monsterMap = monsterMap;
}
public Properties getPros() {
return pros;
}
public void setPros(Properties pros) {
this.pros = pros;
}
}
(2)配置 beans.xml
<!--配置Master对象
体会 spring 容器配置特点 依赖注入-非常灵活
-->
<bean class="com.spring.bean.Master" id="master">
<property name="name" value="太上老君"/>
<!--给list属性赋值-->
<property name="monsterList">
<list>
<!--引用的方法-->
<ref bean="monster01"/>
<ref bean="monster02"/>
<!--内部bean-->
<bean class="com.spring.bean.Monster">
<property name="name" value="老鼠精"/>
<property name="monsterId" value="100"/>
<property name="skill" value="吃粮食"/>
</bean>
</list>
</property>
<!--给map属性赋值-->
<property name="monsterMap">
<map>
<entry>
<key>
<value>monster03</value>
</key>
<!--这里使用的外部bean,引入-->
<ref bean="monster03"/>
</entry>
<entry>
<key>
<value>monster04</value>
</key>
<ref bean="monster04"/>
</entry>
</map>
</property>
<!--给set属性赋值-->
<property name="monsterSet">
<set>
<ref bean="monster05"/>
<ref bean="monster06"/>
<bean class="com.spring.bean.Monster">
<property name="name" value="金角大王"/>
<property name="skill" value="吐水"/>
<property name="monsterId" value="666"/>
</bean>
</set>
</property>
<!--给数组属性赋值
array标签中使用 value 还是 bean , ref .. 要根据你的业务决定
-->
<property name="monsterName">
<array>
<value>小妖怪</value>
<value>大妖怪</value>
<value>老妖怪</value>
</array>
</property>
<!--给Properties属性赋值 结构k(String)-v(String)-->
<property name="pros">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
<prop key="ip">127.0.0.1</prop>
</props>
</property>
</bean>
(3)测试
public static void main(String[] args) {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Master master01 = ioc.getBean("master", Master.class);
//获取 list 集合
System.out.println("======list=======");
List<Monster> monster_list = master01.getMonsterList();
for (Monster monster : monster_list) {
System.out.println(monster);
}
//获取 map 集合
System.out.println("======map=======");
Map<String, Monster> monster_map = master01.getMonsterMap();
Set<Map.Entry<String, Monster>> entrySet = monster_map.entrySet();
for (Map.Entry<String, Monster> entry : entrySet) {
System.out.println(entry);
}
//获取 properties 集合
System.out.println("======properties=======");
Properties pros = master01.getPros();
String property1 = pros.getProperty("username");
String property2 = pros.getProperty("password");
String property3 = pros.getProperty("ip");
System.out.println(property1 + "\t" + property2 + "\t" + property3);
//获取数组
System.out.println("======数组=======");
String[] monsterName = master01.getMonsterName();
for (String s : monsterName) {
System.out.println("妖怪名= " + s);
}
//获取 set
System.out.println("======set=======");
Set<Monster> monsterSet = master01.getMonsterSet();
for (Monster monster : monsterSet) {
System.out.println(monster);
}
}
运行效果:
细节说明:
- 这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式
- key 是 string 而 value 也是 string
2.7 通过 util 名称空间创建 list
spring 的 ioc 容器, 可以通过 util 名称空间创建 list
<!--
通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用
-->
<util:list id="myListBook">
<value>三国演义</value>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</util:list>
<bean id="bookStore" class="com.spring.bean.BookStore">
<property name="bookList" ref="myListBook"/>
</bean>
集合
应用案例:
(1)创建 BookStore.java
public class BookStore {//书店
private List<String> bookList;
public BookStore() {
}
public List<String> getBookList() {
return bookList;
}
public void setBookList(List<String> bookList) {
this.bookList = bookList;
}
}
(2)修改 beans.xml , 增加配置
<!--
通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用
-->
<util:list id="myListBook">
<value>三国演义</value>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</util:list>
<bean id="bookStore" class="com.spring.bean.BookStore">
<property name="bookList" ref="myListBook"/>
</bean>
(3)测试
@Test
public void bookStoreTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
BookStore bookStore = ioc.getBean("bookStore", BookStore.class);
System.out.println(bookStore.getBookList());
}
2.8 级联属性赋值
spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值
应用案例:
(1)创建 Dept.java
public class Dept {
private String name;
public Dept() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)创建 Emp.java
public class Emp {
private String name;
private Dept dept;
public Emp() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
(3)修改 beans.xml , 增加配置
<!--配置Dept对象-->
<bean class="com.spring.bean.Dept" id="dept"/>
<!--配置Emp对象-->
<bean class="com.spring.bean.Emp" id="emp">
<property name="name" value="jack"/>
<property name="dept" ref="dept"/>
<!--给dept的name属性指定值[级联属性赋值]-->
<property name="dept.name" value="Java开发部门"/>
</bean>
2.9 通过静态工厂获取对象
在 spring 的 ioc 容器, 可以通过静态工厂获取 bean 对象
应用案例:
(1)创建 MyStaticFactory.java
import java.util.HashMap;
import java.util.Map;
public class MyStaticFactory {
private static Map<String, Monster> monsterMap;
static {
monsterMap = new HashMap<String, Monster>();
monsterMap.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
monsterMap.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public static Monster getMonster(String key) {
return monsterMap.get(key);
}
}
(2)修改 beans.xml , 增加配置
<!-- 通过静态工厂来获取 bean 对象 -->
<bean id="my_monster" class="com.spring.factory.MyStaticFactory"
factory-method="getMonster">
<!-- constructor-arg 标签提供 key -->
<constructor-arg value="monster_01"/>
</bean>
(3)测试
@Test
public void test02() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster = ioc.getBean("my_monster", Monster.class);
System.out.println(monster);
}
2.10 通过实例工厂获取对象
在 spring 的 ioc 容器, 可以通过实例工厂获取 bean 对象
应用案例:
(1)创建 MyInstanceFactory.java
import java.util.HashMap;
import java.util.Map;
public class MyInstanceFactory {
private Map<String, Monster> monster_map;
//非静态代码块
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "猴子精", "吃人"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public Monster getMonster(String key) {
return monster_map.get(key);
}
}
(2)配置 beans.xml
<!-- 通过实例工厂来获取 bean 对象 -->
<bean id="myInstanceFactory" class="com.spring.factory.MyInstanceFactory"/>
<bean id="my_monster2" factory-bean="myInstanceFactory"
factory-method="getMonster">
<constructor-arg value="monster_02"/>
</bean>
(3)测试代码
@Test
public void getBeanByInstanceFactory() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster my_monster = ioc.getBean("my_monster2", Monster.class);
System.out.println(my_monster);
}
(4)结果
2.11 通过 FactoryBean 获取对象
在 spring 的 ioc 容器,通过 FactoryBean 获取 bean 对象
应用案例:
(1)创建MyFactoryBean.java
说明:
- 该类实现了 FactoryBean 接口,并指定了泛型Monster
- 创建了一个map集合,用于存放创建好的对象
- 重写了getObject方法,返回keyVal对应的对象,即this.monster_map.get(keyVal)
- 重写了getObjectType方法,返回对象的运行类型
- 重写isSingleton方法,表示对象是否为单例,true为是
public class MyFactoryBean implements FactoryBean<Monster> {
private String keyVal;
private Map<String, Monster> monster_map;
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public void setKeyVal(String keyVal) {
this.keyVal = keyVal;
}
@Override
public Monster getObject() throws Exception {
// TODO Auto-generated method stub
return this.monster_map.get(keyVal);
}
@Override
public Class getObjectType() {
// TODO Auto-generated method stub
return Monster.class;
}
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
(2)配置 beans.xml
<!--
1. 通过 FactoryBean 来获取 bean 对象
2. name="keyVal" 就是 MyFactoryBean 定义的 setKeyVal 方法
3. value="monster_01" ,就是给 keyVal 的值
-->
<bean id="myFactoryBean" class="com.spring.factory.MyFactoryBean">
<property name="keyVal" value="monster_01"/>
</bean>
(3)测试代码
@Test
public void getBeanByFactoryBean() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster = ioc.getBean("myFactoryBean", Monster.class);
System.out.println(monster);
}
2.12 bean 配置信息重用(继承)
在 spring 的 ioc 容器中, 提供了一种继承的方式来实现 bean 配置信息的重用
应用案例:
(1)配置beans.xml
<!-- 继承的方式来实现 bean 配置信息的重用 -->
<bean id="monster10" class="com.spring.bean.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>
<!-- parent="monster10" 就是继承使用了 monster10 的配置信息 -->
<bean id="monster11" class="com.spring.bean.Monster" parent="monster10"/>
<!-- 当我们把某个bean设置为 abstract="true" 这个bean只能被继承,而不能实例化了 -->
<bean id="monster12" class="com.spring.bean.Monster" abstract="true">
<property name="monsterId" value="12"/>
<property name="name" value="美女蛇"/>
<property name="skill" value="吃人"/>
</bean>
<!-- parent="monster12" 就是继承使用了 monster12 的配置信息 -->
<bean id="monster13" class="com.spring.bean.Monster" parent="monster12"/>
说明:
- 可通过parent="monster10"方式继承其他bean,实现bean 配置信息的重用
- 把某个bean设置为 abstract="true" ,这个bean只能被继承,而不能实例化了
测试代码:
@Test
public void getBeanByExtends() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster1 = ioc.getBean("monster11", Monster.class);
System.out.println(monster1);
Monster monster2 = ioc.getBean("monster13", Monster.class);
System.out.println(monster2);
}
2.13 bean 创建顺序
说明:
(1)在 spring 的 ioc 容器, 默认是按照配置的顺序创建 bean 对象
<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />
会先创建 student01 这个 bean 对象,然后创建 department01 这个 bean 对象
(2)如果这样配置,表示 student01 对象依赖于 department01 对象
<bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.hspedu.bean.Department" />
就会先创建 department01 对象,再创建 student01 对象.
(3)如果使用ref关联两个bean,无需关注配置顺序
说明:spring会先把有关联的对象都创建好,再处理引用关系
案例:
- 先创建 id=memberDAOImpl
- 再创建 id = memberServiceImpl
- 调用 memberServiceImpl.setMemberDAO() 完成引用
- 先创建 id = memberServiceImpl
- 再创建 id=memberDAOImpl
- 最后调用 memberServiceImpl.setMemberDAO() 完成引用
2.14 bean 对象的单例和多例
在 spring 的 ioc 容器, 默认是按照单例创建的,即配置一个 bean 对象后,ioc 容器只会创建一个 bean 实例。
如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置 scope="prototype" 来指定,这样spring在获取对象时才会临时创建该对象,而不是像单例对象一样提前创建好
应用案例:
(1)创建 Car.java
public class cat {
public cat() {
System.out.println("cat 构造器");
}
}
(2)配置 beans.xml
<!--
如果希望 ioc 容器配置的某个 bean 对象,
是以多个实例形式创建可以通过配置 scope="prototype" 来指定
-->
<bean name="cat" scope="prototype" class="com.spring.bean.Cat"/>
(3)测试代码
@Test
public void getBeanByPrototype() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
for (int i = 0; i < 3; i++) {
Cat cat = ioc.getBean("cat", Cat.class);
System.out.println(cat);
}
}
细节说明:
- 在默认情况下 scope属性是 singleton,在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合
- 在ioc容器中, 只有一个这个bean对象
- 当执行getBean时, 返回的的是同一个对象
- 如果我们希望每次getBean返回一个新的Bean对象,则可以scope="prototype"
-
如 果 是 单 例 singleton, 同 时 希 望 在 getBean 时 才 创 建 , 可 以 指 定 懒 加 载lazy-init="true" (注意默认是 false)
-
通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求.
-
如果 scope="prototype" 这时你的 lazy-init 属性的值不管是 ture, 还是 false 都是在getBean 时候,才创建对象.
2.15 bean 的生命周期
:bean 对象创建是由 JVM 完成的,依次执行如下方法:
(1)创建 House.java
public class House {
private String name;
public House() {
System.out.println("House() 构造器");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("House setName()...");
this.name = name;
}
//初始化的方式
public void init() {
System.out.println("House init()..");
}
//销毁的方法
public void destory() {
System.out.println("House destory()..");
}
}
(2)配置beans.xml,配置 bean 的初始化方法和销毁方法
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.spring.bean.House"
init-method="init" destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>
(3)测试代码
@Test
public void beanLife() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
//关闭容器
((ConfigurableApplicationContext) ioc).close();
}
细节说明:
- 初始化 init 方法和 destory 方法, 是程序员来指定
- 销毁方法就是当关闭容器时,才会被调用
2.16 配置 bean 的后置处理器
说明:
(1)在 spring 的 ioc 容器,可以配置 bean 的后置处理器
(2)该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
(3)程序员可以在后置处理器中编写自己的代码
应用案例:
(1)创 建 后 置处 理 器 MyBeanPostProcessor.java
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在 bean 初始化之前完成某些任务
* @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返
回的 bean 对象也会被修改
* @param beanName: 就是 ioc 容器配置的 bean 的名称
* @return Object: 就是返回的 bean 对象
*/
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessBeforeInitialization 被 调 用 " + beanName + " bean= " + bean.getClass());
return bean;
}
/**
* 在 bean 初始化之后完成某些任务
* @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返
回的 bean 对象也会被修改
* @param beanName: 就是 ioc 容器配置的 bean 的名称
* @return Object: 就是返回的 bean 对象
*/
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("postProcessAfterInitialization 被调用 " + beanName + " bean= " + bean.getClass());
return bean;
}
}
0(2)配置beans03.xml
当我们在xml 容器配置文件 配置了 MyBeanPostProcessor,这时后置处理器对象,就会作用在该容器创建的所有Bean对象上
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.spring.bean.House"
init-method="init" destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>
<!-- bean 后置处理器的配置 -->
<bean id="myBeanPostProcessor" class="com.spring.bean.MyBeanPostProcessor" />
(3)测试代码
@Test
public void testBeanPostProcessor() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans03.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
//关闭容器
((ConfigurableApplicationContext) ioc).close();
}
疑惑点说明:
- 怎么执行到这个方法:使用 AOP(反射+动态代理+IO+容器+注解)
- 有什么用:可以对 IOC 容器中所有的对象进行统一处理 ,比如 日志处理/权限的校验/安全的验证/事务管理.
- 针对容器的所有对象吗:是的=>切面编程特点
2.17 通过属性文件给 bean 注入值
在 spring 的 ioc 容器,通过属性文件给 bean 注入值
应用案例:
(1)resources/ 下创建my.properties
可以在Unicode编码转换 - 站长工具 (chinaz.com)网站将中文转成unicode编码
monsterId=1000
name=\u4e4c\u9f9f\u7cbe
skill=\u7f29\u8116\u5b50
(2)修改 src\beans.xml , 继续完成配置
location="classpath:my.properties" 表示在类路径下的my.properties读取
这时我们的属性值通过${属性名}获取,这里说的 属性名 就是 my.properties文件中的 k=v 的k
<!--
1. 通过属性文件给 bean 注入值,
2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
-->
<context:property-placeholder location="classpath:my.properties"/>
<bean id="monster100" class="com.spring.bean.Monster">
<property name="monsterId" value="${monsterId}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>
(3)测试代码
@Test
public void setProByProFile() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster100 = ioc.getBean("monster100", Monster.class);
System.out.println(monster100);
}
2.18 基于 XML 的 bean 的自动装配
在 spring 的 ioc 容器,可以实现自动装配 bean
应用案例:
public class OrderDao {
public void saveOrder() {
System.out.println("保存 一个订单...");
}
}
创建 OrderService.java
public class OrderService {
private OrderDao orderDao;
public OrderDao getOrderDao() {
return orderDao;
}
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
}
创建 OrderAction.java
public class OrderAction {
private OrderService orderService;
public OrderService getOrderService() {
return orderService;
}
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}
(2)配置 beans.xml
根据类型进行自动组装
<!--
autowire="byType" 表示根据类型进行自动组装.
-->
<bean id="orderAction" autowire="byType" class="com.spring.action.OrderAction" />
<bean id="orderService" autowire="byType" class="com.spring.service.OrderService"/>
<bean id="orderDao" class="com.spring.dao.OrderDao"/>
根据名称进行自动组装
<!--
1. 说明: autowire = "byName" 会自动去找 id 为 setXxxx 后面 Xxxx 的 bean 自动组装.
,如果找到就装配,如果找不到就报错, 比如这里的
2. <bean id="orderAction" autowire="byName" class="com.spring.bean.OrderAction" />
就 会 去 找 OrderAction 类 中 定 义 的 setOrderService 的 id 为 orderService 的
OrderService bean 组装,找到就阻装,找不到就组装失败
-->
<bean id="orderAction" autowire="byName" class="com.spring.action.OrderAction"/>
<bean id="orderService" autowire="byName" class="com.spring.service.OrderService"/>
<bean id="orderDao" class="com.spring.dao.OrderDao"/>
说明:
(1)autowire="byType" 表示 在创建 orderService时通过类型的方式 给对象属性 自动完成赋值/引用
(2)比如OrderService 对象有 private OrderDao orderDao
(3)就会在容器中去找有没有 OrderDao类型对象
(4)如果有,就会自动的装配, 如果是按照 byType 方式来装配, 这个容器中,不能有两个OrderDao类型对象
(5)如果对象没有属性, autowire就没有必要写
(6)如果设置的是 autowire="byName" 表示通过名字完成自动装配
(7)比如下面的 autowire="byName" class="com.hspedu.spring.service.OrderService"
- 先看 OrderService 属性 private OrderDao orderDao
- 再根据这个属性的setXxx()方法的 xxx 来找对象id(而不是根据属性名)
- public void setOrderDao() 就会找id=orderDao对象来进行自动装配
- 如果没有就装配失败
2.19 spring el 表达式
(1)Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操 作对象。
(2)和 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象
(3)SpEL 使用#{…}作为定界符,所有在大框号中的字符都将被认为是 SpEL 表达式。
应用案例:
(1)创建 SpELBean.java
public class SpELBean {
private String name;
private Monster monster;
private String monsterName;
private String crySound;
private String bookName;
private Double result;
public SpELBean() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Monster getMonster() {
return monster;
}
public void setMonster(Monster monster) {
this.monster = monster;
}
public String getMonsterName() {
return monsterName;
}
public void setMonsterName(String monsterName) {
this.monsterName = monsterName;
}
public String getCrySound() {
return crySound;
}
public void setCrySound(String crySound) {
this.crySound = crySound;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Double getResult() {
return result;
}
public void setResult(Double result) {
this.result = result;
}
public String cry(String sound) {
return "发出 " + sound + "叫声...";
}
public static String read(String bookName) {
return "正在看 " + bookName;
}
}
(2)配置 beans.xml
<!-- spring el 表达式 -->
<bean id="spELBean" class="com.spring.bean.SpELBean">
<!-- sp el 给字面量 -->
<property name="name" value="#{'小苏'}"/>
<!-- sp el 引用其它 bean -->
<property name="monster" value="#{monster01}"/>
<!-- sp el 引用其它 bean 的属性值 -->
<property name="monsterName" value="#{monster01.name}"/>
<!-- sp el 调用普通方法(返回值) 赋值 -->
<property name="crySound" value="#{spELBean.cry('喵喵的..')}"/>
<!-- sp el 调用静态方法(返回值) 赋值 -->
<property name="bookName" value="#{T(com.spring.bean.SpELBean).read(' 天龙八部')}"/>
<!-- sp el 通过运算赋值 -->
<property name="result" value="#{89*1.2}"/>
</bean>
<!--配置一个monster对象-->
<bean id="monster01" class="com.spring.bean.Monster">
<property name="monsterId" value="100"/>
<property name="name" value="蜈蚣精~"/>
<property name="skill" value="蜇人~"/>
</bean>
(3)测试代码
@Test
public void setProBySpel() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans04.xml");
SpELBean spELBean = ioc.getBean("spELBean", SpELBean.class);
System.out.println(spELBean.getName());
System.out.println(spELBean.getMonster());
System.out.println(spELBean.getMonsterName());
System.out.println(spELBean.getCrySound());
System.out.println(spELBean.getBookName());
System.out.println(spELBean.getResult());
}
3 基于注解配置 bean
3.1 基本介绍
基于注解的方式配置 bean, 主要用于项目开发中的组件,比如 Controller、Service、和 Dao.
组件注解的形式有:
(1)@Component 表示当前注解标识的是一个组件
(2)@Controller 表示当前注解标识的是一个控制器,通常用于 Servlet
(3)@Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类
(4)@Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类
3.2 快速入门案例
使用注解的方式来配置 Controller / Service / Respository / Component
(1)创建 UserAction.java UserService.java, UserDao.java MyComponent.java
/**
* @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
*/
@Controller
public class UserAction {}
/**
* @Service 标识该类是一个Service类/对象
*/
@Service
public class UserService {
}
/**
* 使用 @Repository 标识该类是一个Repository是一个持久化层的类/对象
*/
@Repository
public class UserDao {
}
/**
* @Component 标识该类是一个组件, 是一个通用的注解
*/
@Component
public class MyComponent {
}
(2)在xml文件中配置要扫描的包
<!--配置容器要扫描的包
1. component-scan 要对指定包下的类进行扫描, 并创建对象到容器
2. base-package 指定要扫描的包
3. 含义是当spring容器创建/初始化时,就会扫描com.spring.component包
下的所有的 有注解 @Controller / @Service / @Respository / @Component类
将其实例化,生成对象,放入到ioc容器
-->
<context:component-scan base-package="com.spring.component"/>
(3)测试代码
@Test
public void getBeanByAnnotation() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
UserAction userAction = ioc.getBean(UserAction.class);
System.out.println(userAction);
UserDao userDao = ioc.getBean(UserDao.class);
System.out.println(userDao);
MyComponent myComponent = ioc.getBean(MyComponent.class);
System.out.println(myComponent);
UserService userService = ioc.getBean(UserService.class);
System.out.println(userService);
}
运行结果:
(5)细节说明:
- 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间。可以使用通配符 * 来指定 ,比如 com.spring.* 表示。
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.spring.component" />
-
Spring 的 IOC 容器不能检测一个使用了 @Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的 @Service@Repository 也是一样的道理( 也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的)
-
<context:component-scan base-package="com.hspedu.spring.component" resource-pattern="User*.class" />,resource-pattern="User*.class": 表示只扫描com.spring.component 和它的子包下的User打头的类
- 排除指定类
如果我们希望排除某个包/子包下的某种类型的注解,可以通过exclude-filter来指定
(1)context:exclude-filter 指定要排除哪些类
(2)type 指定排除方式 ,annotation表示按照注解来排除
(3)expression="org.springframework.stereotype.Service" 指定要排除的注解的全路径
<context:component-scan base-package="com.spring.component">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
以上配置表示不扫描带Service和Repository注解的类
- 扫描指定类
如果我们希望按照自己的规则,来扫描包/子包下的某些注解, 可以通过 include-filter
(1)use-default-filters="false" 表示不使用默认的过滤机制/扫描机制(这个一定要有)
(2)context:include-filter 表示要去扫描哪些类
(3)type="annotation" 按照注解方式来扫描/过滤
(4)expression="org.springframework.stereotype.Service" 指定要扫描的注解的全路径
<context:component-scan base-package="com.spring.component" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
以上配置表示只扫描com.spring.component包下,带Service、Repository、Controller注解的类
- 标记注解后,默认类名首字母小写作为 id 的值。也可以使用注解的 value 属性 指定 id 值,并且 value 可以省略。
@Controller(value="userAction01")
//value可以省略
@Controller("userAction01")
3.3 实现简单的 Spring 基于注解配置的程序
3.3.1 实现思路
3.3.2 代码实现
说明:以下实现代码基于上方入门案例中的四个类
(1)创建ComponentScan.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
//表示ComponentScan注解可以传入一个value属性
String value() default "";
}
(2)创建WwjSpringConfig.java
/**
* 这是一个配置类,作用类似于原生Spring的 beans.xml 容器配置文件
*/
@ComponentScan(value = "com.spring.component")
public class WwjSpringConfig {
}
(3)创建WwjSpringApplicationCo ntext.java
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* 该类作用类似于Spring原生ioc容器
*/
public class WwjSpringApplicationContext {
//拿到配置类.class文件
private Class configClass;
//ioc存放的就是通过反射创建的对象
private final ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();
//构造器
public WwjSpringApplicationContext(Class configClass){
this.configClass = configClass;
// System.out.println("this.configClass=" + this.configClass);
//获取要扫描的包
//1.先获取到ComponentScan注解
ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
//2.获取到注解的value值
String path = componentScan.value();
// System.out.println("要扫描的包= " + path);
//得到要扫描的包下的所有class文件(在target目录下)
//1.得到类的加载器
ClassLoader classLoader = WwjSpringApplicationContext.class.getClassLoader();
//2.通过类的加载器获取到要扫描的包的资源路径
path = path.replace(".","/");//一定要把.替换成/
URL resource = classLoader.getResource(path);
// System.out.println(resource);
//3.将要加载的资源(.class) 路径下的文件进行遍历
File file = new File(resource.getFile());
if (file.isDirectory()){
File[] files = file.listFiles();
for (File f : files) {
// System.out.println(f.getAbsolutePath());
//获取到.class文件的绝对路径
String fileAbsolutePath = f.getAbsolutePath();
//这里只处理.class文件
if (fileAbsolutePath.endsWith(".class")) {
//获取到类全类名
//1.获取到类名
String className = fileAbsolutePath.substring
(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
// System.out.println(className);
//2.拼接成全类名
String classFullName = path.replace("/",".") + "." + className;
// System.out.println(classFullName);
//3.判断该类是不是需要注入容器,看该类是不是有注解 @Component @Service @Repository @Controller
try {
//这时,就得到了该类的Class对象
//1. Class clazz = Class.forName(classFullName) 可以反射加载类
//2. classLoader.loadClass(classFullName); 也可以反射类的Class
//3. 区别是 : 上面方式会调用该类的静态方法, 下面方法不会
//4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
Class<?> aClass = classLoader.loadClass(classFullName);
//判断该类是否有 @Component @Service @Repository @Controller
if (aClass.isAnnotationPresent(Component.class) ||
aClass.isAnnotationPresent(Service.class) ||
aClass.isAnnotationPresent(Repository.class) ||
aClass.isAnnotationPresent(Controller.class)){
String keyVal = StringUtils.uncapitalize(className);
//获取到value值,替换指定id
if (aClass.isAnnotationPresent(Component.class)){
Component annotation = aClass.getDeclaredAnnotation(Component.class);
String value = annotation.value();
if (!value.equals("")){
keyVal = value;
}
} else if (aClass.isAnnotationPresent(Service.class)) {
Service annotation = aClass.getDeclaredAnnotation(Service.class);
String value = annotation.value();
if (!value.equals("")){
keyVal = value;
}
} else if (aClass.isAnnotationPresent(Repository.class)) {
Repository annotation = aClass.getDeclaredAnnotation(Repository.class);
String value = annotation.value();
if (!value.equals("")){
keyVal = value;
}
} else if (aClass.isAnnotationPresent(Controller.class)) {
Controller annotation = aClass.getDeclaredAnnotation(Controller.class);
String value = annotation.value();
if (!value.equals("")){
keyVal = value;
}
}
//这时候就可以创建该类的对象,并放入到ioc容器中
Class<?> clazz = Class.forName(classFullName);
Object o = clazz.newInstance();
// String defaultID = className.substring(0,1).toLowerCase() + className.substring(1);
// System.out.println(defaultID);
//org.springframework.util.StringUtils包中的静态方法可以将字符串首字母小写
ioc.put(keyVal,o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String id){
return ioc.get(id);
}
}
(4)创建测试主程序WwjSpringApplicationContextTest.java
public class WwjSpringApplicationContextTest {
public static void main(String[] args) {
WwjSpringApplicationContext ioc = new WwjSpringApplicationContext(WwjSpringConfig.class);
System.out.println(ioc.getBean("yss1"));
System.out.println(ioc.getBean("yss2"));
System.out.println(ioc.getBean("yss3"));
}
(5)运行结果
3.4 自动装配
基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource
3.4.1 @AutoWired 的规则说明
3.4.2 @Resource 的规则说明
3.4.3 应用案例
(1)实现 UserAction 和 UserService 的两级自动组装
@Service
public class UserService {
//方法..
public void hi(){
System.out.println("UserService hi()~");
}
}
/**
* @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
*/
@Controller
public class UserAction {
//@Autowired 自动装配 UserService, 这时是以 UserService,class 类型对对象进行组装
//如待装配的类型对应的 bean 在 IOC 容器中有多个,则是以 id=userService 的 UserService 对象进行组装
@Autowired
private UserService userService;
public void sayOk() {
System.out.println("UserAction 的sayOk()");
System.out.println("userAction 装配的 userService属性=" + userService);
userService.hi();
}
//不写这个方法,也可以完成组装
public void setUserService(UserService userService) {
this.userService = userService;
}
}
(2)测试代码
@Test
public void setProByAnnotationAutowired() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
UserAction userAction01 = ioc.getBean(UserAction.class);
userAction01.sayOk();
}
3.5 泛型依赖注入
基本说明:
(1)为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的注入机制
(2)在继承关系复杂情况下,泛型依赖注入就会有很大的优越性
应用实例:
(1)各个类关系图
(2)传统方法是将 PhoneDao /BookDao 自动装配到 BookService/PhoneSerive 中,当这种继承关系多时,就比较麻烦,可以使用 spring 提供的泛型依赖注入
(3)创建 Book.java,Phone.java等类
package com.spring.depinjection;
public class Book {
}
package com.spring.depinjection;
public class Phone {
}
package com.spring.depinjection;
public abstract class BaseDao<T> {
public abstract void save();
}
package com.spring.depinjection;
import org.springframework.stereotype.Repository;
@Repository
public class BookDao extends BaseDao<Book> {
@Override
public void save() {
System.out.println("BookDao 的 save()");
}
}
package com.spring.depinjection;
import org.springframework.stereotype.Repository;
@Repository
public class PhoneDao extends BaseDao<Phone> {
@Override
public void save() {
System.out.println("PhoneDao 的 save()");
}
}
package com.spring.depinjection;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save() {
baseDao.save();
}
}
package com.spring.depinjection;
import org.springframework.stereotype.Service;
@Service
public class BookService extends BaseService<Book> {
}
package com.spring.depinjection;
import org.springframework.stereotype.Service;
@Service
public class PhoneService extends BaseService<Phone> {
}
(4)修改 beans06.xml , 增加配置
<context:component-scan base-package="com.hspedu.spring.depinjection"/>
(5)测试代码
@Test
public void setProByDepinjectionAutowired() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
BookService bookService = ioc.getBean(BookService.class);
bookService.save();
PhoneService phoneService = ioc.getBean(PhoneService.class);
phoneService.save();
}
(6)测试结果