控制反转(IOC)
是什么
控制反转(Inversion of Control,IoC)是一种设计思想,它的本质是将对象的创建、销毁、依赖关系的维护等控制权从程序代码中转移出去,交由容器来负责管理。在Java开发中,IoC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。IoC容器负责创建对象、维护对象之间的关系,并在需要时注入依赖。
IoC的设计思想是:将原本在程序中手动创建对象的控制权,交由容器来管理。传统Java SE程序设计中,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由IoC容器来控制对象的创建。IoC容器控制了对象,主要控制了外部资源获取(不只是对象包括比如文件等)。
依赖注入(Dependency Injection,DI)是IoC的一种实现方式。DI是指组件之间依赖关系由容器在运行期决定,形象地说,即由容器动态地将某个依赖关系注入到组件之中。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
ioc的底层原理是 工厂设计模式+反射+xml解析
简单来说,对象由spring创建,属性也由spring来赋值。控制反转的核心是:将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三方容器来负责创建与维护。
为什么需要IOC
原来的三层架构,Dao层(连接数据库,做数据库的操作),service层(业务处理)和web层(调用service层的方法和前端做交互)代码之间耦合度太高,所以使用spring的ioc来管理对象,三层架构的设计违背了OCP(开闭原则)原则,即扩展应该是开放的,不应该造成原来代码的修改,对原来代码的修改时关闭的。
实现方式:依赖注入(DI)
依赖:A对象和B对象有关系。注入:一种数据传递行为,用来让A和B对象产生关系
两种方式:set方法和构造方法
bean注入方式
set注入
基于set方法实现,底层会调用反射机制会调用属性对应的set方法来给属性赋值。要求实体类里面必须有对应属性的set方法
property标签的ref是要注入的Bean对象的id
有参构造方式
核心原理是调用有参构造器给属性赋值
通过测试得知,通过构造方法注入的时候:
- 可以通过下标 index
- 可以通过参数名 name
- 也可以不指定下标和参数名,可以类型自动推断。
set注入细化
注入外部bean
bean定义到外面,在property标签使用ref注入属性,常用。
注入内部Bean
Bean标签里面嵌套Bean,这种方式作了解
<?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="userServiceBean" class="com.powernode.spring6.service.UserService">
<property name="userDao">
<bean class="com.powernode.spring6.dao.UserDao"/>
</property>
</bean>
</beans>
注入简单类型
需要特别注意:如果给简单类型赋值,使用value属性或value标签。而不是ref。
简单类型包括哪些呢?可以通过Spring的源码来分析一下:BeanUtils类
public class BeanUtils{
//.......
/**
* Check if the given type represents a "simple" property: a simple value
* type or an array of simple value types.
* <p>See {@link #isSimpleValueType(Class)} for the definition of <em>simple
* value type</em>.
* <p>Used to determine properties to check for a "simple" dependency-check.
* @param type the type to check
* @return whether the given type represents a "simple" property
* @see org.springframework.beans.factory.support.RootBeanDefinition#DEPENDENCY_CHECK_SIMPLE
* @see org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#checkDependencies
* @see #isSimpleValueType(Class)
*/
public static boolean isSimpleProperty(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
/**
* Check if the given type represents a "simple" value type: a primitive or
* primitive wrapper, an enum, a String or other CharSequence, a Number, a
* Date, a Temporal, a URI, a URL, a Locale, or a Class.
* <p>{@code Void} and {@code void} are not considered simple value types.
* @param type the type to check
* @return whether the given type represents a "simple" value type
* @see #isSimpleProperty(Class)
*/
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
//........
}
通过源码分析得知,简单类型包括:
- 基本数据类型
- 基本数据类型对应的包装类
- String或其他的CharSequence子类
- Number子类
- Date子类
- Enum子类
- URI
- URL
- Temporal子类
- Locale
- Class
- 另外还包括以上简单值类型对应的数组类型。
注入数组
<array>标签里面 value标签或者ref标签
要点:
- 如果数组中是简单类型,使用value标签。
- 如果数组中是非简单类型,使用ref标签。
注入集合
注意:注入List集合的时候使用list标签,如果List集合中是简单类型使用value标签,反之使用ref标签。
注入set集合
无序不可重复
要点:
- 使用<set>标签
- set集合中元素是简单类型的使用value标签,反之使用ref标签。
注入map集合
要点:
- 使用<map>标签
- 如果key是简单类型,使用 key 属性,反之使用 key-ref 属性。
- 如果value是简单类型,使用 value 属性,反之使用 value-ref 属性。
注入properties
java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合。key和value都是字符串
要点:
- 使用<props>标签嵌套<prop>标签完成。
- prop带name 标签之间是value
注入null和空字符串
注入空字符串使用:<value/> 或者 value=""
注入null使用:<null/> 或者 不为该属性赋值
注入的值含有特殊符号
解决方案包括两种:
- 第一种:特殊符号使用转义字符代替。
- 第二种:将含有特殊符号的字符串放到:<![CDATA[ 内容]]> 当中。因为放在CDATA区中的数据不会被XML文件解析器解析。
<property name="result">
<!--只能使用value标签-->
<value><![CDATA[2 < 3]]></value>
</property>
P命名空间的注入
目的是简化配置,不用再使用Property标签了,对set注入的简化,实体类需要setter方法
xml里面配置头部信息 xmlns:p="http://www.springframework.org/schema/p"
然后编写Bean标签 格式 p:属性="值"
<bean id="Ptest" class="com.lch.pojo.Ptest" p:name="lch" p:id="10"></bean>
编写测试程序
@Test
public void test1(){
ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml");
Ptest ptest = (Ptest) Context.getBean("Ptest");
System.out.println(ptest);
}
c命名空间注入(构造方法注入)
目的是简化配置,简化构造方法注入方式 实体类需要配置构造方法
xml里面配置头部信息 xmlns:c="http://www.springframework.org/schema/c"
<bean id="CTest" class="com.lch.pojo.CTest" c:_0="C命名空间注入" c:_1="1"></bean>
util命名空间
可以让配置复用
spring配置文件里配置头部信息,实体类需要set方法
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">
基于xml的自动装配
autowire指定自动装配方式
根据名称装配(byName)
这说明,如果根据名称装配(byName),底层会调用set方法进行注入。
例如:setAge() 对应的名字是age,setPassword()对应的名字是password,setEmail()对应的名字是email。
userService有个userDao对象 名字为aaa 而set方法为setAaa 和bean id ="aaa"对应 可以实现自动注入
<bean id="userService" class="com.powernode.spring6.service.UserService" autowire="byName"/>
<bean id="aaa" class="com.powernode.spring6.dao.UserDao"/>
根据类型自动装配(byType)
基于set方法,某种Bean的类型必须是唯一的
<!--byType表示根据类型自动装配-->
<bean id="accountService" class="com.powernode.spring6.service.AccountService" autowire="byType"/>
<bean class="com.powernode.spring6.dao.AccountDao"/>
IOC创建对象的方式(Bean对象创建方式)
bean标签配置方法
<bean id= class = >
bean的id和class属性:
- id属性:代表对象的唯一标识。可以看做一个人的身份证号。
- class属性:用来指定要创建的java对象的类名,这个类名必须是全限定类名(带包名)。
<property name= value=>标签
根据下标,根据名字
默认无参构造的方式
配置文件加载时,容器中管理的对象就已经初始化了。
要让spring创建对象,必须有无参构造,spring使用无参构造创建对象的原理:利用反射机制创建
// dom4j解析beans.xml文件,从中获取class的全限定类名
// 通过反射机制调用无参数构造方法创建对象
Class clazz = Class.forName("com.powernode.spring6.bean.User");
Object obj = clazz.newInstance();