IOC[inversion Of Control 反转控制]
在传统的开发模式下,需要使用JdbcUtils / 反射,由程序员编写程序, 在程序中读取配置信息 ,然后创建对象, new Object???() // 反射方式,再使用对象完成任务。程序员不能够专注的实现对象对业务的处理。
IOC的开发模式很好的解决了这个问题。Spring 根据配置文件 xml/注解 创建对象,并将对象放入到容器(ConcurrentHashMap)中, 可以完成对象之间的依赖。
当需要使用某个对象实例的时候, 可以直接从容器中获取。程序员就可以更加关注如何使用对象完成相应的业务, (以前需要 new … ==> 注解/配置 方式)。
DI(Dependency Injection)依赖注入,可以理解成是 IOC 的另外叫法。Spring 最大的价值是通过配置,给程序提供需要使用的web 层[Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity]对象, 这个是核心价值所在,也是 ioc 的具体体现,实现解耦。
Spring 快速入门实例
在开始之前我们需要下载spring5的开发包。
commons-logging-1.1.3.jar:spring日志需要的jar。
spring开发ioc基本包:
spring-beans-5.3.8.jar
spring-context-5.3.8.jar
spring-core-5.3.8.jar
spring-expression-5.3.8.jar
还要导入Dom4j.jar包
创建一个beans.xml存放bean。
<!--配置Monster对象-->
<bean id="monster" class="com.spring.bean.Monster">
<property name="monsterId" value="1"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>
获取对象并给对象属性赋值输出。
package com.spring.test;
import com.spring.beans.Monster;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class ApplicationContext {
private ConcurrentHashMap<String, Object> ioc =
new ConcurrentHashMap<>();
public ApplicationContext(String iocBeanXmlFile) throws DocumentException,
ClassNotFoundException, IllegalAccessException, InstantiationException {
//反射的方式得到->使用dom4j
//得到一个解析器
SAXReader reader = new SAXReader();
Document document = reader.read(new File(iocBeanXmlFile));
//1. 得到rootElement
Element rootElement = document.getRootElement();
//2. 获取第1 个bean, 如果有多个就遍历所有
Element bean = (Element) rootElement.elements("bean").get(0);
String id = bean.attributeValue("id");
String classFullPath = bean.attributeValue("class");
List<Element> property = bean.elements("property");
Integer monsterId = Integer.parseInt(property.get(0).attributeValue("value"));
String name = property.get(1).attributeValue("value");
String skill = property.get(2).attributeValue("value");
//3. 使用反射创建bean 实例,并放入到ioc 中
Class cls = Class.forName(classFullPath);
Monster instance = (Monster)cls.newInstance();
instance.setMonsterId(monsterId);
instance.setName(name);
instance.setSkill(skill);
ioc.put(id, instance);
}
public Object getBean(String id) {
return ioc.get(id);
}
}
创建一个测试类
package com.spring.test;
import com.spring.beans.Monster;
import org.dom4j.DocumentException;
public class SpringIOCTest {
public static void main(String[] args) throws DocumentException,
IllegalAccessException, InstantiationException, ClassNotFoundException {
//得到beans.xml 文件.
ApplicationContext ioc = new ApplicationContext("src/beans.xml");
Monster monster01 = (Monster)ioc.getBean("monster01");
System.out.println("ioc 的monster01= " + monster01);
}
}
获取bean的方法
通过类型获取bean
//通过类型来获取容器的bean 对象
@Test
public void getMonsterByType() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
//创建容器
Monster monster = ioc.getBean(Monster.class);
//获取容器中Monster类型的bean对象
System.out.println("monster=" + monster);
Monster monster1 = ioc.getBean(Monster.class);
System.out.println("monster == monster1 的值= " + (monster == monster1));
}
按类型来获取bean, 要求ioc 容器中的同一个类的bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException。一般应用于在一个线程中只需要一个对象实例(单例)的情况。在容器配置文件(比如beans.xml)中给属性赋值, 底层是通过setter 方法完成的。
通过构造器配置bean
在spring的ioc容器,可以通过构造器来配置bean对象。
<bean id="monster02" class="com.spring.beans.Monster">
<constructor-arg value="2" index="0"/>
<constructor-arg value="蜘蛛精" index="1"/>
<constructor-arg value="蛛丝束缚" index="2"/>
//1. 通过index 属性来区分是第几个参数
</bean>
<!--数据类型就是对应的Java 数据类型,按构造器参数顺序-->
<bean id="monster03" class="com.spring.beans.Monster">
<constructor-arg value="3" type="java.lang.Integer"/>
<constructor-arg value="白骨精" type="java.lang.String"/>
<constructor-arg value="白骨鞭" type="java.lang.String"/>
</bean>
//2. 通过type 属性来区分是什么类型(按照顺序)
通过p名称空间配置bean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--在spring 的ioc 容器, 可以通过p 名称空间来配置bean 对象-->
<bean id="monster04" class="com.spring.beans.Monster"
p:monsterId="4"
p:name="红孩儿"
p:skill="吐火~"
/>
引用/注入其它bean 对象
在spring 的ioc 容器, 可以通过ref 来实现bean 对象的相互引用。
<!-- 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"/>
引用/注入内部bean 对象
<bean id="memberServiceImpl02"
class="com.spring.service.MemberServiceImpl">
<property name="memberDAO">
<bean class="com.spring.dao.MemberDAOImpl"/>
</property>
</bean>
引用/注入集合/数组类型
<bean id="master01" class="com.spring.beans.Master">
<property name="name" value="太上老君"/>
<!-- 给bean 对象的list 集合赋值-->
<property name="monsterList">
<list>
<ref bean="monster03"/>
<ref bean="monster02"/>
</list>
</property>
<!-- 给bean 对象的map 集合赋值-->
<property name="monsterMap">
<map>
<entry>
<key>
<value>monsterKey01</value>
</key>
<ref bean="monster01"/>
</entry>
<entry>
<key>
<value>monsterKey02</value>
</key>
<ref bean="monster02"/>
</entry>
</map>
</property>
<!-- 给bean 对象的properties 集合赋值-->
<property name="pros">
<props>
<prop key="k1">Java 工程师</prop>
<prop key="k2">前端工程师</prop>
<prop key="k3">大数据工程师</prop>
</props>
</property>
<!-- 给bean 对象的数组属性注入值-->
<property name="monsterName">
<array>
<value>银角大王</value>
<value>金角大王</value>
</array>
</property>
<!-- 给bean 对象的set 属性注入值-->
<property name="monsterSet">
<set>
<ref bean="monster01"/>
<bean class="com.spring.beans.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="玉兔"/>
<property name="skill" value="钻地洞"/>
</bean>
</set>
</property>
</bean>
测试
@Test
public void setCollectionByPro() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Master master01 = ioc.getBean("master01", 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("k1");
String property2 = pros.getProperty("k2");
String property3 = pros.getProperty("k3");
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);
}
}
细节
- 主要掌握List/Map/Properties 三种集合的使用。
- Properties 集合的特点。
- 这个Properties 是Hashtable 的子类, 是key-value 的形式。
- key 是string 而value 也是string。
通过util 名称空间创建list
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--通过util 名称空间来创建list 集合,可以当做创建bean 对象的工具来使用-->
<util:list id="myListBook">
<value>三国演义</value>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</util:list>
<bean id="bookStore" class="com.spring.beans.BookStore">
<property name="bookList" ref="myListBook"/>
</bean>
级联属性赋值
spring 的ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值。
<bean id="emp" class="com.spring.beans.Emp">
<property name="name" value="jack"/>
<property name="dept" ref="dept"/>
<property name="dept.name" value="Java 开发部"/>
</bean>
<bean id="dept" class="com.spring.beans.Dept"/>
package com.spring.beans;
public class Dept {
private String name;
public Dept() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.spring.beans;
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;
}
}
通过静态工厂获取对象
package com.spring.factory;
import com.spring.beans.Monster;
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);
}
}
<!-- 通过静态工厂来获取bean 对象-->
<bean id="my_monster" class="com.spring.factory.MyStaticFactory" factory-method="getMonster">
<!-- constructor-arg 标签提供key -->
<constructor-arg value="monster_01"/>
</bean>
通过实例工厂获取对象
package com.spring.factory;
import com.spring.beans.Monster;
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);
}
}
<bean id="myInstanceFactory" class="com.spring.factory.MyInstanceFactory"/>
<bean id="my_monster2" factory-bean="myInstanceFactory"
factory-method="getMonster">
<constructor-arg value="monster_02"/>
</bean>
bean 配置信息重用(继承)
在spring 的ioc 容器, 提供了一种继承的方式来实现bean 配置信息的重用。
<!-- 继承的方式来实现bean 配置信息的重用-->
<bean id="monster10" class="com.spring.beans.Monster">
<property name="monsterId" value="10"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>
<!-- parent="monster10" 就是继承使用了monster10 的配置信息-->
<bean id="monster11" class="com.spring.beans.Monster"
parent="monster10"/>
<!-- 当我们把某个bean设置为abstract="true" 这个bean只能被继承,而不能实例化了-->
<bean id="monster12" class="com.spring.beans.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.beans.Monster"
parent="monster12"/>
测试
@Test
public void getBeanByExtends() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster1 = ioc.getBean("monster11", Monster.class);
System.out.println(monster1);
Monster monster2 = (Monster) ioc.getBean("monster13", Monster.class);
System.out.println(monster2);
}
bean 创建顺序
- 在spring 的ioc 容器, 默认是按照配置的顺序创建bean 对象
<bean id="student01" class="com.bean.Student" />
<bean id="department01" class="com.bean.Department" />
会先创建student01 这个bean 对象,然后创建department01 这个bean 对象。
- 如果这样配置
<bean id="student01" class="com.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.bean.Department" />
会先创建department01 对象,再创建student01 对象.
bean 对象的单例和多例
在spring 的ioc 容器, 在默认是按照单例创建的,即配置一个bean 对象后,ioc 容器只会创建一个bean 实例。
如果我们希望ioc容器配置某个bean 对象,以多个实例形式创建,则可以通过配置scope=“prototype” 来指定。
//配置beans.xml
<bean name="car" scope="prototype" class="com.spring.beans.Car"/>
测试
@Test
public void getBeanByPrototype() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
for (int i = 0; i < 3; i++) {
Car car = ioc.getBean("car", Car.class);
System.out.println(car);
}
}
注意
1. 默认是单例singleton, 在启动容器时, 默认就会创建, 并放入到singletonObjects 集合。
2. 当<bean scope="prototype" > 设置为多实例机制后, 该bean 是在getBean()时才创建。
3. 如果是单例singleton, 同时希望在getBean 时才创建, 可以指定懒加载
lazy-init="true"。
4. 通常情况下, lazy-init 使用默认值false , 在开发看来用空间换时间是值得的, 除非有特殊的要求。
5. 如果scope="prototype" 多实例机制,lazy-init 属性的值不管是ture, 还是false 都是在getBean 时候创建对象。
bean 的生命周期
bean 对象创建是由JVM 完成的。通过如下步骤可以进行测试
-
执行构造器。
-
执行set 相关方法。
package com.spring.beans; 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().."); } }
-
调用bean 的初始化的方法(需要配置)。
<!-- 配置bean 的初始化方法和销毁方法--> <bean id="house" class="com.spring.beans.House" init-method="init" destroy-method="destory"> <property name="name" value="深圳"/> </bean>
-
使用bean。
-
当容器关闭时候,调用bean 的销毁方法(需要配置)。
测试
@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 方法, 由程序员设置。
- 销毁方法当关闭容器时,才会被调用。
配置bean 的后置处理器
- 在spring 的ioc 容器,可以配置bean 的后置处理器。
- 该处理器/对象会在bean 初始化方法调用前和初始化方法调用后被调用。
- 程序员可以在后置处理器中编写自己的代码。
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;
}
}
配置beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置bean 的初始化方法和销毁方法-->
<bean id="house" class="com.spring.beans.House"
init-method="init"
destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>
<!-- bean 后置处理器的配置-->
<bean id="myBeanPostProcessor"
class="com.spring.beans.MyBeanPostProcessor" />
</beans>
测试
@Test
public void testBeanPostProcessor() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans02.xml");
House house = ioc.getBean("house", House.class);
System.out.println(house);
//关闭容器
((ConfigurableApplicationContext) ioc).close();
}
使用AOP(反射+动态代理+IO+容器+注解)执行后置处理器,可以对IOC 容器中所有的对象进行统一处理,比如日志处理/权限的校验/安全的验证/事务管理。是切面编程的特点。
通过属性文件给bean 注入值
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--
1. 通过属性文件给bean 注入值,
2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
-->
<context:property-placeholder location="classpath:my.properties"/>
<bean id="monster10" class="com.spring.beans.Monster">
<property name="monsterId" value="${id}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>